@dainamite/cpq 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (504) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION.md +236 -0
  4. package/README.md +108 -0
  5. package/dist/modules/cpq/acl.js +30 -0
  6. package/dist/modules/cpq/acl.js.map +7 -0
  7. package/dist/modules/cpq/api/_helpers/handleArcError.js +37 -0
  8. package/dist/modules/cpq/api/_helpers/handleArcError.js.map +7 -0
  9. package/dist/modules/cpq/api/attribute-options/route.js +47 -0
  10. package/dist/modules/cpq/api/attribute-options/route.js.map +7 -0
  11. package/dist/modules/cpq/api/bundle-slots/route.js +101 -0
  12. package/dist/modules/cpq/api/bundle-slots/route.js.map +7 -0
  13. package/dist/modules/cpq/api/bundle-tree/route.js +28 -0
  14. package/dist/modules/cpq/api/bundle-tree/route.js.map +7 -0
  15. package/dist/modules/cpq/api/context.js +29 -0
  16. package/dist/modules/cpq/api/context.js.map +7 -0
  17. package/dist/modules/cpq/api/inventory/assets/route.js +113 -0
  18. package/dist/modules/cpq/api/inventory/assets/route.js.map +7 -0
  19. package/dist/modules/cpq/api/inventory/assets/status/route.js +35 -0
  20. package/dist/modules/cpq/api/inventory/assets/status/route.js.map +7 -0
  21. package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js +30 -0
  22. package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js.map +7 -0
  23. package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js +31 -0
  24. package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js.map +7 -0
  25. package/dist/modules/cpq/api/inventory/subscription-items/route.js +99 -0
  26. package/dist/modules/cpq/api/inventory/subscription-items/route.js.map +7 -0
  27. package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js +57 -0
  28. package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js.map +7 -0
  29. package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js +62 -0
  30. package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js.map +7 -0
  31. package/dist/modules/cpq/api/inventory/subscriptions/route.js +116 -0
  32. package/dist/modules/cpq/api/inventory/subscriptions/route.js.map +7 -0
  33. package/dist/modules/cpq/api/inventory/subscriptions/status/route.js +35 -0
  34. package/dist/modules/cpq/api/inventory/subscriptions/status/route.js.map +7 -0
  35. package/dist/modules/cpq/api/offering-components/route.js +91 -0
  36. package/dist/modules/cpq/api/offering-components/route.js.map +7 -0
  37. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js +39 -0
  38. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +7 -0
  39. package/dist/modules/cpq/api/orders/[orderId]/route.js +28 -0
  40. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +7 -0
  41. package/dist/modules/cpq/api/orders/[orderId]/status/route.js +38 -0
  42. package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +7 -0
  43. package/dist/modules/cpq/api/orders/route.js +71 -0
  44. package/dist/modules/cpq/api/orders/route.js.map +7 -0
  45. package/dist/modules/cpq/api/price-rules/route.js +180 -0
  46. package/dist/modules/cpq/api/price-rules/route.js.map +7 -0
  47. package/dist/modules/cpq/api/pricing-table-entries/route.js +142 -0
  48. package/dist/modules/cpq/api/pricing-table-entries/route.js.map +7 -0
  49. package/dist/modules/cpq/api/pricing-tables/route.js +179 -0
  50. package/dist/modules/cpq/api/pricing-tables/route.js.map +7 -0
  51. package/dist/modules/cpq/api/product-attributes/route.js +160 -0
  52. package/dist/modules/cpq/api/product-attributes/route.js.map +7 -0
  53. package/dist/modules/cpq/api/product-charges/route.js +166 -0
  54. package/dist/modules/cpq/api/product-charges/route.js.map +7 -0
  55. package/dist/modules/cpq/api/product-offerings/route.js +294 -0
  56. package/dist/modules/cpq/api/product-offerings/route.js.map +7 -0
  57. package/dist/modules/cpq/api/product-relationships/route.js +144 -0
  58. package/dist/modules/cpq/api/product-relationships/route.js.map +7 -0
  59. package/dist/modules/cpq/api/product-specifications/route.js +292 -0
  60. package/dist/modules/cpq/api/product-specifications/route.js.map +7 -0
  61. package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js +71 -0
  62. package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js.map +7 -0
  63. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +29 -0
  64. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js.map +7 -0
  65. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +54 -0
  66. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +7 -0
  67. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -0
  68. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +7 -0
  69. package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js +72 -0
  70. package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js.map +7 -0
  71. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +35 -0
  72. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +7 -0
  73. package/dist/modules/cpq/api/quotes/[quoteId]/route.js +102 -0
  74. package/dist/modules/cpq/api/quotes/[quoteId]/route.js.map +7 -0
  75. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +27 -0
  76. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +7 -0
  77. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js +79 -0
  78. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js.map +7 -0
  79. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js +95 -0
  80. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js.map +7 -0
  81. package/dist/modules/cpq/api/quotes/attributes/route.js +52 -0
  82. package/dist/modules/cpq/api/quotes/attributes/route.js.map +7 -0
  83. package/dist/modules/cpq/api/quotes/from-subscription/route.js +72 -0
  84. package/dist/modules/cpq/api/quotes/from-subscription/route.js.map +7 -0
  85. package/dist/modules/cpq/api/quotes/price/route.js +69 -0
  86. package/dist/modules/cpq/api/quotes/price/route.js.map +7 -0
  87. package/dist/modules/cpq/api/quotes/route.js +97 -0
  88. package/dist/modules/cpq/api/quotes/route.js.map +7 -0
  89. package/dist/modules/cpq/api/quotes/validate-relationships/route.js +86 -0
  90. package/dist/modules/cpq/api/quotes/validate-relationships/route.js.map +7 -0
  91. package/dist/modules/cpq/api/resolveQuotingService.js +7 -0
  92. package/dist/modules/cpq/api/resolveQuotingService.js.map +7 -0
  93. package/dist/modules/cpq/api/wizards/resolve/route.js +38 -0
  94. package/dist/modules/cpq/api/wizards/resolve/route.js.map +7 -0
  95. package/dist/modules/cpq/api/wizards/route.js +111 -0
  96. package/dist/modules/cpq/api/wizards/route.js.map +7 -0
  97. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +136 -0
  98. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +7 -0
  99. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +185 -0
  100. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +7 -0
  101. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +343 -0
  102. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +7 -0
  103. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +13 -0
  104. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +7 -0
  105. package/dist/modules/cpq/backend/cpq/inventory/page.js +35 -0
  106. package/dist/modules/cpq/backend/cpq/inventory/page.js.map +7 -0
  107. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +18 -0
  108. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +7 -0
  109. package/dist/modules/cpq/backend/cpq/inventory/shared.js +20 -0
  110. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +7 -0
  111. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +794 -0
  112. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +7 -0
  113. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +13 -0
  114. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +7 -0
  115. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +917 -0
  116. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +7 -0
  117. package/dist/modules/cpq/backend/cpq/offerings/page.js +405 -0
  118. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +7 -0
  119. package/dist/modules/cpq/backend/cpq/offerings/page.meta.js +18 -0
  120. package/dist/modules/cpq/backend/cpq/offerings/page.meta.js.map +7 -0
  121. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +677 -0
  122. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +7 -0
  123. package/dist/modules/cpq/backend/cpq/orders/page.js +134 -0
  124. package/dist/modules/cpq/backend/cpq/orders/page.js.map +7 -0
  125. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +18 -0
  126. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +7 -0
  127. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +272 -0
  128. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +7 -0
  129. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +407 -0
  130. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +7 -0
  131. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +13 -0
  132. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +7 -0
  133. package/dist/modules/cpq/backend/cpq/price-rules/page.js +211 -0
  134. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +7 -0
  135. package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js +18 -0
  136. package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js.map +7 -0
  137. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +275 -0
  138. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +7 -0
  139. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +112 -0
  140. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +7 -0
  141. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +492 -0
  142. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +7 -0
  143. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js +50 -0
  144. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js.map +7 -0
  145. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +249 -0
  146. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +7 -0
  147. package/dist/modules/cpq/backend/cpq/pricing/page.js +87 -0
  148. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +7 -0
  149. package/dist/modules/cpq/backend/cpq/pricing/page.meta.js +18 -0
  150. package/dist/modules/cpq/backend/cpq/pricing/page.meta.js.map +7 -0
  151. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +652 -0
  152. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +7 -0
  153. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +1289 -0
  154. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +7 -0
  155. package/dist/modules/cpq/backend/cpq/quotes/page.js +283 -0
  156. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +7 -0
  157. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +18 -0
  158. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +7 -0
  159. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +214 -0
  160. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +7 -0
  161. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +1399 -0
  162. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +7 -0
  163. package/dist/modules/cpq/backend/cpq/specifications/page.js +148 -0
  164. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +7 -0
  165. package/dist/modules/cpq/backend/cpq/specifications/page.meta.js +18 -0
  166. package/dist/modules/cpq/backend/cpq/specifications/page.meta.js.map +7 -0
  167. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +204 -0
  168. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +7 -0
  169. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +191 -0
  170. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +7 -0
  171. package/dist/modules/cpq/backend/cpq/wizards/page.js +119 -0
  172. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +7 -0
  173. package/dist/modules/cpq/backend/cpq/wizards/page.meta.js +18 -0
  174. package/dist/modules/cpq/backend/cpq/wizards/page.meta.js.map +7 -0
  175. package/dist/modules/cpq/ce.js +151 -0
  176. package/dist/modules/cpq/ce.js.map +7 -0
  177. package/dist/modules/cpq/cli.js +11 -0
  178. package/dist/modules/cpq/cli.js.map +7 -0
  179. package/dist/modules/cpq/components/CpqListView.js +265 -0
  180. package/dist/modules/cpq/components/CpqListView.js.map +7 -0
  181. package/dist/modules/cpq/components/NumberInput.js +95 -0
  182. package/dist/modules/cpq/components/NumberInput.js.map +7 -0
  183. package/dist/modules/cpq/components/StatusPath.js +60 -0
  184. package/dist/modules/cpq/components/StatusPath.js.map +7 -0
  185. package/dist/modules/cpq/components/statusMaps.js +66 -0
  186. package/dist/modules/cpq/components/statusMaps.js.map +7 -0
  187. package/dist/modules/cpq/components/useCpqRowActions.js +125 -0
  188. package/dist/modules/cpq/components/useCpqRowActions.js.map +7 -0
  189. package/dist/modules/cpq/data/entities.js +1556 -0
  190. package/dist/modules/cpq/data/entities.js.map +7 -0
  191. package/dist/modules/cpq/data/validators.js +575 -0
  192. package/dist/modules/cpq/data/validators.js.map +7 -0
  193. package/dist/modules/cpq/di.js +57 -0
  194. package/dist/modules/cpq/di.js.map +7 -0
  195. package/dist/modules/cpq/events.js +50 -0
  196. package/dist/modules/cpq/events.js.map +7 -0
  197. package/dist/modules/cpq/index.js +16 -0
  198. package/dist/modules/cpq/index.js.map +7 -0
  199. package/dist/modules/cpq/lib/seeds/api.js +32 -0
  200. package/dist/modules/cpq/lib/seeds/api.js.map +7 -0
  201. package/dist/modules/cpq/lib/seeds/tenant-provisioning.js +158 -0
  202. package/dist/modules/cpq/lib/seeds/tenant-provisioning.js.map +7 -0
  203. package/dist/modules/cpq/lib/seeds.js +6 -0
  204. package/dist/modules/cpq/lib/seeds.js.map +7 -0
  205. package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js +40 -0
  206. package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js.map +7 -0
  207. package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js +54 -0
  208. package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js.map +7 -0
  209. package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js +15 -0
  210. package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js.map +7 -0
  211. package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js +62 -0
  212. package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js.map +7 -0
  213. package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js +56 -0
  214. package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js.map +7 -0
  215. package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js +26 -0
  216. package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js.map +7 -0
  217. package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js +37 -0
  218. package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js.map +7 -0
  219. package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js +35 -0
  220. package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js.map +7 -0
  221. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js +21 -0
  222. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js.map +7 -0
  223. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js +46 -0
  224. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js.map +7 -0
  225. package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js +118 -0
  226. package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js.map +7 -0
  227. package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js +63 -0
  228. package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js.map +7 -0
  229. package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js +28 -0
  230. package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js.map +7 -0
  231. package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js +36 -0
  232. package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js.map +7 -0
  233. package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js +14 -0
  234. package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js.map +7 -0
  235. package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js +14 -0
  236. package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js.map +7 -0
  237. package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js +58 -0
  238. package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js.map +7 -0
  239. package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js +29 -0
  240. package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js.map +7 -0
  241. package/dist/modules/cpq/services/cpqBundleService.js +447 -0
  242. package/dist/modules/cpq/services/cpqBundleService.js.map +7 -0
  243. package/dist/modules/cpq/services/cpqInventoryService.js +1371 -0
  244. package/dist/modules/cpq/services/cpqInventoryService.js.map +7 -0
  245. package/dist/modules/cpq/services/cpqOrderService.js +901 -0
  246. package/dist/modules/cpq/services/cpqOrderService.js.map +7 -0
  247. package/dist/modules/cpq/services/cpqPricingService.js +289 -0
  248. package/dist/modules/cpq/services/cpqPricingService.js.map +7 -0
  249. package/dist/modules/cpq/services/cpqProductService.js +204 -0
  250. package/dist/modules/cpq/services/cpqProductService.js.map +7 -0
  251. package/dist/modules/cpq/services/cpqQuotingService.js +1867 -0
  252. package/dist/modules/cpq/services/cpqQuotingService.js.map +7 -0
  253. package/dist/modules/cpq/services/cpqValidationService.js +234 -0
  254. package/dist/modules/cpq/services/cpqValidationService.js.map +7 -0
  255. package/dist/modules/cpq/services/cpqWizardService.js +137 -0
  256. package/dist/modules/cpq/services/cpqWizardService.js.map +7 -0
  257. package/dist/modules/cpq/services/types.js +114 -0
  258. package/dist/modules/cpq/services/types.js.map +7 -0
  259. package/dist/modules/cpq/setup.js +49 -0
  260. package/dist/modules/cpq/setup.js.map +7 -0
  261. package/dist/modules/cpq/subscribers/subscription-amended-log.js +18 -0
  262. package/dist/modules/cpq/subscribers/subscription-amended-log.js.map +7 -0
  263. package/dist/modules/cpq/subscribers/subscription-cancelled-log.js +19 -0
  264. package/dist/modules/cpq/subscribers/subscription-cancelled-log.js.map +7 -0
  265. package/dist/modules/cpq/subscribers/subscription-merged-log.js +20 -0
  266. package/dist/modules/cpq/subscribers/subscription-merged-log.js.map +7 -0
  267. package/dist/modules/cpq/subscribers/subscription-renewed-log.js +18 -0
  268. package/dist/modules/cpq/subscribers/subscription-renewed-log.js.map +7 -0
  269. package/dist/modules/cpq/subscribers/subscription-superseded-log.js +18 -0
  270. package/dist/modules/cpq/subscribers/subscription-superseded-log.js.map +7 -0
  271. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +192 -0
  272. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +7 -0
  273. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js +17 -0
  274. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js.map +7 -0
  275. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +79 -0
  276. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +7 -0
  277. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js +17 -0
  278. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js.map +7 -0
  279. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +181 -0
  280. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +7 -0
  281. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js +17 -0
  282. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js.map +7 -0
  283. package/dist/modules/cpq/widgets/injection-table.js +44 -0
  284. package/dist/modules/cpq/widgets/injection-table.js.map +7 -0
  285. package/dist/modules/cpq/workflows/WizardRunner.js +113 -0
  286. package/dist/modules/cpq/workflows/WizardRunner.js.map +7 -0
  287. package/dist/modules/cpq/workflows/WizardTrigger.js +116 -0
  288. package/dist/modules/cpq/workflows/WizardTrigger.js.map +7 -0
  289. package/dist/modules/cpq/workflows/engine.js +188 -0
  290. package/dist/modules/cpq/workflows/engine.js.map +7 -0
  291. package/dist/modules/cpq/workflows/registry.js +16 -0
  292. package/dist/modules/cpq/workflows/registry.js.map +7 -0
  293. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +119 -0
  294. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +7 -0
  295. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +24 -0
  296. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +7 -0
  297. package/dist/modules/cpq/workflows/steps/ItemListStep.js +115 -0
  298. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +7 -0
  299. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +327 -0
  300. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +7 -0
  301. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +231 -0
  302. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +7 -0
  303. package/dist/modules/cpq/workflows/steps/ReviewStep.js +152 -0
  304. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +7 -0
  305. package/dist/modules/cpq/workflows/steps/index.js +49 -0
  306. package/dist/modules/cpq/workflows/steps/index.js.map +7 -0
  307. package/dist/modules/cpq/workflows/types.js +1 -0
  308. package/dist/modules/cpq/workflows/types.js.map +7 -0
  309. package/package.json +150 -0
  310. package/src/modules/cpq/__integration__/TC-CPQ-001.spec.ts +177 -0
  311. package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +123 -0
  312. package/src/modules/cpq/__integration__/TC-CPQ-003.spec.ts +115 -0
  313. package/src/modules/cpq/__integration__/TC-CPQ-004.spec.ts +184 -0
  314. package/src/modules/cpq/__integration__/TC-CPQ-005.spec.ts +43 -0
  315. package/src/modules/cpq/__integration__/TC-CPQ-006.spec.ts +87 -0
  316. package/src/modules/cpq/__integration__/TC-CPQ-007.spec.ts +79 -0
  317. package/src/modules/cpq/__integration__/TC-CPQ-008.spec.ts +55 -0
  318. package/src/modules/cpq/__integration__/TC-CPQ-009.spec.ts +45 -0
  319. package/src/modules/cpq/__integration__/TC-CPQ-010.spec.ts +54 -0
  320. package/src/modules/cpq/__integration__/TC-CPQ-011.spec.ts +24 -0
  321. package/src/modules/cpq/__integration__/TC-CPQ-012.spec.ts +62 -0
  322. package/src/modules/cpq/__integration__/TC-CPQ-ARC-001.spec.ts +52 -0
  323. package/src/modules/cpq/__integration__/TC-CPQ-ARC-002.spec.ts +65 -0
  324. package/src/modules/cpq/__integration__/TC-CPQ-ARC-003.spec.ts +107 -0
  325. package/src/modules/cpq/__integration__/TC-CPQ-ARC-004.spec.ts +74 -0
  326. package/src/modules/cpq/__integration__/TC-CPQ-ARC-005.spec.ts +86 -0
  327. package/src/modules/cpq/__integration__/TC-CPQ-ARC-006.spec.ts +79 -0
  328. package/src/modules/cpq/__integration__/TC-CPQ-ARC-007.spec.ts +63 -0
  329. package/src/modules/cpq/__integration__/TC-CPQ-ARC-008.spec.ts +62 -0
  330. package/src/modules/cpq/__integration__/TC-CPQ-ARC-009.spec.ts +62 -0
  331. package/src/modules/cpq/__integration__/TC-CPQ-ARC-010.spec.ts +59 -0
  332. package/src/modules/cpq/__integration__/TC-CPQ-ARC-011.spec.ts +128 -0
  333. package/src/modules/cpq/__integration__/_helpers/cpqArcFixtures.ts +208 -0
  334. package/src/modules/cpq/__tests__/package-isolation.test.ts +232 -0
  335. package/src/modules/cpq/acl.ts +26 -0
  336. package/src/modules/cpq/api/_helpers/__tests__/handleArcError.test.ts +117 -0
  337. package/src/modules/cpq/api/_helpers/handleArcError.ts +81 -0
  338. package/src/modules/cpq/api/attribute-options/route.ts +55 -0
  339. package/src/modules/cpq/api/bundle-slots/route.ts +112 -0
  340. package/src/modules/cpq/api/bundle-tree/route.ts +31 -0
  341. package/src/modules/cpq/api/context.ts +39 -0
  342. package/src/modules/cpq/api/inventory/assets/route.ts +123 -0
  343. package/src/modules/cpq/api/inventory/assets/status/route.ts +34 -0
  344. package/src/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.ts +30 -0
  345. package/src/modules/cpq/api/inventory/customer/[customerId]/route.ts +31 -0
  346. package/src/modules/cpq/api/inventory/subscription-items/route.ts +107 -0
  347. package/src/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.ts +64 -0
  348. package/src/modules/cpq/api/inventory/subscriptions/expiring/route.ts +66 -0
  349. package/src/modules/cpq/api/inventory/subscriptions/route.ts +126 -0
  350. package/src/modules/cpq/api/inventory/subscriptions/status/route.ts +34 -0
  351. package/src/modules/cpq/api/offering-components/route.ts +103 -0
  352. package/src/modules/cpq/api/orders/[orderId]/activate/route.ts +48 -0
  353. package/src/modules/cpq/api/orders/[orderId]/route.ts +28 -0
  354. package/src/modules/cpq/api/orders/[orderId]/status/route.ts +38 -0
  355. package/src/modules/cpq/api/orders/route.ts +77 -0
  356. package/src/modules/cpq/api/price-rules/route.ts +210 -0
  357. package/src/modules/cpq/api/pricing-table-entries/route.ts +159 -0
  358. package/src/modules/cpq/api/pricing-tables/route.ts +202 -0
  359. package/src/modules/cpq/api/product-attributes/route.ts +175 -0
  360. package/src/modules/cpq/api/product-charges/route.ts +181 -0
  361. package/src/modules/cpq/api/product-offerings/route.ts +329 -0
  362. package/src/modules/cpq/api/product-relationships/route.ts +156 -0
  363. package/src/modules/cpq/api/product-specifications/route.ts +325 -0
  364. package/src/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.ts +77 -0
  365. package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +28 -0
  366. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +55 -0
  367. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +26 -0
  368. package/src/modules/cpq/api/quotes/[quoteId]/merge-meta/route.ts +78 -0
  369. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +34 -0
  370. package/src/modules/cpq/api/quotes/[quoteId]/route.ts +109 -0
  371. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +26 -0
  372. package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.ts +91 -0
  373. package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.ts +107 -0
  374. package/src/modules/cpq/api/quotes/attributes/route.ts +61 -0
  375. package/src/modules/cpq/api/quotes/from-subscription/route.ts +85 -0
  376. package/src/modules/cpq/api/quotes/price/route.ts +81 -0
  377. package/src/modules/cpq/api/quotes/route.ts +114 -0
  378. package/src/modules/cpq/api/quotes/validate-relationships/route.ts +100 -0
  379. package/src/modules/cpq/api/resolveQuotingService.ts +11 -0
  380. package/src/modules/cpq/api/wizards/resolve/route.ts +39 -0
  381. package/src/modules/cpq/api/wizards/route.ts +127 -0
  382. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +166 -0
  383. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +251 -0
  384. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +9 -0
  385. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +440 -0
  386. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +16 -0
  387. package/src/modules/cpq/backend/cpq/inventory/page.tsx +49 -0
  388. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +32 -0
  389. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +9 -0
  390. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +1085 -0
  391. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +1165 -0
  392. package/src/modules/cpq/backend/cpq/offerings/page.meta.ts +16 -0
  393. package/src/modules/cpq/backend/cpq/offerings/page.tsx +468 -0
  394. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +921 -0
  395. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +16 -0
  396. package/src/modules/cpq/backend/cpq/orders/page.tsx +182 -0
  397. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +310 -0
  398. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +9 -0
  399. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +472 -0
  400. package/src/modules/cpq/backend/cpq/price-rules/page.meta.ts +16 -0
  401. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +253 -0
  402. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +431 -0
  403. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +122 -0
  404. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +588 -0
  405. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.ts +90 -0
  406. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +338 -0
  407. package/src/modules/cpq/backend/cpq/pricing/page.meta.ts +16 -0
  408. package/src/modules/cpq/backend/cpq/pricing/page.tsx +111 -0
  409. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +737 -0
  410. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +1721 -0
  411. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +16 -0
  412. package/src/modules/cpq/backend/cpq/quotes/page.tsx +370 -0
  413. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +300 -0
  414. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +1722 -0
  415. package/src/modules/cpq/backend/cpq/specifications/page.meta.ts +16 -0
  416. package/src/modules/cpq/backend/cpq/specifications/page.tsx +191 -0
  417. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +319 -0
  418. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +234 -0
  419. package/src/modules/cpq/backend/cpq/wizards/page.meta.ts +16 -0
  420. package/src/modules/cpq/backend/cpq/wizards/page.tsx +141 -0
  421. package/src/modules/cpq/ce.ts +150 -0
  422. package/src/modules/cpq/cli.ts +10 -0
  423. package/src/modules/cpq/components/CpqListView.tsx +431 -0
  424. package/src/modules/cpq/components/NumberInput.tsx +166 -0
  425. package/src/modules/cpq/components/StatusPath.tsx +116 -0
  426. package/src/modules/cpq/components/statusMaps.ts +148 -0
  427. package/src/modules/cpq/components/useCpqRowActions.tsx +183 -0
  428. package/src/modules/cpq/data/__tests__/validators.arc.test.ts +271 -0
  429. package/src/modules/cpq/data/__tests__/validators.test.ts +782 -0
  430. package/src/modules/cpq/data/entities.ts +1467 -0
  431. package/src/modules/cpq/data/validators.ts +663 -0
  432. package/src/modules/cpq/di.ts +70 -0
  433. package/src/modules/cpq/events.ts +62 -0
  434. package/src/modules/cpq/index.ts +15 -0
  435. package/src/modules/cpq/lib/seeds/__tests__/api.test.ts +86 -0
  436. package/src/modules/cpq/lib/seeds/__tests__/tenant-provisioning.test.ts +349 -0
  437. package/src/modules/cpq/lib/seeds/api.ts +119 -0
  438. package/src/modules/cpq/lib/seeds/tenant-provisioning.ts +239 -0
  439. package/src/modules/cpq/lib/seeds.ts +18 -0
  440. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +7473 -0
  441. package/src/modules/cpq/migrations/Migration20260220220000_cpq.ts +51 -0
  442. package/src/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.ts +70 -0
  443. package/src/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.ts +15 -0
  444. package/src/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.ts +73 -0
  445. package/src/modules/cpq/migrations/Migration20260402000000_cpq_workflows.ts +59 -0
  446. package/src/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.ts +30 -0
  447. package/src/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.ts +36 -0
  448. package/src/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.ts +33 -0
  449. package/src/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.ts +22 -0
  450. package/src/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.ts +56 -0
  451. package/src/modules/cpq/migrations/Migration20260410100000_cpq_inventory.ts +118 -0
  452. package/src/modules/cpq/migrations/Migration20260411000000_cpq_orders.ts +62 -0
  453. package/src/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.ts +33 -0
  454. package/src/modules/cpq/migrations/Migration20260504064621_cpq_arc.ts +68 -0
  455. package/src/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.ts +30 -0
  456. package/src/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.ts +27 -0
  457. package/src/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.ts +82 -0
  458. package/src/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.ts +52 -0
  459. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +59 -0
  460. package/src/modules/cpq/services/__tests__/cpqBundleService.test.ts +661 -0
  461. package/src/modules/cpq/services/__tests__/cpqInventoryService.arc.test.ts +357 -0
  462. package/src/modules/cpq/services/__tests__/cpqInventoryService.test.ts +718 -0
  463. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +603 -0
  464. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +1012 -0
  465. package/src/modules/cpq/services/__tests__/cpqProductService.test.ts +603 -0
  466. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +143 -0
  467. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +658 -0
  468. package/src/modules/cpq/services/__tests__/cpqValidationService.test.ts +811 -0
  469. package/src/modules/cpq/services/__tests__/cpqWizardService.test.ts +389 -0
  470. package/src/modules/cpq/services/__tests__/types.test.ts +359 -0
  471. package/src/modules/cpq/services/cpqBundleService.ts +671 -0
  472. package/src/modules/cpq/services/cpqInventoryService.ts +1929 -0
  473. package/src/modules/cpq/services/cpqOrderService.ts +1230 -0
  474. package/src/modules/cpq/services/cpqPricingService.ts +412 -0
  475. package/src/modules/cpq/services/cpqProductService.ts +311 -0
  476. package/src/modules/cpq/services/cpqQuotingService.ts +2638 -0
  477. package/src/modules/cpq/services/cpqValidationService.ts +323 -0
  478. package/src/modules/cpq/services/cpqWizardService.ts +204 -0
  479. package/src/modules/cpq/services/types.ts +303 -0
  480. package/src/modules/cpq/setup.ts +63 -0
  481. package/src/modules/cpq/subscribers/subscription-amended-log.ts +20 -0
  482. package/src/modules/cpq/subscribers/subscription-cancelled-log.ts +19 -0
  483. package/src/modules/cpq/subscribers/subscription-merged-log.ts +22 -0
  484. package/src/modules/cpq/subscribers/subscription-renewed-log.ts +18 -0
  485. package/src/modules/cpq/subscribers/subscription-superseded-log.ts +19 -0
  486. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +339 -0
  487. package/src/modules/cpq/widgets/injection/customer-inventory/widget.ts +16 -0
  488. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +96 -0
  489. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.ts +16 -0
  490. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +265 -0
  491. package/src/modules/cpq/widgets/injection/quote-configurator/widget.ts +16 -0
  492. package/src/modules/cpq/widgets/injection-table.ts +42 -0
  493. package/src/modules/cpq/workflows/WizardRunner.tsx +158 -0
  494. package/src/modules/cpq/workflows/WizardTrigger.tsx +142 -0
  495. package/src/modules/cpq/workflows/engine.tsx +278 -0
  496. package/src/modules/cpq/workflows/registry.ts +15 -0
  497. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +136 -0
  498. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +34 -0
  499. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +146 -0
  500. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +438 -0
  501. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +287 -0
  502. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +219 -0
  503. package/src/modules/cpq/workflows/steps/index.ts +51 -0
  504. package/src/modules/cpq/workflows/types.ts +102 -0
