@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,1371 @@
1
+ import {
2
+ CpqInventorySubscription,
3
+ CpqInventorySubscriptionItem,
4
+ CpqInventoryAsset,
5
+ CpqProductSpecification,
6
+ CpqSubscriptionChangeLog
7
+ } from "../data/entities.js";
8
+ import {
9
+ INVENTORY_SUBSCRIPTION_TRANSITIONS,
10
+ INVENTORY_ASSET_TRANSITIONS,
11
+ getBaseCurrencyCode
12
+ } from "./types.js";
13
+ class DefaultCpqInventoryService {
14
+ constructor(em) {
15
+ this.em = em;
16
+ }
17
+ // ═══════════════════════════════════════════════════════════════
18
+ // Subscriptions
19
+ // ═══════════════════════════════════════════════════════════════
20
+ async listSubscriptions(filters, scope) {
21
+ const page = Math.max(1, filters.page ?? 1);
22
+ const pageSize = Math.min(100, Math.max(1, filters.pageSize ?? 50));
23
+ const offset = (page - 1) * pageSize;
24
+ const where = {
25
+ organizationId: scope.organizationId,
26
+ tenantId: scope.tenantId,
27
+ deletedAt: null
28
+ };
29
+ if (filters.customerId) where.customerId = filters.customerId;
30
+ if (filters.sourceOrderId) where.sourceOrderId = filters.sourceOrderId;
31
+ if (filters.billingCycle) where.billingCycle = filters.billingCycle;
32
+ if (filters.status) {
33
+ where.status = Array.isArray(filters.status) ? { $in: filters.status } : filters.status;
34
+ }
35
+ const search = filters.search?.trim();
36
+ if (search) {
37
+ where.$or = [
38
+ { code: { $ilike: `%${search}%` } },
39
+ { name: { $ilike: `%${search}%` } }
40
+ ];
41
+ }
42
+ if (filters.productId) {
43
+ const itemIds = await this.em.find(
44
+ CpqInventorySubscriptionItem,
45
+ {
46
+ organizationId: scope.organizationId,
47
+ tenantId: scope.tenantId,
48
+ deletedAt: null,
49
+ productId: filters.productId
50
+ },
51
+ { fields: ["subscriptionId"] }
52
+ );
53
+ const subIds = [...new Set(itemIds.map((i) => i.subscriptionId))];
54
+ if (subIds.length === 0) {
55
+ return { items: [], total: 0, page, pageSize, totalPages: 0 };
56
+ }
57
+ where.id = { $in: subIds };
58
+ }
59
+ if (filters.expiringWithinDays != null && filters.expiringWithinDays > 0) {
60
+ const now = /* @__PURE__ */ new Date();
61
+ const cutoff = new Date(now.getTime() + filters.expiringWithinDays * 24 * 60 * 60 * 1e3);
62
+ where.currentTermEnd = { $gte: now, $lte: cutoff };
63
+ }
64
+ const defaultSortField = filters.expiringWithinDays != null && filters.expiringWithinDays > 0 ? "currentTermEnd" : "createdAt";
65
+ const sortField = filters.sortField ?? defaultSortField;
66
+ const sortDir = filters.sortDir === "asc" ? "asc" : filters.sortField === void 0 && filters.expiringWithinDays != null ? "asc" : "desc";
67
+ const [items, total] = await this.em.findAndCount(
68
+ CpqInventorySubscription,
69
+ where,
70
+ { limit: pageSize, offset, orderBy: { [sortField]: sortDir } }
71
+ );
72
+ return {
73
+ items,
74
+ total,
75
+ page,
76
+ pageSize,
77
+ totalPages: Math.ceil(total / pageSize)
78
+ };
79
+ }
80
+ async getSubscription(subscriptionId, scope) {
81
+ const sub = await this.em.findOne(CpqInventorySubscription, {
82
+ id: subscriptionId,
83
+ organizationId: scope.organizationId,
84
+ tenantId: scope.tenantId,
85
+ deletedAt: null
86
+ });
87
+ if (!sub) throw { status: 404, error: "Subscription not found" };
88
+ const allItems = await this.em.find(CpqInventorySubscriptionItem, {
89
+ subscriptionId: sub.id,
90
+ organizationId: scope.organizationId,
91
+ tenantId: scope.tenantId,
92
+ deletedAt: null
93
+ }, { orderBy: { sortOrder: "asc" } });
94
+ const itemTree = this.buildItemTree(allItems);
95
+ return { ...sub, items: itemTree };
96
+ }
97
+ async createSubscription(input, scope) {
98
+ const code = input.code ?? await this.generateSubscriptionCode(scope);
99
+ const subId = crypto.randomUUID();
100
+ const sub = this.em.create(CpqInventorySubscription, {
101
+ id: subId,
102
+ organizationId: scope.organizationId,
103
+ tenantId: scope.tenantId,
104
+ customerId: input.customerId,
105
+ code,
106
+ name: input.name,
107
+ description: input.description ?? null,
108
+ status: "pending",
109
+ billingCycle: input.billingCycle ?? "monthly",
110
+ currencyCode: input.currencyCode ?? await getBaseCurrencyCode(this.em, scope),
111
+ mrcAmount: "0",
112
+ nrcAmount: "0",
113
+ startDate: input.startDate ? new Date(input.startDate) : null,
114
+ termMonths: input.termMonths ?? null,
115
+ autoRenew: input.autoRenew ?? true,
116
+ sourceQuoteId: input.sourceQuoteId ?? null,
117
+ sourceOrderId: input.sourceOrderId ?? null,
118
+ metadata: input.metadata ?? null
119
+ });
120
+ this.em.persist(sub);
121
+ const items = [];
122
+ for (const itemInput of input.items) {
123
+ const item = this.em.create(CpqInventorySubscriptionItem, {
124
+ organizationId: scope.organizationId,
125
+ tenantId: scope.tenantId,
126
+ subscriptionId: sub.id,
127
+ customerId: input.customerId,
128
+ parentItemId: itemInput.parentItemId ?? null,
129
+ productId: itemInput.productId ?? null,
130
+ offeringId: itemInput.offeringId ?? null,
131
+ specId: itemInput.specId ?? null,
132
+ name: itemInput.name,
133
+ status: "pending",
134
+ configuration: itemInput.configuration ?? {},
135
+ charges: itemInput.charges ?? null,
136
+ mrcAmount: String(itemInput.mrcAmount ?? 0),
137
+ nrcAmount: String(itemInput.nrcAmount ?? 0),
138
+ currencyCode: itemInput.currencyCode ?? input.currencyCode ?? await getBaseCurrencyCode(this.em, scope),
139
+ quantity: itemInput.quantity ?? 1,
140
+ capacityTotal: itemInput.capacityTotal != null ? String(itemInput.capacityTotal) : null,
141
+ capacityUsed: null,
142
+ capacityUnit: itemInput.capacityUnit ?? null,
143
+ sourceQuoteLineId: itemInput.sourceQuoteLineId ?? null,
144
+ metadata: itemInput.metadata ?? null,
145
+ sortOrder: itemInput.sortOrder ?? 0
146
+ });
147
+ this.em.persist(item);
148
+ items.push(item);
149
+ }
150
+ const mrcSum = items.reduce((s, i) => s + Number(i.mrcAmount), 0);
151
+ const nrcSum = items.reduce((s, i) => s + Number(i.nrcAmount), 0);
152
+ sub.mrcAmount = String(mrcSum);
153
+ sub.nrcAmount = String(nrcSum);
154
+ await this.em.flush();
155
+ const afterSnapshot = await this.snapshotSubscription(sub, scope);
156
+ const log = this.createChangeLog({
157
+ subscription: sub,
158
+ changeType: "created",
159
+ sourceQuoteId: input.sourceQuoteId ?? null,
160
+ sourceOrderId: input.sourceOrderId ?? null,
161
+ performedByUserId: null,
162
+ beforeSnapshot: null,
163
+ afterSnapshot,
164
+ lineChanges: items.map((item) => ({
165
+ action: "add",
166
+ itemId: item.id,
167
+ sourceQuoteLineId: item.sourceQuoteLineId ?? null,
168
+ mrcAmount: Number(item.mrcAmount),
169
+ nrcAmount: Number(item.nrcAmount),
170
+ quantity: item.quantity
171
+ }))
172
+ });
173
+ sub.lastChangeLogId = log.id;
174
+ await this.em.flush();
175
+ return sub;
176
+ }
177
+ async updateSubscription(id, updates, scope) {
178
+ const sub = await this.em.findOne(CpqInventorySubscription, {
179
+ id,
180
+ organizationId: scope.organizationId,
181
+ tenantId: scope.tenantId,
182
+ deletedAt: null
183
+ });
184
+ if (!sub) throw { status: 404, error: "Subscription not found" };
185
+ if (updates.name !== void 0) sub.name = updates.name;
186
+ if (updates.description !== void 0) sub.description = updates.description;
187
+ if (updates.billingCycle !== void 0) sub.billingCycle = updates.billingCycle;
188
+ if (updates.autoRenew !== void 0) sub.autoRenew = updates.autoRenew;
189
+ if (updates.termMonths !== void 0) sub.termMonths = updates.termMonths;
190
+ if (updates.currentTermEnd !== void 0) sub.currentTermEnd = updates.currentTermEnd ? new Date(updates.currentTermEnd) : null;
191
+ if (updates.metadata !== void 0) sub.metadata = updates.metadata;
192
+ await this.em.flush();
193
+ return sub;
194
+ }
195
+ async deleteSubscription(id, scope) {
196
+ const sub = await this.em.findOne(CpqInventorySubscription, {
197
+ id,
198
+ organizationId: scope.organizationId,
199
+ tenantId: scope.tenantId,
200
+ deletedAt: null
201
+ });
202
+ if (!sub) throw { status: 404, error: "Subscription not found" };
203
+ sub.deletedAt = /* @__PURE__ */ new Date();
204
+ await this.em.flush();
205
+ return { success: true };
206
+ }
207
+ async transitionSubscriptionStatus(id, targetStatus, scope) {
208
+ const sub = await this.em.findOne(CpqInventorySubscription, {
209
+ id,
210
+ organizationId: scope.organizationId,
211
+ tenantId: scope.tenantId,
212
+ deletedAt: null
213
+ });
214
+ if (!sub) throw { status: 404, error: "Subscription not found" };
215
+ const currentStatus = sub.status;
216
+ const allowed = INVENTORY_SUBSCRIPTION_TRANSITIONS[currentStatus] ?? [];
217
+ if (!allowed.includes(targetStatus)) {
218
+ throw { status: 409, error: `Cannot transition from '${sub.status}' to '${targetStatus}'` };
219
+ }
220
+ const now = /* @__PURE__ */ new Date();
221
+ if (targetStatus === "active" && !sub.activatedAt) {
222
+ sub.activatedAt = now;
223
+ }
224
+ if (targetStatus === "suspended") {
225
+ sub.suspendedAt = now;
226
+ }
227
+ if (currentStatus === "suspended" && targetStatus === "active") {
228
+ sub.suspendedAt = null;
229
+ }
230
+ if (targetStatus === "terminated" || targetStatus === "superseded") {
231
+ sub.terminatedAt = now;
232
+ }
233
+ const items = await this.em.find(CpqInventorySubscriptionItem, {
234
+ subscriptionId: sub.id,
235
+ organizationId: scope.organizationId,
236
+ tenantId: scope.tenantId,
237
+ deletedAt: null,
238
+ status: { $nin: ["terminated", "expired", "superseded"] }
239
+ });
240
+ for (const item of items) {
241
+ item.status = targetStatus;
242
+ }
243
+ if (targetStatus === "active" || targetStatus === "terminated" || targetStatus === "superseded") {
244
+ const assetTargetStatus = targetStatus === "active" ? "active" : "cancelled";
245
+ const excludeStatuses = targetStatus === "active" ? ["returned"] : ["returned", "cancelled"];
246
+ const assets = await this.em.find(CpqInventoryAsset, {
247
+ subscriptionId: sub.id,
248
+ organizationId: scope.organizationId,
249
+ tenantId: scope.tenantId,
250
+ deletedAt: null,
251
+ status: { $nin: excludeStatuses }
252
+ });
253
+ for (const asset of assets) {
254
+ asset.status = assetTargetStatus;
255
+ }
256
+ }
257
+ sub.status = targetStatus;
258
+ await this.em.flush();
259
+ return sub;
260
+ }
261
+ // ═══════════════════════════════════════════════════════════════
262
+ // Subscription Items
263
+ // ═══════════════════════════════════════════════════════════════
264
+ async listSubscriptionItems(filters, scope) {
265
+ const where = {
266
+ organizationId: scope.organizationId,
267
+ tenantId: scope.tenantId,
268
+ deletedAt: null
269
+ };
270
+ if (filters.subscriptionId) where.subscriptionId = filters.subscriptionId;
271
+ if (filters.parentItemId) where.parentItemId = filters.parentItemId;
272
+ if (filters.status) where.status = filters.status;
273
+ return this.em.find(CpqInventorySubscriptionItem, where, {
274
+ orderBy: { sortOrder: "asc" }
275
+ });
276
+ }
277
+ async addSubscriptionItem(subscriptionId, input, scope) {
278
+ const sub = await this.em.findOne(CpqInventorySubscription, {
279
+ id: subscriptionId,
280
+ organizationId: scope.organizationId,
281
+ tenantId: scope.tenantId,
282
+ deletedAt: null
283
+ });
284
+ if (!sub) throw { status: 404, error: "Subscription not found" };
285
+ if (input.parentItemId) {
286
+ const parent = await this.em.findOne(CpqInventorySubscriptionItem, {
287
+ id: input.parentItemId,
288
+ organizationId: scope.organizationId,
289
+ tenantId: scope.tenantId,
290
+ deletedAt: null
291
+ });
292
+ if (!parent) throw { status: 404, error: "Parent item not found" };
293
+ if (parent.subscriptionId !== subscriptionId) {
294
+ throw { status: 409, error: "Parent item does not belong to this subscription" };
295
+ }
296
+ }
297
+ const item = this.em.create(CpqInventorySubscriptionItem, {
298
+ organizationId: scope.organizationId,
299
+ tenantId: scope.tenantId,
300
+ subscriptionId,
301
+ customerId: sub.customerId,
302
+ parentItemId: input.parentItemId ?? null,
303
+ productId: input.productId ?? null,
304
+ offeringId: input.offeringId ?? null,
305
+ specId: input.specId ?? null,
306
+ name: input.name,
307
+ status: "pending",
308
+ configuration: input.configuration ?? {},
309
+ charges: input.charges ?? null,
310
+ mrcAmount: String(input.mrcAmount ?? 0),
311
+ nrcAmount: String(input.nrcAmount ?? 0),
312
+ currencyCode: input.currencyCode ?? sub.currencyCode,
313
+ quantity: input.quantity ?? 1,
314
+ capacityTotal: input.capacityTotal != null ? String(input.capacityTotal) : null,
315
+ capacityUsed: null,
316
+ capacityUnit: input.capacityUnit ?? null,
317
+ sourceQuoteLineId: input.sourceQuoteLineId ?? null,
318
+ metadata: input.metadata ?? null,
319
+ sortOrder: input.sortOrder ?? 0
320
+ });
321
+ this.em.persist(item);
322
+ await this.em.flush();
323
+ await this.recomputeSubscriptionAggregates(subscriptionId, scope);
324
+ return item;
325
+ }
326
+ async updateSubscriptionItem(id, updates, scope) {
327
+ const item = await this.em.findOne(CpqInventorySubscriptionItem, {
328
+ id,
329
+ organizationId: scope.organizationId,
330
+ tenantId: scope.tenantId,
331
+ deletedAt: null
332
+ });
333
+ if (!item) throw { status: 404, error: "Subscription item not found" };
334
+ if (updates.name !== void 0) item.name = updates.name;
335
+ if (updates.metadata !== void 0) item.metadata = updates.metadata;
336
+ if (updates.capacityTotal !== void 0) item.capacityTotal = updates.capacityTotal != null ? String(updates.capacityTotal) : null;
337
+ if (updates.capacityUsed !== void 0) item.capacityUsed = updates.capacityUsed != null ? String(updates.capacityUsed) : null;
338
+ if (updates.capacityUnit !== void 0) item.capacityUnit = updates.capacityUnit;
339
+ if (updates.sortOrder !== void 0) item.sortOrder = updates.sortOrder;
340
+ await this.em.flush();
341
+ return item;
342
+ }
343
+ async deleteSubscriptionItem(id, scope) {
344
+ const item = await this.em.findOne(CpqInventorySubscriptionItem, {
345
+ id,
346
+ organizationId: scope.organizationId,
347
+ tenantId: scope.tenantId,
348
+ deletedAt: null
349
+ });
350
+ if (!item) throw { status: 404, error: "Subscription item not found" };
351
+ item.deletedAt = /* @__PURE__ */ new Date();
352
+ await this.em.flush();
353
+ await this.recomputeSubscriptionAggregates(item.subscriptionId, scope);
354
+ return { success: true };
355
+ }
356
+ // ═══════════════════════════════════════════════════════════════
357
+ // Assets
358
+ // ═══════════════════════════════════════════════════════════════
359
+ async listAssets(filters, scope) {
360
+ const page = Math.max(1, filters.page ?? 1);
361
+ const pageSize = Math.min(100, Math.max(1, filters.pageSize ?? 50));
362
+ const offset = (page - 1) * pageSize;
363
+ const where = {
364
+ organizationId: scope.organizationId,
365
+ tenantId: scope.tenantId,
366
+ deletedAt: null
367
+ };
368
+ if (filters.customerId) where.customerId = filters.customerId;
369
+ if (filters.status) {
370
+ where.status = Array.isArray(filters.status) ? { $in: filters.status } : filters.status;
371
+ }
372
+ if (filters.assetType) where.assetType = filters.assetType;
373
+ if (filters.productId) where.productId = filters.productId;
374
+ if (filters.subscriptionId) where.subscriptionId = filters.subscriptionId;
375
+ if (filters.subscriptionItemId) where.subscriptionItemId = filters.subscriptionItemId;
376
+ const search = filters.search?.trim();
377
+ if (search) {
378
+ where.$or = [
379
+ { code: { $ilike: `%${search}%` } },
380
+ { name: { $ilike: `%${search}%` } }
381
+ ];
382
+ }
383
+ const sortField = filters.sortField ?? "createdAt";
384
+ const sortDir = filters.sortDir === "asc" ? "asc" : "desc";
385
+ const [items, total] = await this.em.findAndCount(
386
+ CpqInventoryAsset,
387
+ where,
388
+ { limit: pageSize, offset, orderBy: { [sortField]: sortDir } }
389
+ );
390
+ return {
391
+ items,
392
+ total,
393
+ page,
394
+ pageSize,
395
+ totalPages: Math.ceil(total / pageSize)
396
+ };
397
+ }
398
+ async getAsset(assetId, scope) {
399
+ const asset = await this.em.findOne(CpqInventoryAsset, {
400
+ id: assetId,
401
+ organizationId: scope.organizationId,
402
+ tenantId: scope.tenantId,
403
+ deletedAt: null
404
+ });
405
+ if (!asset) throw { status: 404, error: "Asset not found" };
406
+ return asset;
407
+ }
408
+ async createAsset(input, scope) {
409
+ const code = input.code ?? await this.generateAssetCode(scope);
410
+ if (input.subscriptionId) {
411
+ const sub = await this.em.findOne(CpqInventorySubscription, {
412
+ id: input.subscriptionId,
413
+ organizationId: scope.organizationId,
414
+ tenantId: scope.tenantId,
415
+ deletedAt: null
416
+ });
417
+ if (!sub) throw { status: 404, error: "Subscription not found" };
418
+ }
419
+ if (input.subscriptionItemId) {
420
+ const item = await this.em.findOne(CpqInventorySubscriptionItem, {
421
+ id: input.subscriptionItemId,
422
+ organizationId: scope.organizationId,
423
+ tenantId: scope.tenantId,
424
+ deletedAt: null
425
+ });
426
+ if (!item) throw { status: 404, error: "Subscription item not found" };
427
+ }
428
+ const asset = this.em.create(CpqInventoryAsset, {
429
+ organizationId: scope.organizationId,
430
+ tenantId: scope.tenantId,
431
+ customerId: input.customerId,
432
+ subscriptionId: input.subscriptionId ?? null,
433
+ subscriptionItemId: input.subscriptionItemId ?? null,
434
+ productId: input.productId ?? null,
435
+ offeringId: input.offeringId ?? null,
436
+ specId: input.specId ?? null,
437
+ code,
438
+ name: input.name,
439
+ description: input.description ?? null,
440
+ status: "pending",
441
+ assetType: input.assetType ?? "one_time",
442
+ currencyCode: input.currencyCode ?? await getBaseCurrencyCode(this.em, scope),
443
+ purchasePrice: String(input.purchasePrice ?? 0),
444
+ quantity: input.quantity ?? 1,
445
+ purchaseDate: input.purchaseDate ? new Date(input.purchaseDate) : null,
446
+ deliveryDate: input.deliveryDate ? new Date(input.deliveryDate) : null,
447
+ configuration: input.configuration ?? {},
448
+ charges: input.charges ?? null,
449
+ sourceQuoteId: input.sourceQuoteId ?? null,
450
+ sourceOrderId: input.sourceOrderId ?? null,
451
+ sourceQuoteLineId: input.sourceQuoteLineId ?? null,
452
+ metadata: input.metadata ?? null
453
+ });
454
+ this.em.persist(asset);
455
+ await this.em.flush();
456
+ return asset;
457
+ }
458
+ async updateAsset(id, updates, scope) {
459
+ const asset = await this.em.findOne(CpqInventoryAsset, {
460
+ id,
461
+ organizationId: scope.organizationId,
462
+ tenantId: scope.tenantId,
463
+ deletedAt: null
464
+ });
465
+ if (!asset) throw { status: 404, error: "Asset not found" };
466
+ if (updates.name !== void 0) asset.name = updates.name;
467
+ if (updates.description !== void 0) asset.description = updates.description;
468
+ if (updates.assetType !== void 0) asset.assetType = updates.assetType;
469
+ if (updates.deliveryDate !== void 0) asset.deliveryDate = updates.deliveryDate ? new Date(updates.deliveryDate) : null;
470
+ if (updates.metadata !== void 0) asset.metadata = updates.metadata;
471
+ await this.em.flush();
472
+ return asset;
473
+ }
474
+ async deleteAsset(id, scope) {
475
+ const asset = await this.em.findOne(CpqInventoryAsset, {
476
+ id,
477
+ organizationId: scope.organizationId,
478
+ tenantId: scope.tenantId,
479
+ deletedAt: null
480
+ });
481
+ if (!asset) throw { status: 404, error: "Asset not found" };
482
+ asset.deletedAt = /* @__PURE__ */ new Date();
483
+ await this.em.flush();
484
+ return { success: true };
485
+ }
486
+ async transitionAssetStatus(id, targetStatus, scope) {
487
+ const asset = await this.em.findOne(CpqInventoryAsset, {
488
+ id,
489
+ organizationId: scope.organizationId,
490
+ tenantId: scope.tenantId,
491
+ deletedAt: null
492
+ });
493
+ if (!asset) throw { status: 404, error: "Asset not found" };
494
+ const currentStatus = asset.status;
495
+ const allowed = INVENTORY_ASSET_TRANSITIONS[currentStatus] ?? [];
496
+ if (!allowed.includes(targetStatus)) {
497
+ throw { status: 409, error: `Cannot transition from '${asset.status}' to '${targetStatus}'` };
498
+ }
499
+ asset.status = targetStatus;
500
+ await this.em.flush();
501
+ return asset;
502
+ }
503
+ // ═══════════════════════════════════════════════════════════════
504
+ // Customer View
505
+ // ═══════════════════════════════════════════════════════════════
506
+ async getCustomerInventory(customerId, filters, scope) {
507
+ const baseWhere = {
508
+ organizationId: scope.organizationId,
509
+ tenantId: scope.tenantId,
510
+ deletedAt: null,
511
+ customerId
512
+ };
513
+ const subTerminalStatuses = ["terminated", "expired"];
514
+ const itemTerminalStatuses = ["terminated", "expired"];
515
+ const assetTerminalStatuses = ["returned"];
516
+ const subWhere = { ...baseWhere };
517
+ const itemWhere = { ...baseWhere };
518
+ const assetWhere = { ...baseWhere };
519
+ if (filters.status) {
520
+ const statuses = Array.isArray(filters.status) ? filters.status : [filters.status];
521
+ subWhere.status = { $in: statuses };
522
+ itemWhere.status = { $in: statuses };
523
+ assetWhere.status = { $in: statuses };
524
+ } else if (!filters.includeTerminated) {
525
+ subWhere.status = { $nin: subTerminalStatuses };
526
+ itemWhere.status = { $nin: itemTerminalStatuses };
527
+ assetWhere.status = { $nin: assetTerminalStatuses };
528
+ }
529
+ if (filters.productId) {
530
+ subWhere.id = { $in: await this.subscriptionIdsForProduct(filters.productId, scope) };
531
+ itemWhere.productId = filters.productId;
532
+ assetWhere.productId = filters.productId;
533
+ }
534
+ const [subscriptions, subscriptionItems, assets] = await Promise.all([
535
+ this.em.find(CpqInventorySubscription, subWhere, { orderBy: { createdAt: "desc" } }),
536
+ this.em.find(CpqInventorySubscriptionItem, itemWhere, { orderBy: { sortOrder: "asc" } }),
537
+ this.em.find(CpqInventoryAsset, assetWhere, { orderBy: { createdAt: "desc" } })
538
+ ]);
539
+ const activeSubscriptions = subscriptions.filter((s) => s.status === "active").length;
540
+ const totalMrc = subscriptions.filter((s) => s.status === "active").reduce((sum, s) => sum + Number(s.mrcAmount), 0);
541
+ const activeAssets = assets.filter((a) => a.status === "active").length;
542
+ const pendingItems = subscriptionItems.filter((i) => i.status === "pending").length;
543
+ const currencyCode = subscriptions[0]?.currencyCode ?? assets[0]?.currencyCode ?? await getBaseCurrencyCode(this.em, scope);
544
+ return {
545
+ customerId,
546
+ subscriptions,
547
+ subscriptionItems,
548
+ assets,
549
+ summary: {
550
+ activeSubscriptions,
551
+ totalMrc,
552
+ activeAssets,
553
+ pendingItems,
554
+ currencyCode
555
+ }
556
+ };
557
+ }
558
+ async getCustomerParentItems(customerId, filters, scope) {
559
+ const where = {
560
+ organizationId: scope.organizationId,
561
+ tenantId: scope.tenantId,
562
+ deletedAt: null,
563
+ customerId,
564
+ parentItemId: null,
565
+ status: "active"
566
+ };
567
+ if (filters.productId) where.productId = filters.productId;
568
+ if (filters.offeringId) where.offeringId = filters.offeringId;
569
+ return this.em.find(CpqInventorySubscriptionItem, where, {
570
+ orderBy: { sortOrder: "asc" }
571
+ });
572
+ }
573
+ // ═══════════════════════════════════════════════════════════════
574
+ // ARC (Amend / Renew / Cancel) — XD-250
575
+ //
576
+ // Mutate-in-place primitives invoked by cpqOrderService.activateOrder
577
+ // per attached target subscription. Each method:
578
+ // • is idempotent on (sourceOrderId, subscriptionId), enforced via the
579
+ // DB-level UNIQUE on cpq_subscription_change_logs;
580
+ // • takes optimistic lock on subscription.version (MikroORM auto-bumps);
581
+ // • writes a CpqSubscriptionChangeLog row and updates
582
+ // subscription.lastChangeLogId.
583
+ //
584
+ // Event emission is the caller's job (centralized in cpqOrderService
585
+ // AFTER the activation transaction commits).
586
+ // ═══════════════════════════════════════════════════════════════
587
+ async findExpiringSubscriptions(filters, scope) {
588
+ const withinDays = Math.min(365, Math.max(1, filters.withinDays ?? 30));
589
+ const status = filters.status ?? "active";
590
+ const page = Math.max(1, filters.page ?? 1);
591
+ const pageSize = Math.min(100, Math.max(1, filters.pageSize ?? 50));
592
+ const offset = (page - 1) * pageSize;
593
+ const now = /* @__PURE__ */ new Date();
594
+ const horizon = new Date(now.getTime() + withinDays * 24 * 60 * 60 * 1e3);
595
+ const where = {
596
+ organizationId: scope.organizationId,
597
+ tenantId: scope.tenantId,
598
+ deletedAt: null,
599
+ status,
600
+ currentTermEnd: { $gte: now, $lte: horizon }
601
+ };
602
+ if (filters.customerId) where.customerId = filters.customerId;
603
+ if (filters.billingCycle) where.billingCycle = filters.billingCycle;
604
+ const [items, total] = await this.em.findAndCount(
605
+ CpqInventorySubscription,
606
+ where,
607
+ { limit: pageSize, offset, orderBy: { currentTermEnd: "asc" } }
608
+ );
609
+ return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
610
+ }
611
+ async listChangeLog(subscriptionId, filters, scope) {
612
+ const page = Math.max(1, filters.page ?? 1);
613
+ const pageSize = Math.min(100, Math.max(1, filters.pageSize ?? 50));
614
+ const offset = (page - 1) * pageSize;
615
+ const where = {
616
+ organizationId: scope.organizationId,
617
+ tenantId: scope.tenantId,
618
+ deletedAt: null,
619
+ subscriptionId
620
+ };
621
+ const [items, total] = await this.em.findAndCount(
622
+ CpqSubscriptionChangeLog,
623
+ where,
624
+ { limit: pageSize, offset, orderBy: { createdAt: "desc" } }
625
+ );
626
+ return { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) };
627
+ }
628
+ async applyAmendment(input, scope) {
629
+ const idempotent = await this.findIdempotentChangeLog(
630
+ input.sourceOrderId,
631
+ input.subscriptionId,
632
+ scope
633
+ );
634
+ if (idempotent) {
635
+ const sub2 = await this.requireSubscription(input.subscriptionId, scope);
636
+ return { changeLog: idempotent, subscription: sub2 };
637
+ }
638
+ const sub = await this.requireSubscription(input.subscriptionId, scope);
639
+ const beforeSnapshot = await this.snapshotSubscription(sub, scope);
640
+ const { lineChangeSummary } = await this.applyLineChanges(
641
+ sub,
642
+ input.lineChanges,
643
+ scope,
644
+ null
645
+ );
646
+ await this.recomputeSubscriptionAggregatesInPlace(sub, scope);
647
+ await this.em.flush();
648
+ const afterSnapshot = await this.snapshotSubscription(sub, scope);
649
+ const log = this.createChangeLog({
650
+ subscription: sub,
651
+ changeType: "amend",
652
+ sourceQuoteId: input.sourceQuoteId,
653
+ sourceOrderId: input.sourceOrderId,
654
+ performedByUserId: input.performedByUserId,
655
+ beforeSnapshot,
656
+ afterSnapshot,
657
+ lineChanges: lineChangeSummary
658
+ });
659
+ sub.lastChangeLogId = log.id;
660
+ await this.em.flush();
661
+ return { changeLog: log, subscription: sub };
662
+ }
663
+ async applyRenewal(input, scope) {
664
+ const idempotent = await this.findIdempotentChangeLog(
665
+ input.sourceOrderId,
666
+ input.subscriptionId,
667
+ scope
668
+ );
669
+ if (idempotent) {
670
+ const sub2 = await this.requireSubscription(input.subscriptionId, scope);
671
+ return { changeLog: idempotent, subscription: sub2 };
672
+ }
673
+ const sub = await this.requireSubscription(input.subscriptionId, scope);
674
+ const beforeSnapshot = await this.snapshotSubscription(sub, scope);
675
+ const oldTermStart = sub.currentTermStart;
676
+ const oldTermEnd = sub.currentTermEnd;
677
+ const oldTermMonths = sub.termMonths;
678
+ const newStart = toDate(input.term.newTermStart);
679
+ const newEnd = toDate(input.term.newTermEnd);
680
+ sub.currentTermStart = newStart;
681
+ sub.currentTermEnd = newEnd;
682
+ if (input.term.newTermMonths != null) sub.termMonths = input.term.newTermMonths;
683
+ const { lineChangeSummary } = await this.applyLineChanges(
684
+ sub,
685
+ input.lineChanges,
686
+ scope,
687
+ null
688
+ );
689
+ await this.recomputeSubscriptionAggregatesInPlace(sub, scope);
690
+ await this.em.flush();
691
+ const afterSnapshot = await this.snapshotSubscription(sub, scope);
692
+ const log = this.createChangeLog({
693
+ subscription: sub,
694
+ changeType: "renew",
695
+ sourceQuoteId: input.sourceQuoteId,
696
+ sourceOrderId: input.sourceOrderId,
697
+ performedByUserId: input.performedByUserId,
698
+ beforeSnapshot,
699
+ afterSnapshot,
700
+ lineChanges: lineChangeSummary,
701
+ termChange: {
702
+ oldTermStart: toIsoOrNull(oldTermStart),
703
+ newTermStart: newStart.toISOString(),
704
+ oldTermEnd: toIsoOrNull(oldTermEnd),
705
+ newTermEnd: newEnd.toISOString(),
706
+ oldTermMonths: oldTermMonths ?? null,
707
+ newTermMonths: input.term.newTermMonths ?? oldTermMonths ?? null
708
+ }
709
+ });
710
+ sub.lastChangeLogId = log.id;
711
+ await this.em.flush();
712
+ return { changeLog: log, subscription: sub };
713
+ }
714
+ async applyMergeRenewal(input, scope) {
715
+ if (input.sourceIds.length < 2) {
716
+ throw { status: 400, error: "Merge requires at least two source subscriptions" };
717
+ }
718
+ const existingSourceLogs = await this.em.find(CpqSubscriptionChangeLog, {
719
+ organizationId: scope.organizationId,
720
+ tenantId: scope.tenantId,
721
+ deletedAt: null,
722
+ sourceOrderId: input.sourceOrderId,
723
+ subscriptionId: { $in: input.sourceIds },
724
+ changeType: "merge-source"
725
+ });
726
+ if (existingSourceLogs.length === input.sourceIds.length) {
727
+ const mergeResult = await this.em.findOne(CpqSubscriptionChangeLog, {
728
+ organizationId: scope.organizationId,
729
+ tenantId: scope.tenantId,
730
+ deletedAt: null,
731
+ sourceOrderId: input.sourceOrderId,
732
+ changeType: "merge-result"
733
+ });
734
+ if (mergeResult) {
735
+ const m2 = await this.requireSubscription(mergeResult.subscriptionId, scope);
736
+ return {
737
+ mergedSubscription: m2,
738
+ mergeResultChangeLog: mergeResult,
739
+ sourceChangeLogs: existingSourceLogs
740
+ };
741
+ }
742
+ }
743
+ const sources = await this.em.find(CpqInventorySubscription, {
744
+ id: { $in: input.sourceIds },
745
+ organizationId: scope.organizationId,
746
+ tenantId: scope.tenantId,
747
+ deletedAt: null
748
+ });
749
+ if (sources.length !== input.sourceIds.length) {
750
+ throw { status: 404, error: "One or more source subscriptions not found" };
751
+ }
752
+ const customers = new Set(sources.map((s) => s.customerId));
753
+ const currencies = new Set(sources.map((s) => s.currencyCode));
754
+ const cycles = new Set(sources.map((s) => s.billingCycle));
755
+ if (customers.size > 1 || currencies.size > 1 || cycles.size > 1) {
756
+ throw {
757
+ status: 400,
758
+ error: "Merge sources must share customer, currency, and billing cycle"
759
+ };
760
+ }
761
+ for (const s of sources) {
762
+ if (s.status !== "active" && s.status !== "suspended") {
763
+ throw {
764
+ status: 409,
765
+ error: `Source subscription ${s.id} is in '${s.status}' status \u2014 must be active or suspended to merge`
766
+ };
767
+ }
768
+ }
769
+ const customerId = sources[0].customerId;
770
+ const currencyCode = sources[0].currencyCode;
771
+ const billingCycle = sources[0].billingCycle;
772
+ const beforeSnapshots = /* @__PURE__ */ new Map();
773
+ for (const s of sources) {
774
+ beforeSnapshots.set(s.id, await this.snapshotSubscription(s, scope));
775
+ }
776
+ const newTermStart = toDate(input.term.newTermStart);
777
+ const newTermEnd = toDate(input.term.newTermEnd);
778
+ const code = input.mergeMeta.newSubCode ?? await this.generateSubscriptionCode(scope);
779
+ const name = input.mergeMeta.newSubName ?? `Merged contract`;
780
+ const newSubId = crypto.randomUUID();
781
+ const m = this.em.create(CpqInventorySubscription, {
782
+ id: newSubId,
783
+ organizationId: scope.organizationId,
784
+ tenantId: scope.tenantId,
785
+ customerId,
786
+ code,
787
+ name,
788
+ status: "active",
789
+ billingCycle,
790
+ currencyCode,
791
+ mrcAmount: "0",
792
+ nrcAmount: "0",
793
+ currentTermStart: newTermStart,
794
+ currentTermEnd: newTermEnd,
795
+ termMonths: input.term.newTermMonths ?? null,
796
+ autoRenew: true,
797
+ activatedAt: /* @__PURE__ */ new Date(),
798
+ sourceQuoteId: input.sourceQuoteId,
799
+ sourceOrderId: input.sourceOrderId
800
+ });
801
+ this.em.persist(m);
802
+ await this.em.flush();
803
+ const sourceItemToNewItem = /* @__PURE__ */ new Map();
804
+ for (const source of sources) {
805
+ const activeItems = await this.em.find(CpqInventorySubscriptionItem, {
806
+ subscriptionId: source.id,
807
+ organizationId: scope.organizationId,
808
+ tenantId: scope.tenantId,
809
+ deletedAt: null,
810
+ status: { $nin: ["terminated", "expired", "superseded"] }
811
+ });
812
+ for (const item of activeItems) {
813
+ const clonedId = crypto.randomUUID();
814
+ const cloned = this.em.create(CpqInventorySubscriptionItem, {
815
+ id: clonedId,
816
+ organizationId: scope.organizationId,
817
+ tenantId: scope.tenantId,
818
+ subscriptionId: m.id,
819
+ customerId,
820
+ parentItemId: null,
821
+ productId: item.productId ?? null,
822
+ offeringId: item.offeringId ?? null,
823
+ specId: item.specId ?? null,
824
+ name: item.name,
825
+ status: "active",
826
+ configuration: { ...item.configuration },
827
+ charges: item.charges ? [...item.charges] : null,
828
+ mrcAmount: item.mrcAmount,
829
+ nrcAmount: item.nrcAmount,
830
+ currencyCode: item.currencyCode,
831
+ quantity: item.quantity,
832
+ capacityTotal: item.capacityTotal,
833
+ capacityUsed: item.capacityUsed,
834
+ capacityUnit: item.capacityUnit,
835
+ // Carry-over marker: sourceQuoteLineId stays null so the operator can
836
+ // distinguish from operator-add lines later.
837
+ sourceQuoteLineId: null,
838
+ metadata: item.metadata,
839
+ sortOrder: item.sortOrder
840
+ });
841
+ this.em.persist(cloned);
842
+ sourceItemToNewItem.set(item.id, clonedId);
843
+ }
844
+ }
845
+ await this.em.flush();
846
+ for (const source of sources) {
847
+ const assets = await this.em.find(CpqInventoryAsset, {
848
+ subscriptionId: source.id,
849
+ organizationId: scope.organizationId,
850
+ tenantId: scope.tenantId,
851
+ deletedAt: null,
852
+ status: { $nin: ["returned", "cancelled"] }
853
+ });
854
+ for (const asset of assets) {
855
+ asset.subscriptionId = m.id;
856
+ if (asset.subscriptionItemId) {
857
+ const newItemId = sourceItemToNewItem.get(asset.subscriptionItemId);
858
+ if (newItemId) asset.subscriptionItemId = newItemId;
859
+ }
860
+ }
861
+ }
862
+ await this.em.flush();
863
+ const translatedChanges = input.lineChanges.map((lc) => {
864
+ if ((lc.action === "cancel" || lc.action === "modify") && lc.sourceSubscriptionItemId) {
865
+ const newItemId = sourceItemToNewItem.get(lc.sourceSubscriptionItemId);
866
+ return {
867
+ ...lc,
868
+ sourceSubscriptionItemId: newItemId ?? lc.sourceSubscriptionItemId
869
+ };
870
+ }
871
+ return lc;
872
+ });
873
+ const { lineChangeSummary } = await this.applyLineChanges(
874
+ m,
875
+ translatedChanges,
876
+ scope,
877
+ null
878
+ );
879
+ await this.recomputeSubscriptionAggregatesInPlace(m, scope);
880
+ await this.em.flush();
881
+ const afterSnapshot = await this.snapshotSubscription(m, scope);
882
+ const sourceLogs = [];
883
+ const now = /* @__PURE__ */ new Date();
884
+ for (const source of sources) {
885
+ const items = await this.em.find(CpqInventorySubscriptionItem, {
886
+ subscriptionId: source.id,
887
+ organizationId: scope.organizationId,
888
+ tenantId: scope.tenantId,
889
+ deletedAt: null,
890
+ status: { $nin: ["terminated", "expired", "superseded"] }
891
+ });
892
+ for (const item of items) {
893
+ item.status = "superseded";
894
+ }
895
+ source.status = "superseded";
896
+ source.mergedIntoSubscriptionId = m.id;
897
+ source.terminatedAt = now;
898
+ source.mrcAmount = "0";
899
+ const sourceLog = this.createChangeLog({
900
+ subscription: source,
901
+ changeType: "merge-source",
902
+ sourceQuoteId: input.sourceQuoteId,
903
+ sourceOrderId: input.sourceOrderId,
904
+ performedByUserId: input.performedByUserId,
905
+ beforeSnapshot: beforeSnapshots.get(source.id) ?? null,
906
+ // afterSnapshot intentionally null — terminal state IS the meaningful "after".
907
+ afterSnapshot: null,
908
+ lineChanges: null,
909
+ mergedIntoSubscriptionId: m.id
910
+ });
911
+ source.lastChangeLogId = sourceLog.id;
912
+ sourceLogs.push(sourceLog);
913
+ }
914
+ const mergeResultLog = this.createChangeLog({
915
+ subscription: m,
916
+ changeType: "merge-result",
917
+ sourceQuoteId: input.sourceQuoteId,
918
+ sourceOrderId: input.sourceOrderId,
919
+ performedByUserId: input.performedByUserId,
920
+ beforeSnapshot: null,
921
+ // M is brand new — no "before" state.
922
+ afterSnapshot,
923
+ lineChanges: lineChangeSummary,
924
+ termChange: {
925
+ oldTermStart: null,
926
+ newTermStart: newTermStart.toISOString(),
927
+ oldTermEnd: null,
928
+ newTermEnd: newTermEnd.toISOString(),
929
+ oldTermMonths: null,
930
+ newTermMonths: input.term.newTermMonths ?? null
931
+ },
932
+ mergedFromSubscriptionIds: input.sourceIds
933
+ });
934
+ m.lastChangeLogId = mergeResultLog.id;
935
+ await this.em.flush();
936
+ return {
937
+ mergedSubscription: m,
938
+ mergeResultChangeLog: mergeResultLog,
939
+ sourceChangeLogs: sourceLogs
940
+ };
941
+ }
942
+ async applyCancel(input, scope) {
943
+ const idempotent = await this.findIdempotentChangeLog(
944
+ input.sourceOrderId,
945
+ input.subscriptionId,
946
+ scope
947
+ );
948
+ if (idempotent) {
949
+ const sub2 = await this.requireSubscription(input.subscriptionId, scope);
950
+ return { changeLog: idempotent, subscription: sub2 };
951
+ }
952
+ const sub = await this.requireSubscription(input.subscriptionId, scope);
953
+ if (sub.status !== "active" && sub.status !== "suspended") {
954
+ throw {
955
+ status: 409,
956
+ error: `Cannot cancel subscription in '${sub.status}' status`
957
+ };
958
+ }
959
+ const beforeSnapshot = await this.snapshotSubscription(sub, scope);
960
+ await this.transitionSubscriptionStatus(sub.id, "terminated", scope);
961
+ const reload = await this.requireSubscription(input.subscriptionId, scope);
962
+ const afterSnapshot = await this.snapshotSubscription(reload, scope);
963
+ const log = this.createChangeLog({
964
+ subscription: reload,
965
+ changeType: "cancel",
966
+ sourceQuoteId: input.sourceQuoteId,
967
+ sourceOrderId: input.sourceOrderId,
968
+ performedByUserId: input.performedByUserId,
969
+ beforeSnapshot,
970
+ afterSnapshot,
971
+ lineChanges: null,
972
+ reasonCode: input.reasonCode ?? null,
973
+ reasonText: input.reasonText ?? null,
974
+ etfAmount: input.etfAmount != null ? String(input.etfAmount) : null,
975
+ etfCurrency: input.etfCurrency ?? null
976
+ });
977
+ reload.lastChangeLogId = log.id;
978
+ await this.em.flush();
979
+ return { changeLog: log, subscription: reload };
980
+ }
981
+ // ═══════════════════════════════════════════════════════════════
982
+ // Private helpers
983
+ // ═══════════════════════════════════════════════════════════════
984
+ async recomputeSubscriptionAggregates(subscriptionId, scope) {
985
+ const items = await this.em.find(CpqInventorySubscriptionItem, {
986
+ subscriptionId,
987
+ organizationId: scope.organizationId,
988
+ tenantId: scope.tenantId,
989
+ deletedAt: null
990
+ });
991
+ const mrcSum = items.reduce((s, i) => s + Number(i.mrcAmount), 0);
992
+ const nrcSum = items.reduce((s, i) => s + Number(i.nrcAmount), 0);
993
+ const sub = await this.em.findOne(CpqInventorySubscription, {
994
+ id: subscriptionId,
995
+ organizationId: scope.organizationId,
996
+ tenantId: scope.tenantId
997
+ });
998
+ if (sub) {
999
+ sub.mrcAmount = String(mrcSum);
1000
+ sub.nrcAmount = String(nrcSum);
1001
+ await this.em.flush();
1002
+ }
1003
+ }
1004
+ // ─── ARC private helpers (XD-250) ────────────────────────────────
1005
+ async requireSubscription(id, scope) {
1006
+ const sub = await this.em.findOne(CpqInventorySubscription, {
1007
+ id,
1008
+ organizationId: scope.organizationId,
1009
+ tenantId: scope.tenantId,
1010
+ deletedAt: null
1011
+ });
1012
+ if (!sub) throw { status: 404, error: "Subscription not found" };
1013
+ return sub;
1014
+ }
1015
+ async findIdempotentChangeLog(sourceOrderId, subscriptionId, scope) {
1016
+ if (!sourceOrderId) return null;
1017
+ return this.em.findOne(CpqSubscriptionChangeLog, {
1018
+ organizationId: scope.organizationId,
1019
+ tenantId: scope.tenantId,
1020
+ deletedAt: null,
1021
+ sourceOrderId,
1022
+ subscriptionId
1023
+ });
1024
+ }
1025
+ async snapshotSubscription(sub, scope) {
1026
+ const items = await this.em.find(CpqInventorySubscriptionItem, {
1027
+ subscriptionId: sub.id,
1028
+ organizationId: scope.organizationId,
1029
+ tenantId: scope.tenantId,
1030
+ deletedAt: null
1031
+ }) ?? [];
1032
+ const assets = await this.em.find(CpqInventoryAsset, {
1033
+ subscriptionId: sub.id,
1034
+ organizationId: scope.organizationId,
1035
+ tenantId: scope.tenantId,
1036
+ deletedAt: null
1037
+ }) ?? [];
1038
+ return {
1039
+ id: sub.id,
1040
+ code: sub.code,
1041
+ name: sub.name,
1042
+ status: sub.status,
1043
+ billingCycle: sub.billingCycle,
1044
+ currencyCode: sub.currencyCode,
1045
+ mrcAmount: sub.mrcAmount,
1046
+ nrcAmount: sub.nrcAmount,
1047
+ currentTermStart: toIsoOrNull(sub.currentTermStart),
1048
+ currentTermEnd: toIsoOrNull(sub.currentTermEnd),
1049
+ termMonths: sub.termMonths ?? null,
1050
+ version: sub.version,
1051
+ items: items.map((i) => ({
1052
+ id: i.id,
1053
+ productId: i.productId,
1054
+ offeringId: i.offeringId,
1055
+ specId: i.specId,
1056
+ name: i.name,
1057
+ status: i.status,
1058
+ configuration: i.configuration,
1059
+ mrcAmount: i.mrcAmount,
1060
+ nrcAmount: i.nrcAmount,
1061
+ currencyCode: i.currencyCode,
1062
+ quantity: i.quantity,
1063
+ sourceQuoteLineId: i.sourceQuoteLineId
1064
+ })),
1065
+ assets: assets.map((a) => ({
1066
+ id: a.id,
1067
+ code: a.code,
1068
+ name: a.name,
1069
+ status: a.status,
1070
+ subscriptionItemId: a.subscriptionItemId,
1071
+ productId: a.productId
1072
+ }))
1073
+ };
1074
+ }
1075
+ /**
1076
+ * Apply ARC line changes to a subscription's item set in place.
1077
+ * Returns a per-line summary ready to write into ChangeLog.line_changes.
1078
+ */
1079
+ async applyLineChanges(sub, lineChanges, scope, _ignored) {
1080
+ void _ignored;
1081
+ const summary = [];
1082
+ if (lineChanges.length === 0) return { lineChangeSummary: summary };
1083
+ const specIdsForAdd = /* @__PURE__ */ new Set();
1084
+ for (const lc of lineChanges) {
1085
+ if (lc.action === "add" && lc.itemInput?.specId) {
1086
+ specIdsForAdd.add(lc.itemInput.specId);
1087
+ }
1088
+ }
1089
+ const assetizableSpecs = /* @__PURE__ */ new Map();
1090
+ if (specIdsForAdd.size > 0) {
1091
+ const specs = await this.em.find(CpqProductSpecification, {
1092
+ id: { $in: Array.from(specIdsForAdd) },
1093
+ organizationId: scope.organizationId,
1094
+ tenantId: scope.tenantId,
1095
+ deletedAt: null
1096
+ });
1097
+ for (const s of specs) assetizableSpecs.set(s.id, s.isAssetizable === true);
1098
+ }
1099
+ const quoteLineToFinalItemId = /* @__PURE__ */ new Map();
1100
+ const preGeneratedAddIds = /* @__PURE__ */ new Map();
1101
+ for (const lc of lineChanges) {
1102
+ if (!lc.quoteLineId) continue;
1103
+ if (lc.action === "add") {
1104
+ const id = crypto.randomUUID();
1105
+ preGeneratedAddIds.set(lc.quoteLineId, id);
1106
+ quoteLineToFinalItemId.set(lc.quoteLineId, id);
1107
+ } else if (lc.action === "modify" && lc.sourceSubscriptionItemId) {
1108
+ quoteLineToFinalItemId.set(lc.quoteLineId, lc.sourceSubscriptionItemId);
1109
+ }
1110
+ }
1111
+ for (const lc of lineChanges) {
1112
+ if (lc.action === "add") {
1113
+ if (!lc.itemInput) {
1114
+ throw { status: 400, error: "add line is missing itemInput" };
1115
+ }
1116
+ const newItemId = lc.quoteLineId ? preGeneratedAddIds.get(lc.quoteLineId) ?? crypto.randomUUID() : crypto.randomUUID();
1117
+ const parentItemId = lc.itemInput.parentItemId ?? (lc.parentQuoteLineId ? quoteLineToFinalItemId.get(lc.parentQuoteLineId) ?? null : null);
1118
+ const item = this.em.create(CpqInventorySubscriptionItem, {
1119
+ id: newItemId,
1120
+ organizationId: scope.organizationId,
1121
+ tenantId: scope.tenantId,
1122
+ subscriptionId: sub.id,
1123
+ customerId: sub.customerId,
1124
+ parentItemId,
1125
+ productId: lc.itemInput.productId ?? null,
1126
+ offeringId: lc.itemInput.offeringId ?? null,
1127
+ specId: lc.itemInput.specId ?? null,
1128
+ name: lc.itemInput.name,
1129
+ status: "active",
1130
+ configuration: lc.itemInput.configuration ?? {},
1131
+ charges: lc.itemInput.charges ?? null,
1132
+ mrcAmount: String(lc.itemInput.mrcAmount ?? 0),
1133
+ nrcAmount: String(lc.itemInput.nrcAmount ?? 0),
1134
+ currencyCode: lc.itemInput.currencyCode ?? sub.currencyCode,
1135
+ quantity: lc.itemInput.quantity ?? 1,
1136
+ capacityTotal: lc.itemInput.capacityTotal != null ? String(lc.itemInput.capacityTotal) : null,
1137
+ capacityUsed: null,
1138
+ capacityUnit: lc.itemInput.capacityUnit ?? null,
1139
+ sourceQuoteLineId: lc.quoteLineId ?? lc.itemInput.sourceQuoteLineId ?? null,
1140
+ metadata: lc.itemInput.metadata ?? null,
1141
+ sortOrder: lc.itemInput.sortOrder ?? 0
1142
+ });
1143
+ this.em.persist(item);
1144
+ if (lc.itemInput.specId && assetizableSpecs.get(lc.itemInput.specId)) {
1145
+ const code = await this.generateAssetCode(scope);
1146
+ const asset = this.em.create(CpqInventoryAsset, {
1147
+ organizationId: scope.organizationId,
1148
+ tenantId: scope.tenantId,
1149
+ customerId: sub.customerId,
1150
+ subscriptionId: sub.id,
1151
+ subscriptionItemId: item.id,
1152
+ productId: lc.itemInput.productId ?? null,
1153
+ offeringId: lc.itemInput.offeringId ?? null,
1154
+ specId: lc.itemInput.specId,
1155
+ code,
1156
+ name: lc.itemInput.name,
1157
+ status: "pending",
1158
+ assetType: "one_time",
1159
+ currencyCode: lc.itemInput.currencyCode ?? sub.currencyCode,
1160
+ purchasePrice: String(lc.itemInput.nrcAmount ?? 0),
1161
+ quantity: lc.itemInput.quantity ?? 1,
1162
+ configuration: lc.itemInput.configuration ?? {},
1163
+ charges: lc.itemInput.charges ?? null,
1164
+ sourceOrderId: null,
1165
+ sourceQuoteLineId: lc.quoteLineId ?? null
1166
+ });
1167
+ this.em.persist(asset);
1168
+ }
1169
+ summary.push({
1170
+ action: "add",
1171
+ itemId: item.id,
1172
+ quoteLineId: lc.quoteLineId ?? null,
1173
+ mrcAmount: Number(item.mrcAmount),
1174
+ nrcAmount: Number(item.nrcAmount),
1175
+ quantity: item.quantity
1176
+ });
1177
+ } else if (lc.action === "cancel") {
1178
+ if (!lc.sourceSubscriptionItemId) {
1179
+ throw { status: 400, error: "cancel line is missing sourceSubscriptionItemId" };
1180
+ }
1181
+ const item = await this.em.findOne(CpqInventorySubscriptionItem, {
1182
+ id: lc.sourceSubscriptionItemId,
1183
+ subscriptionId: sub.id,
1184
+ organizationId: scope.organizationId,
1185
+ tenantId: scope.tenantId,
1186
+ deletedAt: null
1187
+ });
1188
+ if (!item) {
1189
+ throw {
1190
+ status: 404,
1191
+ error: `Subscription item ${lc.sourceSubscriptionItemId} not found on subscription ${sub.id}`
1192
+ };
1193
+ }
1194
+ item.status = "terminated";
1195
+ const assets = await this.em.find(CpqInventoryAsset, {
1196
+ subscriptionItemId: item.id,
1197
+ organizationId: scope.organizationId,
1198
+ tenantId: scope.tenantId,
1199
+ deletedAt: null,
1200
+ status: { $nin: ["returned", "cancelled"] }
1201
+ });
1202
+ for (const asset of assets) asset.status = "cancelled";
1203
+ summary.push({
1204
+ action: "cancel",
1205
+ itemId: item.id,
1206
+ quoteLineId: lc.quoteLineId ?? null,
1207
+ mrcDelta: -Number(item.mrcAmount)
1208
+ });
1209
+ } else if (lc.action === "modify") {
1210
+ if (!lc.sourceSubscriptionItemId) {
1211
+ throw { status: 400, error: "modify line is missing sourceSubscriptionItemId" };
1212
+ }
1213
+ const item = await this.em.findOne(CpqInventorySubscriptionItem, {
1214
+ id: lc.sourceSubscriptionItemId,
1215
+ subscriptionId: sub.id,
1216
+ organizationId: scope.organizationId,
1217
+ tenantId: scope.tenantId,
1218
+ deletedAt: null
1219
+ });
1220
+ if (!item) {
1221
+ throw {
1222
+ status: 404,
1223
+ error: `Subscription item ${lc.sourceSubscriptionItemId} not found on subscription ${sub.id}`
1224
+ };
1225
+ }
1226
+ const oldMrc = Number(item.mrcAmount);
1227
+ if (lc.modifyPatch?.configuration !== void 0) {
1228
+ item.configuration = lc.modifyPatch.configuration;
1229
+ }
1230
+ if (lc.modifyPatch?.charges !== void 0) {
1231
+ item.charges = lc.modifyPatch.charges;
1232
+ }
1233
+ if (lc.modifyPatch?.mrcAmount !== void 0) {
1234
+ item.mrcAmount = String(lc.modifyPatch.mrcAmount);
1235
+ }
1236
+ if (lc.modifyPatch?.nrcAmount !== void 0) {
1237
+ item.nrcAmount = String(lc.modifyPatch.nrcAmount);
1238
+ }
1239
+ if (lc.modifyPatch?.quantity !== void 0) {
1240
+ item.quantity = lc.modifyPatch.quantity;
1241
+ }
1242
+ const newMrc = Number(item.mrcAmount);
1243
+ if (newMrc !== oldMrc || lc.modifyPatch?.configuration || lc.modifyPatch?.charges) {
1244
+ summary.push({
1245
+ action: "modify",
1246
+ itemId: item.id,
1247
+ quoteLineId: lc.quoteLineId ?? null,
1248
+ mrcDelta: newMrc - oldMrc
1249
+ });
1250
+ }
1251
+ }
1252
+ }
1253
+ return { lineChangeSummary: summary };
1254
+ }
1255
+ async recomputeSubscriptionAggregatesInPlace(sub, scope) {
1256
+ const items = await this.em.find(CpqInventorySubscriptionItem, {
1257
+ subscriptionId: sub.id,
1258
+ organizationId: scope.organizationId,
1259
+ tenantId: scope.tenantId,
1260
+ deletedAt: null,
1261
+ status: { $nin: ["terminated", "expired", "superseded"] }
1262
+ });
1263
+ const mrcSum = items.reduce((s, i) => s + Number(i.mrcAmount), 0);
1264
+ const nrcSum = items.reduce((s, i) => s + Number(i.nrcAmount), 0);
1265
+ sub.mrcAmount = String(mrcSum);
1266
+ sub.nrcAmount = String(nrcSum);
1267
+ }
1268
+ createChangeLog(args) {
1269
+ const log = this.em.create(CpqSubscriptionChangeLog, {
1270
+ organizationId: args.subscription.organizationId,
1271
+ tenantId: args.subscription.tenantId,
1272
+ subscriptionId: args.subscription.id,
1273
+ changeType: args.changeType,
1274
+ sourceQuoteId: args.sourceQuoteId ?? null,
1275
+ sourceOrderId: args.sourceOrderId ?? null,
1276
+ performedByUserId: args.performedByUserId ?? null,
1277
+ effectiveAt: /* @__PURE__ */ new Date(),
1278
+ beforeSnapshot: args.beforeSnapshot,
1279
+ afterSnapshot: args.afterSnapshot,
1280
+ lineChanges: args.lineChanges,
1281
+ termChange: args.termChange ?? null,
1282
+ mergedIntoSubscriptionId: args.mergedIntoSubscriptionId ?? null,
1283
+ mergedFromSubscriptionIds: args.mergedFromSubscriptionIds ?? null,
1284
+ reasonCode: args.reasonCode ?? null,
1285
+ reasonText: args.reasonText ?? null,
1286
+ etfAmount: args.etfAmount ?? null,
1287
+ etfCurrency: args.etfCurrency ?? null
1288
+ });
1289
+ this.em.persist(log);
1290
+ return log;
1291
+ }
1292
+ buildItemTree(items) {
1293
+ const nodeMap = /* @__PURE__ */ new Map();
1294
+ for (const item of items) {
1295
+ nodeMap.set(item.id, {
1296
+ id: item.id,
1297
+ subscriptionId: item.subscriptionId,
1298
+ parentItemId: item.parentItemId ?? null,
1299
+ productId: item.productId ?? null,
1300
+ offeringId: item.offeringId ?? null,
1301
+ specId: item.specId ?? null,
1302
+ name: item.name,
1303
+ status: item.status,
1304
+ configuration: item.configuration ?? {},
1305
+ charges: item.charges ?? null,
1306
+ mrcAmount: Number(item.mrcAmount),
1307
+ nrcAmount: Number(item.nrcAmount),
1308
+ currencyCode: item.currencyCode,
1309
+ quantity: item.quantity,
1310
+ capacityTotal: item.capacityTotal != null ? Number(item.capacityTotal) : null,
1311
+ capacityUsed: item.capacityUsed != null ? Number(item.capacityUsed) : null,
1312
+ capacityUnit: item.capacityUnit ?? null,
1313
+ metadata: item.metadata ?? null,
1314
+ sortOrder: item.sortOrder,
1315
+ children: []
1316
+ });
1317
+ }
1318
+ const roots = [];
1319
+ for (const node of nodeMap.values()) {
1320
+ if (node.parentItemId && nodeMap.has(node.parentItemId)) {
1321
+ nodeMap.get(node.parentItemId).children.push(node);
1322
+ } else {
1323
+ roots.push(node);
1324
+ }
1325
+ }
1326
+ return roots;
1327
+ }
1328
+ async generateSubscriptionCode(scope) {
1329
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
1330
+ const count = await this.em.count(CpqInventorySubscription, {
1331
+ organizationId: scope.organizationId,
1332
+ tenantId: scope.tenantId
1333
+ });
1334
+ const seq = String(count + 1).padStart(5, "0");
1335
+ return `SUB-${year}-${seq}`;
1336
+ }
1337
+ async generateAssetCode(scope) {
1338
+ const year = (/* @__PURE__ */ new Date()).getFullYear();
1339
+ const count = await this.em.count(CpqInventoryAsset, {
1340
+ organizationId: scope.organizationId,
1341
+ tenantId: scope.tenantId
1342
+ });
1343
+ const seq = String(count + 1).padStart(5, "0");
1344
+ return `AST-${year}-${seq}`;
1345
+ }
1346
+ async subscriptionIdsForProduct(productId, scope) {
1347
+ const items = await this.em.find(
1348
+ CpqInventorySubscriptionItem,
1349
+ {
1350
+ organizationId: scope.organizationId,
1351
+ tenantId: scope.tenantId,
1352
+ deletedAt: null,
1353
+ productId
1354
+ },
1355
+ { fields: ["subscriptionId"] }
1356
+ );
1357
+ return [...new Set(items.map((i) => i.subscriptionId))];
1358
+ }
1359
+ }
1360
+ function toDate(value) {
1361
+ return value instanceof Date ? value : new Date(value);
1362
+ }
1363
+ function toIsoOrNull(value) {
1364
+ if (value == null) return null;
1365
+ if (value instanceof Date) return value.toISOString();
1366
+ return String(value);
1367
+ }
1368
+ export {
1369
+ DefaultCpqInventoryService
1370
+ };
1371
+ //# sourceMappingURL=cpqInventoryService.js.map