@@ -0,0 +1,718 @@
1
+ import { DefaultCpqInventoryService } from '../cpqInventoryService'
2
+ import { createMockEm, TEST_SCOPE as SCOPE } from './_helpers/mockEm'
3
+
4
+ function makeService() {
5
+ const em = createMockEm()
6
+ const service = new DefaultCpqInventoryService(em as unknown as never)
7
+ return { em, service }
8
+ }
9
+
10
+ describe('DefaultCpqInventoryService.transitionSubscriptionStatus', () => {
11
+ it('rejects an illegal transition (pending → suspended)', async () => {
12
+ const { em, service } = makeService()
13
+ em.findOne.mockResolvedValueOnce({ id: 's1', status: 'pending' })
14
+
15
+ await expect(service.transitionSubscriptionStatus('s1', 'suspended', SCOPE)).rejects.toMatchObject({
16
+ status: 409,
17
+ error: "Cannot transition from 'pending' to 'suspended'",
18
+ })
19
+ })
20
+
21
+ it('rejects transitions from terminal states', async () => {
22
+ const { em, service } = makeService()
23
+ em.findOne.mockResolvedValueOnce({ id: 's1', status: 'terminated' })
24
+
25
+ await expect(service.transitionSubscriptionStatus('s1', 'active', SCOPE)).rejects.toMatchObject({
26
+ status: 409,
27
+ })
28
+ })
29
+
30
+ it('throws 404 when subscription does not exist', async () => {
31
+ const { em, service } = makeService()
32
+ em.findOne.mockResolvedValueOnce(null)
33
+
34
+ await expect(service.transitionSubscriptionStatus('missing', 'active', SCOPE)).rejects.toMatchObject({
35
+ status: 404,
36
+ error: 'Subscription not found',
37
+ })
38
+ })
39
+
40
+ it('pending → active sets activatedAt and propagates status to items', async () => {
41
+ const { em, service } = makeService()
42
+ const sub = { id: 's1', status: 'pending', activatedAt: null, suspendedAt: null, terminatedAt: null }
43
+ em.findOne.mockResolvedValueOnce(sub)
44
+ const item1 = { id: 'i1', status: 'pending' }
45
+ const item2 = { id: 'i2', status: 'pending' }
46
+ em.find.mockResolvedValueOnce([item1, item2]) // items
47
+ em.find.mockResolvedValueOnce([]) // assets
48
+
49
+ await service.transitionSubscriptionStatus('s1', 'active', SCOPE)
50
+
51
+ expect(sub.status).toBe('active')
52
+ expect(sub.activatedAt).toBeInstanceOf(Date)
53
+ expect(item1.status).toBe('active')
54
+ expect(item2.status).toBe('active')
55
+ })
56
+
57
+ it('active → suspended sets suspendedAt and does NOT cascade to assets', async () => {
58
+ const { em, service } = makeService()
59
+ const sub = { id: 's1', status: 'active', activatedAt: new Date('2026-01-01'), suspendedAt: null, terminatedAt: null }
60
+ em.findOne.mockResolvedValueOnce(sub)
61
+ const item = { id: 'i1', status: 'active' }
62
+ em.find.mockResolvedValueOnce([item]) // items only — no assets fetched for "suspended"
63
+
64
+ await service.transitionSubscriptionStatus('s1', 'suspended', SCOPE)
65
+
66
+ expect(sub.status).toBe('suspended')
67
+ expect(sub.suspendedAt).toBeInstanceOf(Date)
68
+ expect(item.status).toBe('suspended')
69
+ // No assets query for suspended target
70
+ expect(em.find).toHaveBeenCalledTimes(1)
71
+ })
72
+
73
+ it('suspended → active clears suspendedAt and re-activates non-returned assets', async () => {
74
+ const { em, service } = makeService()
75
+ const sub = {
76
+ id: 's1',
77
+ status: 'suspended',
78
+ activatedAt: new Date('2026-01-01'),
79
+ suspendedAt: new Date('2026-02-01'),
80
+ terminatedAt: null,
81
+ }
82
+ em.findOne.mockResolvedValueOnce(sub)
83
+ em.find.mockResolvedValueOnce([{ id: 'i1', status: 'suspended' }])
84
+ const asset = { id: 'a1', status: 'delivered' }
85
+ em.find.mockResolvedValueOnce([asset])
86
+
87
+ await service.transitionSubscriptionStatus('s1', 'active', SCOPE)
88
+
89
+ expect(sub.status).toBe('active')
90
+ expect(sub.suspendedAt).toBeNull()
91
+ expect(asset.status).toBe('active')
92
+
93
+ // Verify the asset find excluded only "returned" status
94
+ const [, assetWhere] = em.find.mock.calls[1]
95
+ expect(assetWhere).toMatchObject({ status: { $nin: ['returned'] } })
96
+ })
97
+
98
+ it('active → terminated sets terminatedAt and cancels all non-returned/cancelled assets', async () => {
99
+ const { em, service } = makeService()
100
+ const sub = {
101
+ id: 's1',
102
+ status: 'active',
103
+ activatedAt: new Date(),
104
+ suspendedAt: null,
105
+ terminatedAt: null,
106
+ }
107
+ em.findOne.mockResolvedValueOnce(sub)
108
+ em.find.mockResolvedValueOnce([{ id: 'i1', status: 'active' }])
109
+ const asset = { id: 'a1', status: 'active' }
110
+ em.find.mockResolvedValueOnce([asset])
111
+
112
+ await service.transitionSubscriptionStatus('s1', 'terminated', SCOPE)
113
+
114
+ expect(sub.status).toBe('terminated')
115
+ expect(sub.terminatedAt).toBeInstanceOf(Date)
116
+ expect(asset.status).toBe('cancelled')
117
+
118
+ const [, assetWhere] = em.find.mock.calls[1]
119
+ expect(assetWhere).toMatchObject({ status: { $nin: ['returned', 'cancelled'] } })
120
+ })
121
+
122
+ it('skips already-terminated/expired items in the cascade query', async () => {
123
+ const { em, service } = makeService()
124
+ em.findOne.mockResolvedValueOnce({ id: 's1', status: 'pending', activatedAt: null })
125
+ em.find.mockResolvedValueOnce([])
126
+ em.find.mockResolvedValueOnce([])
127
+
128
+ await service.transitionSubscriptionStatus('s1', 'active', SCOPE)
129
+
130
+ const [, itemWhere] = em.find.mock.calls[0]
131
+ expect(itemWhere).toMatchObject({
132
+ subscriptionId: 's1',
133
+ // XD-250 added 'superseded' to the terminal item statuses excluded from cascade.
134
+ status: { $nin: ['terminated', 'expired', 'superseded'] },
135
+ })
136
+ })
137
+ })
138
+
139
+ describe('DefaultCpqInventoryService.transitionAssetStatus', () => {
140
+ it('rejects an illegal transition (active → cancelled)', async () => {
141
+ const { em, service } = makeService()
142
+ em.findOne.mockResolvedValueOnce({ id: 'a1', status: 'active' })
143
+
144
+ await expect(service.transitionAssetStatus('a1', 'cancelled', SCOPE)).rejects.toMatchObject({
145
+ status: 409,
146
+ })
147
+ })
148
+
149
+ it('allows pending → delivered', async () => {
150
+ const { em, service } = makeService()
151
+ const asset = { id: 'a1', status: 'pending' }
152
+ em.findOne.mockResolvedValueOnce(asset)
153
+
154
+ const result = await service.transitionAssetStatus('a1', 'delivered', SCOPE)
155
+
156
+ expect(asset.status).toBe('delivered')
157
+ expect(result).toBe(asset)
158
+ expect(em.flush).toHaveBeenCalled()
159
+ })
160
+
161
+ it('allows delivered → returned', async () => {
162
+ const { em, service } = makeService()
163
+ const asset = { id: 'a1', status: 'delivered' }
164
+ em.findOne.mockResolvedValueOnce(asset)
165
+
166
+ await service.transitionAssetStatus('a1', 'returned', SCOPE)
167
+ expect(asset.status).toBe('returned')
168
+ })
169
+
170
+ it('rejects transitions from terminal returned/cancelled', async () => {
171
+ const { em, service } = makeService()
172
+ em.findOne.mockResolvedValueOnce({ id: 'a1', status: 'returned' })
173
+
174
+ await expect(service.transitionAssetStatus('a1', 'active', SCOPE)).rejects.toMatchObject({
175
+ status: 409,
176
+ })
177
+ })
178
+
179
+ it('throws 404 when asset does not exist', async () => {
180
+ const { em, service } = makeService()
181
+ em.findOne.mockResolvedValueOnce(null)
182
+
183
+ await expect(service.transitionAssetStatus('missing', 'active', SCOPE)).rejects.toMatchObject({
184
+ status: 404,
185
+ error: 'Asset not found',
186
+ })
187
+ })
188
+ })
189
+
190
+ describe('DefaultCpqInventoryService.createSubscription', () => {
191
+ it('aggregates MRC and NRC sums across items', async () => {
192
+ const { em, service } = makeService()
193
+
194
+ const result = await service.createSubscription(
195
+ {
196
+ customerId: 'cust',
197
+ name: 'My Sub',
198
+ currencyCode: 'USD',
199
+ items: [
200
+ { name: 'A', mrcAmount: 10, nrcAmount: 5 },
201
+ { name: 'B', mrcAmount: 20, nrcAmount: 0 },
202
+ { name: 'C', mrcAmount: 0, nrcAmount: 15 },
203
+ ],
204
+ },
205
+ SCOPE,
206
+ )
207
+
208
+ expect((result as { mrcAmount: string }).mrcAmount).toBe('30')
209
+ expect((result as { nrcAmount: string }).nrcAmount).toBe('20')
210
+ expect(em.flush).toHaveBeenCalled()
211
+ })
212
+
213
+ it('uses the provided code or generates SUB-YYYY-NNNNN', async () => {
214
+ const { em, service } = makeService()
215
+ em.count.mockResolvedValueOnce(7)
216
+
217
+ const sub = await service.createSubscription(
218
+ {
219
+ customerId: 'cust',
220
+ name: 'NoCode',
221
+ currencyCode: 'USD',
222
+ items: [{ name: 'item' }],
223
+ },
224
+ SCOPE,
225
+ )
226
+
227
+ const expectedSeq = String(8).padStart(5, '0')
228
+ const year = new Date().getFullYear()
229
+ expect((sub as { code: string }).code).toBe(`SUB-${year}-${expectedSeq}`)
230
+ })
231
+
232
+ it('honors an explicit code', async () => {
233
+ const { service } = makeService()
234
+ const sub = await service.createSubscription(
235
+ {
236
+ customerId: 'cust',
237
+ code: 'CUSTOM-001',
238
+ name: 'WithCode',
239
+ currencyCode: 'USD',
240
+ items: [{ name: 'item' }],
241
+ },
242
+ SCOPE,
243
+ )
244
+ expect((sub as { code: string }).code).toBe('CUSTOM-001')
245
+ })
246
+
247
+ it('defaults billingCycle to monthly and autoRenew to true', async () => {
248
+ const { service } = makeService()
249
+ const sub = await service.createSubscription(
250
+ {
251
+ customerId: 'cust',
252
+ name: 'X',
253
+ currencyCode: 'USD',
254
+ items: [{ name: 'item' }],
255
+ },
256
+ SCOPE,
257
+ )
258
+ expect((sub as { billingCycle: string }).billingCycle).toBe('monthly')
259
+ expect((sub as { autoRenew: boolean }).autoRenew).toBe(true)
260
+ })
261
+
262
+ it('initializes new subscription status to "pending"', async () => {
263
+ const { service } = makeService()
264
+ const sub = await service.createSubscription(
265
+ {
266
+ customerId: 'cust',
267
+ name: 'X',
268
+ currencyCode: 'USD',
269
+ items: [{ name: 'item' }],
270
+ },
271
+ SCOPE,
272
+ )
273
+ expect((sub as { status: string }).status).toBe('pending')
274
+ })
275
+ })
276
+
277
+ describe('DefaultCpqInventoryService.addSubscriptionItem', () => {
278
+ it('throws 404 when subscription does not exist', async () => {
279
+ const { em, service } = makeService()
280
+ em.findOne.mockResolvedValueOnce(null)
281
+
282
+ await expect(
283
+ service.addSubscriptionItem('missing', { name: 'x' }, SCOPE),
284
+ ).rejects.toMatchObject({ status: 404, error: 'Subscription not found' })
285
+ })
286
+
287
+ it('throws 404 when parentItemId does not exist', async () => {
288
+ const { em, service } = makeService()
289
+ em.findOne.mockResolvedValueOnce({ id: 's1', currencyCode: 'USD', customerId: 'c1' }) // sub
290
+ em.findOne.mockResolvedValueOnce(null) // parent
291
+
292
+ await expect(
293
+ service.addSubscriptionItem('s1', { name: 'x', parentItemId: 'missing-parent' }, SCOPE),
294
+ ).rejects.toMatchObject({ status: 404, error: 'Parent item not found' })
295
+ })
296
+
297
+ it('throws 409 when parent belongs to a different subscription', async () => {
298
+ const { em, service } = makeService()
299
+ em.findOne.mockResolvedValueOnce({ id: 's1', currencyCode: 'USD', customerId: 'c1' })
300
+ em.findOne.mockResolvedValueOnce({ id: 'parent-1', subscriptionId: 'other-sub' })
301
+
302
+ await expect(
303
+ service.addSubscriptionItem('s1', { name: 'x', parentItemId: 'parent-1' }, SCOPE),
304
+ ).rejects.toMatchObject({
305
+ status: 409,
306
+ error: 'Parent item does not belong to this subscription',
307
+ })
308
+ })
309
+
310
+ it('inherits subscription currency and uses pending status, then recomputes aggregates', async () => {
311
+ const { em, service } = makeService()
312
+ em.findOne.mockResolvedValueOnce({
313
+ id: 's1',
314
+ customerId: 'c1',
315
+ currencyCode: 'EUR',
316
+ })
317
+ // recomputeSubscriptionAggregates flow:
318
+ em.find.mockResolvedValueOnce([{ mrcAmount: '5', nrcAmount: '3' }])
319
+ em.findOne.mockResolvedValueOnce({ id: 's1', mrcAmount: '0', nrcAmount: '0' })
320
+
321
+ const item = await service.addSubscriptionItem(
322
+ 's1',
323
+ { name: 'New Item', mrcAmount: 5, nrcAmount: 3 },
324
+ SCOPE,
325
+ )
326
+
327
+ expect((item as { currencyCode: string }).currencyCode).toBe('EUR')
328
+ expect((item as { status: string }).status).toBe('pending')
329
+ expect(em.flush).toHaveBeenCalledTimes(2) // create + recompute
330
+ })
331
+ })
332
+
333
+ describe('DefaultCpqInventoryService.listSubscriptions', () => {
334
+ it('clamps pageSize to [1, 100]', async () => {
335
+ const { em, service } = makeService()
336
+ em.findAndCount.mockResolvedValueOnce([[], 0])
337
+ await service.listSubscriptions({ pageSize: 500 }, SCOPE)
338
+ const [, , options] = em.findAndCount.mock.calls[0]
339
+ expect(options).toMatchObject({ limit: 100 })
340
+ })
341
+
342
+ it('translates a status array into a $in filter', async () => {
343
+ const { em, service } = makeService()
344
+ em.findAndCount.mockResolvedValueOnce([[], 0])
345
+ await service.listSubscriptions({ status: ['active', 'suspended'] }, SCOPE)
346
+ const [, where] = em.findAndCount.mock.calls[0]
347
+ expect(where).toMatchObject({ status: { $in: ['active', 'suspended'] } })
348
+ })
349
+
350
+ it('passes a single status as a scalar', async () => {
351
+ const { em, service } = makeService()
352
+ em.findAndCount.mockResolvedValueOnce([[], 0])
353
+ await service.listSubscriptions({ status: 'active' }, SCOPE)
354
+ const [, where] = em.findAndCount.mock.calls[0]
355
+ expect(where).toMatchObject({ status: 'active' })
356
+ })
357
+
358
+ it('returns empty results when no subscriptions reference the given productId', async () => {
359
+ const { em, service } = makeService()
360
+ em.find.mockResolvedValueOnce([]) // no items match productId
361
+
362
+ const result = await service.listSubscriptions({ productId: 'p1' }, SCOPE)
363
+ expect(result.items).toEqual([])
364
+ expect(result.total).toBe(0)
365
+ expect(em.findAndCount).not.toHaveBeenCalled()
366
+ })
367
+
368
+ it('narrows the subscription query to ids referencing the given productId', async () => {
369
+ const { em, service } = makeService()
370
+ em.find.mockResolvedValueOnce([
371
+ { subscriptionId: 's1' },
372
+ { subscriptionId: 's2' },
373
+ { subscriptionId: 's1' }, // duplicate filtered out
374
+ ])
375
+ em.findAndCount.mockResolvedValueOnce([
376
+ [{ id: 's1' }, { id: 's2' }],
377
+ 2,
378
+ ])
379
+
380
+ await service.listSubscriptions({ productId: 'p1' }, SCOPE)
381
+
382
+ const [, where] = em.findAndCount.mock.calls[0]
383
+ expect(where.id).toEqual({ $in: ['s1', 's2'] })
384
+ })
385
+ })
386
+
387
+ describe('DefaultCpqInventoryService.getSubscription', () => {
388
+ it('throws 404 when subscription is missing', async () => {
389
+ const { em, service } = makeService()
390
+ em.findOne.mockResolvedValueOnce(null)
391
+
392
+ await expect(service.getSubscription('missing', SCOPE)).rejects.toMatchObject({
393
+ status: 404,
394
+ })
395
+ })
396
+
397
+ it('builds an items tree from the flat list (parents → children)', async () => {
398
+ const { em, service } = makeService()
399
+ em.findOne.mockResolvedValueOnce({
400
+ id: 's1',
401
+ customerId: 'c',
402
+ currencyCode: 'USD',
403
+ })
404
+ em.find.mockResolvedValueOnce([
405
+ {
406
+ id: 'parent',
407
+ subscriptionId: 's1',
408
+ parentItemId: null,
409
+ name: 'Parent',
410
+ status: 'active',
411
+ configuration: {},
412
+ charges: null,
413
+ mrcAmount: '0',
414
+ nrcAmount: '0',
415
+ currencyCode: 'USD',
416
+ quantity: 1,
417
+ capacityTotal: null,
418
+ capacityUsed: null,
419
+ capacityUnit: null,
420
+ metadata: null,
421
+ sortOrder: 1,
422
+ },
423
+ {
424
+ id: 'child-1',
425
+ subscriptionId: 's1',
426
+ parentItemId: 'parent',
427
+ name: 'Child 1',
428
+ status: 'active',
429
+ configuration: {},
430
+ charges: null,
431
+ mrcAmount: '0',
432
+ nrcAmount: '0',
433
+ currencyCode: 'USD',
434
+ quantity: 1,
435
+ capacityTotal: null,
436
+ capacityUsed: null,
437
+ capacityUnit: null,
438
+ metadata: null,
439
+ sortOrder: 2,
440
+ },
441
+ {
442
+ id: 'child-2',
443
+ subscriptionId: 's1',
444
+ parentItemId: 'parent',
445
+ name: 'Child 2',
446
+ status: 'active',
447
+ configuration: {},
448
+ charges: null,
449
+ mrcAmount: '0',
450
+ nrcAmount: '0',
451
+ currencyCode: 'USD',
452
+ quantity: 1,
453
+ capacityTotal: null,
454
+ capacityUsed: null,
455
+ capacityUnit: null,
456
+ metadata: null,
457
+ sortOrder: 3,
458
+ },
459
+ ])
460
+
461
+ const result = await service.getSubscription('s1', SCOPE)
462
+ const items = (result as { items: Array<{ id: string; children: Array<{ id: string }> }> }).items
463
+
464
+ expect(items).toHaveLength(1)
465
+ expect(items[0].id).toBe('parent')
466
+ expect(items[0].children).toHaveLength(2)
467
+ expect(items[0].children.map((c) => c.id)).toEqual(['child-1', 'child-2'])
468
+ })
469
+
470
+ it('treats orphans (parentItemId pointing nowhere) as roots', async () => {
471
+ const { em, service } = makeService()
472
+ em.findOne.mockResolvedValueOnce({ id: 's1', customerId: 'c', currencyCode: 'USD' })
473
+ em.find.mockResolvedValueOnce([
474
+ {
475
+ id: 'orphan',
476
+ subscriptionId: 's1',
477
+ parentItemId: 'never-existed',
478
+ name: 'Orphan',
479
+ status: 'active',
480
+ configuration: {},
481
+ charges: null,
482
+ mrcAmount: '0',
483
+ nrcAmount: '0',
484
+ currencyCode: 'USD',
485
+ quantity: 1,
486
+ capacityTotal: null,
487
+ capacityUsed: null,
488
+ capacityUnit: null,
489
+ metadata: null,
490
+ sortOrder: 1,
491
+ },
492
+ ])
493
+
494
+ const result = await service.getSubscription('s1', SCOPE)
495
+ const items = (result as { items: Array<{ id: string }> }).items
496
+ expect(items.map((i) => i.id)).toEqual(['orphan'])
497
+ })
498
+ })
499
+
500
+ describe('DefaultCpqInventoryService.getCustomerInventory', () => {
501
+ it('excludes terminal statuses by default', async () => {
502
+ const { em, service } = makeService()
503
+ em.find.mockResolvedValue([])
504
+
505
+ await service.getCustomerInventory('cust-1', {}, SCOPE)
506
+
507
+ const [, subWhere] = em.find.mock.calls[0]
508
+ const [, itemWhere] = em.find.mock.calls[1]
509
+ const [, assetWhere] = em.find.mock.calls[2]
510
+
511
+ expect(subWhere).toMatchObject({ status: { $nin: ['terminated', 'expired'] } })
512
+ expect(itemWhere).toMatchObject({ status: { $nin: ['terminated', 'expired'] } })
513
+ expect(assetWhere).toMatchObject({ status: { $nin: ['returned'] } })
514
+ })
515
+
516
+ it('includes everything when includeTerminated=true', async () => {
517
+ const { em, service } = makeService()
518
+ em.find.mockResolvedValue([])
519
+
520
+ await service.getCustomerInventory('cust-1', { includeTerminated: true }, SCOPE)
521
+
522
+ const [, subWhere] = em.find.mock.calls[0]
523
+ expect(subWhere).not.toHaveProperty('status')
524
+ })
525
+
526
+ it('explicit status filter overrides the default exclusion', async () => {
527
+ const { em, service } = makeService()
528
+ em.find.mockResolvedValue([])
529
+
530
+ await service.getCustomerInventory('cust-1', { status: 'active' }, SCOPE)
531
+
532
+ const [, subWhere] = em.find.mock.calls[0]
533
+ expect(subWhere).toMatchObject({ status: { $in: ['active'] } })
534
+ })
535
+
536
+ it('summary counts active subscriptions, sums their MRC, counts active assets and pending items', async () => {
537
+ const { em, service } = makeService()
538
+ em.find
539
+ .mockResolvedValueOnce([
540
+ { status: 'active', mrcAmount: '100', currencyCode: 'USD' },
541
+ { status: 'active', mrcAmount: '50', currencyCode: 'USD' },
542
+ { status: 'suspended', mrcAmount: '999', currencyCode: 'USD' }, // not counted
543
+ ])
544
+ .mockResolvedValueOnce([
545
+ { status: 'pending' },
546
+ { status: 'pending' },
547
+ { status: 'active' },
548
+ ])
549
+ .mockResolvedValueOnce([
550
+ { status: 'active', currencyCode: 'USD' },
551
+ { status: 'active', currencyCode: 'USD' },
552
+ { status: 'pending', currencyCode: 'USD' },
553
+ ])
554
+
555
+ const result = await service.getCustomerInventory('cust', {}, SCOPE)
556
+
557
+ expect(result.summary).toMatchObject({
558
+ activeSubscriptions: 2,
559
+ totalMrc: 150,
560
+ activeAssets: 2,
561
+ pendingItems: 2,
562
+ currencyCode: 'USD',
563
+ })
564
+ })
565
+ })
566
+
567
+ describe('DefaultCpqInventoryService.getCustomerParentItems', () => {
568
+ it('queries only active items with parentItemId=null for the customer', async () => {
569
+ const { em, service } = makeService()
570
+ em.find.mockResolvedValueOnce([])
571
+
572
+ await service.getCustomerParentItems('cust-1', {}, SCOPE)
573
+
574
+ const [, where] = em.find.mock.calls[0]
575
+ expect(where).toMatchObject({
576
+ customerId: 'cust-1',
577
+ parentItemId: null,
578
+ status: 'active',
579
+ })
580
+ })
581
+
582
+ it('applies productId and offeringId filters', async () => {
583
+ const { em, service } = makeService()
584
+ em.find.mockResolvedValueOnce([])
585
+
586
+ await service.getCustomerParentItems('cust', { productId: 'p', offeringId: 'o' }, SCOPE)
587
+
588
+ const [, where] = em.find.mock.calls[0]
589
+ expect(where).toMatchObject({ productId: 'p', offeringId: 'o' })
590
+ })
591
+ })
592
+
593
+ describe('DefaultCpqInventoryService — soft delete + 404s for missing resources', () => {
594
+ it('deleteSubscription throws 404 when subscription is missing', async () => {
595
+ const { em, service } = makeService()
596
+ em.findOne.mockResolvedValueOnce(null)
597
+
598
+ await expect(service.deleteSubscription('missing', SCOPE)).rejects.toMatchObject({
599
+ status: 404,
600
+ })
601
+ })
602
+
603
+ it('deleteSubscription soft-deletes when found', async () => {
604
+ const { em, service } = makeService()
605
+ const sub = { id: 's1', deletedAt: null }
606
+ em.findOne.mockResolvedValueOnce(sub)
607
+
608
+ const result = await service.deleteSubscription('s1', SCOPE)
609
+
610
+ expect(sub.deletedAt).toBeInstanceOf(Date)
611
+ expect(result).toEqual({ success: true })
612
+ expect(em.flush).toHaveBeenCalled()
613
+ })
614
+
615
+ it('deleteAsset throws 404 when missing', async () => {
616
+ const { em, service } = makeService()
617
+ em.findOne.mockResolvedValueOnce(null)
618
+
619
+ await expect(service.deleteAsset('missing', SCOPE)).rejects.toMatchObject({ status: 404 })
620
+ })
621
+
622
+ it('deleteAsset soft-deletes when found', async () => {
623
+ const { em, service } = makeService()
624
+ const asset = { id: 'a1', deletedAt: null }
625
+ em.findOne.mockResolvedValueOnce(asset)
626
+
627
+ await service.deleteAsset('a1', SCOPE)
628
+ expect(asset.deletedAt).toBeInstanceOf(Date)
629
+ })
630
+ })
631
+
632
+ describe('DefaultCpqInventoryService.createAsset', () => {
633
+ it('throws 404 when subscriptionId is provided but does not exist', async () => {
634
+ const { em, service } = makeService()
635
+ em.findOne.mockResolvedValueOnce(null) // subscription lookup
636
+
637
+ await expect(
638
+ service.createAsset(
639
+ { customerId: 'c', subscriptionId: 'missing', name: 'A', currencyCode: 'USD' },
640
+ SCOPE,
641
+ ),
642
+ ).rejects.toMatchObject({ status: 404, error: 'Subscription not found' })
643
+ })
644
+
645
+ it('throws 404 when subscriptionItemId is provided but does not exist', async () => {
646
+ const { em, service } = makeService()
647
+ em.findOne.mockResolvedValueOnce({ id: 'sub-1' }) // subscription
648
+ em.findOne.mockResolvedValueOnce(null) // subscription item
649
+
650
+ await expect(
651
+ service.createAsset(
652
+ {
653
+ customerId: 'c',
654
+ subscriptionId: 'sub-1',
655
+ subscriptionItemId: 'missing-item',
656
+ name: 'A',
657
+ currencyCode: 'USD',
658
+ },
659
+ SCOPE,
660
+ ),
661
+ ).rejects.toMatchObject({ status: 404, error: 'Subscription item not found' })
662
+ })
663
+
664
+ it('defaults assetType to "one_time" and status to "pending"', async () => {
665
+ const { service } = makeService()
666
+
667
+ const asset = await service.createAsset(
668
+ { customerId: 'c', name: 'A', currencyCode: 'USD' },
669
+ SCOPE,
670
+ )
671
+
672
+ expect((asset as { assetType: string }).assetType).toBe('one_time')
673
+ expect((asset as { status: string }).status).toBe('pending')
674
+ })
675
+
676
+ it('generates AST-YYYY-NNNNN code when not provided', async () => {
677
+ const { em, service } = makeService()
678
+ em.count.mockResolvedValueOnce(2)
679
+
680
+ const asset = await service.createAsset(
681
+ { customerId: 'c', name: 'A', currencyCode: 'USD' },
682
+ SCOPE,
683
+ )
684
+
685
+ const expectedSeq = String(3).padStart(5, '0')
686
+ const year = new Date().getFullYear()
687
+ expect((asset as { code: string }).code).toBe(`AST-${year}-${expectedSeq}`)
688
+ })
689
+ })
690
+
691
+ describe('DefaultCpqInventoryService.listAssets', () => {
692
+ it('applies multiple filters: customerId, status (array), assetType, productId, subscription scope', async () => {
693
+ const { em, service } = makeService()
694
+ em.findAndCount.mockResolvedValueOnce([[], 0])
695
+
696
+ await service.listAssets(
697
+ {
698
+ customerId: 'c',
699
+ status: ['active', 'delivered'],
700
+ assetType: 'license',
701
+ productId: 'p',
702
+ subscriptionId: 's',
703
+ subscriptionItemId: 'si',
704
+ },
705
+ SCOPE,
706
+ )
707
+
708
+ const [, where] = em.findAndCount.mock.calls[0]
709
+ expect(where).toMatchObject({
710
+ customerId: 'c',
711
+ status: { $in: ['active', 'delivered'] },
712
+ assetType: 'license',
713
+ productId: 'p',
714
+ subscriptionId: 's',
715
+ subscriptionItemId: 'si',
716
+ })
717
+ })
718
+ })