@dainamite/cpq 0.5.0 → 0.7.0

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 (368) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/MIGRATION.md +74 -0
  3. package/dist/modules/cpq/acl.js +1 -0
  4. package/dist/modules/cpq/acl.js.map +2 -2
  5. package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js +45 -0
  6. package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js.map +7 -0
  7. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js +23 -0
  8. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js.map +7 -0
  9. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js +19 -0
  10. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js.map +7 -0
  11. package/dist/modules/cpq/api/orders/[orderId]/route.js +17 -1
  12. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
  13. package/dist/modules/cpq/api/orders/route.js +47 -2
  14. package/dist/modules/cpq/api/orders/route.js.map +2 -2
  15. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js +54 -0
  16. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js.map +7 -0
  17. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +42 -2
  18. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
  19. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -2
  20. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +2 -2
  21. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js +47 -0
  22. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js.map +7 -0
  23. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js +30 -0
  24. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js.map +7 -0
  25. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js +47 -0
  26. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js.map +7 -0
  27. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +32 -2
  28. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
  29. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +33 -2
  30. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +2 -2
  31. package/dist/modules/cpq/api/quotes/price/route.js +43 -3
  32. package/dist/modules/cpq/api/quotes/price/route.js.map +2 -2
  33. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +3 -9
  34. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
  35. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
  36. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
  37. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
  38. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
  39. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
  40. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
  41. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +3 -3
  42. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
  43. package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
  44. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
  45. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +174 -184
  46. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  47. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
  48. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
  49. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
  50. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
  51. package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
  52. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
  53. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +140 -280
  54. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  55. package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
  56. package/dist/modules/cpq/backend/cpq/orders/page.js.map +2 -2
  57. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
  58. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
  59. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +41 -36
  60. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
  61. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
  62. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
  63. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
  64. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
  65. package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
  66. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
  67. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
  68. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
  69. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
  70. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
  71. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
  72. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
  73. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
  74. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
  75. package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
  76. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
  77. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
  78. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
  79. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +208 -0
  80. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
  81. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
  82. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
  83. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
  84. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
  85. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
  86. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
  87. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
  88. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
  89. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +269 -311
  90. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
  91. package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
  92. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
  93. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
  94. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
  95. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
  96. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
  97. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
  98. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
  99. package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
  100. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
  101. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
  102. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
  103. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
  104. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
  105. package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
  106. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
  107. package/dist/modules/cpq/ce.js +8 -0
  108. package/dist/modules/cpq/ce.js.map +2 -2
  109. package/dist/modules/cpq/cli.js +34 -1
  110. package/dist/modules/cpq/cli.js.map +2 -2
  111. package/dist/modules/cpq/commands/price-adjustments.js +107 -0
  112. package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
  113. package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
  114. package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
  115. package/dist/modules/cpq/components/ChargeBreakdownTable.js +148 -0
  116. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
  117. package/dist/modules/cpq/components/PriceWaterfall.js +68 -0
  118. package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
  119. package/dist/modules/cpq/components/PricingSummaryCard.js +112 -0
  120. package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
  121. package/dist/modules/cpq/components/StatusPath.js +5 -3
  122. package/dist/modules/cpq/components/StatusPath.js.map +2 -2
  123. package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
  124. package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
  125. package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
  126. package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
  127. package/dist/modules/cpq/data/entities.js +97 -0
  128. package/dist/modules/cpq/data/entities.js.map +2 -2
  129. package/dist/modules/cpq/data/price-adjustments.js +70 -0
  130. package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
  131. package/dist/modules/cpq/data/validators.js +4 -0
  132. package/dist/modules/cpq/data/validators.js.map +2 -2
  133. package/dist/modules/cpq/di.js +6 -0
  134. package/dist/modules/cpq/di.js.map +2 -2
  135. package/dist/modules/cpq/events.js +47 -0
  136. package/dist/modules/cpq/events.js.map +2 -2
  137. package/dist/modules/cpq/i18n/de.json +1220 -0
  138. package/dist/modules/cpq/i18n/en.json +1220 -0
  139. package/dist/modules/cpq/i18n/es.json +1220 -0
  140. package/dist/modules/cpq/i18n/pl.json +1220 -0
  141. package/dist/modules/cpq/index.js +1 -1
  142. package/dist/modules/cpq/index.js.map +2 -2
  143. package/dist/modules/cpq/lib/auditActor.js +10 -0
  144. package/dist/modules/cpq/lib/auditActor.js.map +7 -0
  145. package/dist/modules/cpq/lib/formatMoney.js +7 -0
  146. package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
  147. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
  148. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
  149. package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
  150. package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
  151. package/dist/modules/cpq/lib/quotePermissions.js +13 -0
  152. package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
  153. package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
  154. package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
  155. package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
  156. package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
  157. package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
  158. package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
  159. package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
  160. package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
  161. package/dist/modules/cpq/services/cpqOrderService.js +25 -28
  162. package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
  163. package/dist/modules/cpq/services/cpqPricingService.js +7 -0
  164. package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
  165. package/dist/modules/cpq/services/cpqQuotingService.js +330 -109
  166. package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
  167. package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
  168. package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
  169. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
  170. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
  171. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
  172. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
  173. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
  174. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
  175. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
  176. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
  177. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
  178. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
  179. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
  180. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
  181. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
  182. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
  183. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
  184. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
  185. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
  186. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
  187. package/dist/modules/cpq/services/types.js.map +2 -2
  188. package/dist/modules/cpq/setup.js +6 -0
  189. package/dist/modules/cpq/setup.js.map +2 -2
  190. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
  191. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
  192. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
  193. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
  194. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
  195. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
  196. package/dist/modules/cpq/widgets/injection-table.js +2 -2
  197. package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
  198. package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
  199. package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
  200. package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
  201. package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
  202. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
  203. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
  204. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
  205. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
  206. package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
  207. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
  208. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
  209. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
  210. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
  211. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
  212. package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
  213. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
  214. package/dist/modules/cpq/workflows/steps/index.js +12 -0
  215. package/dist/modules/cpq/workflows/steps/index.js.map +2 -2
  216. package/package.json +1 -1
  217. package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
  218. package/src/modules/cpq/__tests__/i18n/dictionaries.test.ts +62 -0
  219. package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
  220. package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -0
  221. package/src/modules/cpq/acl.ts +1 -0
  222. package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
  223. package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
  224. package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
  225. package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
  226. package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
  227. package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
  228. package/src/modules/cpq/api/orders/[orderId]/route.ts +18 -0
  229. package/src/modules/cpq/api/orders/route.ts +51 -1
  230. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
  231. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +48 -1
  232. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
  233. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
  234. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
  235. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
  236. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
  237. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +40 -1
  238. package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
  239. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
  240. package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
  241. package/src/modules/cpq/api/quotes/price/route.ts +28 -2
  242. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +3 -9
  243. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
  244. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
  245. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
  246. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +3 -3
  247. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
  248. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
  249. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +164 -120
  250. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
  251. package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
  252. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +122 -308
  253. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
  254. package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
  255. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
  256. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
  257. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
  258. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
  259. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
  260. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
  261. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
  262. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
  263. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  264. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
  265. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
  266. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
  267. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
  268. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
  269. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
  270. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
  271. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +304 -348
  272. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
  273. package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
  274. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
  275. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
  276. package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
  277. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
  278. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
  279. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  280. package/src/modules/cpq/ce.ts +8 -0
  281. package/src/modules/cpq/cli.ts +48 -1
  282. package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
  283. package/src/modules/cpq/commands/price-adjustments.ts +90 -0
  284. package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
  285. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +203 -0
  286. package/src/modules/cpq/components/PriceWaterfall.tsx +96 -0
  287. package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
  288. package/src/modules/cpq/components/StatusPath.tsx +7 -4
  289. package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
  290. package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
  291. package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
  292. package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
  293. package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
  294. package/src/modules/cpq/data/entities.ts +83 -0
  295. package/src/modules/cpq/data/price-adjustments.ts +67 -0
  296. package/src/modules/cpq/data/validators.ts +3 -0
  297. package/src/modules/cpq/di.ts +6 -0
  298. package/src/modules/cpq/events.ts +53 -2
  299. package/src/modules/cpq/i18n/de.json +1220 -0
  300. package/src/modules/cpq/i18n/en.json +1220 -0
  301. package/src/modules/cpq/i18n/es.json +1220 -0
  302. package/src/modules/cpq/i18n/pl.json +1220 -0
  303. package/src/modules/cpq/index.ts +1 -1
  304. package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
  305. package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
  306. package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
  307. package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
  308. package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
  309. package/src/modules/cpq/lib/auditActor.ts +13 -0
  310. package/src/modules/cpq/lib/formatMoney.ts +4 -0
  311. package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
  312. package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
  313. package/src/modules/cpq/lib/quotePermissions.ts +22 -0
  314. package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
  315. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
  316. package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
  317. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
  318. package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
  319. package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
  320. package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
  321. package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
  322. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
  323. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
  324. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
  325. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
  326. package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
  327. package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
  328. package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
  329. package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
  330. package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
  331. package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
  332. package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
  333. package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
  334. package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
  335. package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
  336. package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
  337. package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
  338. package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
  339. package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
  340. package/src/modules/cpq/services/cpqOrderService.ts +22 -28
  341. package/src/modules/cpq/services/cpqPricingService.ts +8 -0
  342. package/src/modules/cpq/services/cpqQuotingService.ts +374 -125
  343. package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
  344. package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
  345. package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
  346. package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
  347. package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
  348. package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
  349. package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
  350. package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
  351. package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
  352. package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
  353. package/src/modules/cpq/services/types.ts +34 -1
  354. package/src/modules/cpq/setup.ts +7 -1
  355. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
  356. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
  357. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
  358. package/src/modules/cpq/widgets/injection-table.ts +8 -2
  359. package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
  360. package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
  361. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
  362. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
  363. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
  364. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
  365. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
  366. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
  367. package/src/modules/cpq/workflows/steps/index.ts +12 -0
  368. package/src/modules/cpq/workflows/types.ts +4 -0
@@ -1,4 +1,12 @@
1
1
  var _a, _b;
2
+ import { cloneQuoteAdjustments, removeQuoteLineTree } from "./priceAdjustments/quoteAdjustmentLifecycle.js";
3
+ import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
4
+ import { loadQuoteAdjustments } from "./priceAdjustments/loadQuoteAdjustments.js";
5
+ import { resolveManualAdjustments } from "./priceAdjustments/resolveManualAdjustments.js";
6
+ import { withChargeWaterfall } from "./priceAdjustments/chargeWaterfall.js";
7
+ import { withQuotePricingWrite } from "./priceAdjustments/quotePricingWrite.js";
8
+ import { emitQuoteWriteEvents } from "./priceAdjustments/quotePricingEvents.js";
9
+ import { computeWaterfallSummary } from "./priceAdjustments/pricingSnapshot.js";
2
10
  import { SalesQuote, SalesQuoteLine } from "@open-mercato/core/modules/sales/data/entities";
3
11
  import { CustomerEntity } from "@open-mercato/core/modules/customers/data/entities";
4
12
  import {
@@ -17,6 +25,17 @@ import {
17
25
  ARC_QUOTE_TYPES,
18
26
  getBaseCurrencyCode
19
27
  } from "./types.js";
28
+ import {
29
+ ARC_PRICE_FROZEN_KEY,
30
+ ARC_REPRICED_KEY,
31
+ arcPricingInputsChanged,
32
+ buildArcMirrorConfiguration,
33
+ isFrozenAmendMirrorLine,
34
+ isArcMirrorLine,
35
+ stripArcMarkers,
36
+ stripQuoteOnlyArcMarkers
37
+ } from "./arcPriceFreeze.js";
38
+ import { isArcCancelledLine, planArcLineRemoval, planArcLineRestore, removalCancelsItem } from "./arcLineRemoval.js";
20
39
  class DefaultCpqQuotingService {
21
40
  constructor(deps) {
22
41
  this.em = deps.em;
@@ -108,12 +127,26 @@ class DefaultCpqQuotingService {
108
127
  }
109
128
  // ─── Add Quote Item ────────────────────────────────────────────
110
129
  async addQuoteItem(quoteId, item, scope) {
111
- const em = this.em;
112
- const cpqConfig = await this.loadCpqConfig(em, quoteId, scope);
130
+ return this.writeQuote(quoteId, scope, (em, quote) => this.addQuoteItemInTransaction(quoteId, item, scope, em, quote), void 0, void 0, LINE_EVENTS);
131
+ }
132
+ async addQuoteItemInTransaction(quoteId, item, scope, em, cpqConfig) {
133
+ item = { ...item, configuration: stripArcMarkers(item.configuration) };
113
134
  const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
114
135
  if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
115
136
  throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
116
137
  }
138
+ if (item.parentLineId && cpqConfig.quoteType && cpqConfig.quoteType !== "new") {
139
+ const parent = await em.findOne(CpqQuoteLineConfiguration, {
140
+ quoteLineId: item.parentLineId,
141
+ quoteConfigurationId: cpqConfig.id,
142
+ organizationId: scope.organizationId,
143
+ tenantId: scope.tenantId,
144
+ deletedAt: null
145
+ });
146
+ if (parent && isArcCancelledLine(parent, cpqConfig.quoteType)) {
147
+ throw new QuotingError(409, "Cannot add a component to a subscription item marked for removal");
148
+ }
149
+ }
117
150
  const { offering, spec } = await this.resolveOffering(em, item, scope);
118
151
  const mergedConfig = {
119
152
  ...cpqConfig.quoteContext ?? {},
@@ -224,8 +257,10 @@ class DefaultCpqQuotingService {
224
257
  }
225
258
  // ─── Update Quote Item ─────────────────────────────────────────
226
259
  async updateQuoteItem(quoteId, lineId, update, scope) {
227
- const em = this.em;
228
- const cpqConfig = await this.loadCpqConfig(em, quoteId, scope);
260
+ return this.writeQuote(quoteId, scope, (em, quote) => this.updateQuoteItemInTransaction(quoteId, lineId, update, scope, em, quote), void 0, void 0, LINE_EVENTS);
261
+ }
262
+ async updateQuoteItemInTransaction(quoteId, lineId, update, scope, em, cpqConfig) {
263
+ update = { ...update, configuration: stripArcMarkers(update.configuration) };
229
264
  const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
230
265
  if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
231
266
  throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
@@ -238,6 +273,9 @@ class DefaultCpqQuotingService {
238
273
  deletedAt: null
239
274
  });
240
275
  if (!lineConfig) throw new QuotingError(404, "Quote line not found");
276
+ if (isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
277
+ throw new QuotingError(409, "This subscription item is marked for removal \u2014 restore it before changing it");
278
+ }
241
279
  const quoteLine = await em.findOne(SalesQuoteLine, {
242
280
  id: lineId,
243
281
  organizationId: scope.organizationId,
@@ -279,12 +317,27 @@ class DefaultCpqQuotingService {
279
317
  if (existingCfg?._arcMirroredName !== void 0) {
280
318
  arcMarkers._arcMirroredName = existingCfg._arcMirroredName;
281
319
  }
320
+ const wasFrozen = isFrozenAmendMirrorLine(existingCfg, cpqConfig.quoteType);
321
+ const pricingInputsChanged = wasFrozen && arcPricingInputsChanged(
322
+ {
323
+ configuration: existingCfg,
324
+ quantity: lineConfig.quantity,
325
+ termMonths: lineConfig.termMonths,
326
+ startDate: lineConfig.startDate,
327
+ endDate: lineConfig.endDate,
328
+ usageEstimates: lineConfig.usageEstimates
329
+ },
330
+ update
331
+ );
332
+ const staysFrozen = wasFrozen && !pricingInputsChanged;
333
+ if (staysFrozen) arcMarkers[ARC_PRICE_FROZEN_KEY] = true;
334
+ if (pricingInputsChanged || existingCfg?.[ARC_REPRICED_KEY] === true) arcMarkers[ARC_REPRICED_KEY] = true;
282
335
  update.configuration = { ...update.configuration, ...arcMarkers };
283
336
  mergedConfig._arcMirroredFromItemId = arcMarkers._arcMirroredFromItemId;
284
337
  mergedConfig._arcMirroredName = arcMarkers._arcMirroredName;
285
338
  const isArcMirrored = !!arcMarkers._arcMirroredFromItemId;
286
339
  let charges;
287
- if (lineConfig.offeringId || lineConfig.productId) {
340
+ if ((lineConfig.offeringId || lineConfig.productId) && !staysFrozen) {
288
341
  charges = await this.pricingService.resolveProductCharges({
289
342
  offeringId: lineConfig.offeringId ?? void 0,
290
343
  productId: lineConfig.productId ?? void 0,
@@ -330,8 +383,9 @@ class DefaultCpqQuotingService {
330
383
  }
331
384
  // ─── Remove Quote Item ─────────────────────────────────────────
332
385
  async removeQuoteItem(quoteId, lineId, scope) {
333
- const em = this.em;
334
- const cpqConfig = await this.loadCpqConfig(em, quoteId, scope);
386
+ return this.writeQuote(quoteId, scope, (em, quote) => this.removeQuoteItemInTransaction(quoteId, lineId, scope, em, quote), void 0, void 0, LINE_EVENTS);
387
+ }
388
+ async removeQuoteItemInTransaction(quoteId, lineId, scope, em, cpqConfig) {
335
389
  const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
336
390
  if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
337
391
  throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
@@ -344,31 +398,78 @@ class DefaultCpqQuotingService {
344
398
  deletedAt: null
345
399
  });
346
400
  if (!lineConfig) throw new QuotingError(404, "Quote line not found");
347
- const now = /* @__PURE__ */ new Date();
348
- lineConfig.deletedAt = now;
349
- const children = await em.find(CpqQuoteLineConfiguration, {
350
- parentLineId: lineId,
351
- quoteConfigurationId: cpqConfig.id,
401
+ if (removalCancelsItem(lineConfig, cpqConfig.quoteType)) {
402
+ if (lineConfig.action === "cancel") {
403
+ throw new QuotingError(409, "This subscription item is already marked for removal");
404
+ }
405
+ const plan = planArcLineRemoval(lineConfig, await loadLines(em, cpqConfig.id, scope), cpqConfig.quoteType);
406
+ for (const root of plan.removeRoots) await removeQuoteLineTree(em, cpqConfig.id, root, scope);
407
+ await withAtomicFlush(em, [() => {
408
+ for (const line of plan.cancel) {
409
+ line.action = "cancel";
410
+ line.charges = [];
411
+ line.nrcTotal = "0";
412
+ line.mrcTotal = "0";
413
+ line.isConfigured = true;
414
+ line.validationErrors = null;
415
+ }
416
+ }]);
417
+ } else {
418
+ await removeQuoteLineTree(em, cpqConfig.id, lineConfig, scope);
419
+ }
420
+ return this.recalculateInternal(em, cpqConfig, salesQuote, scope, true);
421
+ }
422
+ // ─── Restore a removed subscription item (Amend / Renew) ───────
423
+ async restoreQuoteItem(quoteId, lineId, scope) {
424
+ return this.writeQuote(quoteId, scope, (em, quote) => this.restoreQuoteItemInTransaction(lineId, scope, em, quote), void 0, void 0, LINE_EVENTS);
425
+ }
426
+ async restoreQuoteItemInTransaction(lineId, scope, em, cpqConfig) {
427
+ const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
428
+ if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
429
+ throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
430
+ }
431
+ const lines = await loadLines(em, cpqConfig.id, scope);
432
+ const lineConfig = lines.find((line) => line.quoteLineId === lineId);
433
+ if (!lineConfig) throw new QuotingError(404, "Quote line not found");
434
+ if (!isArcCancelledLine(lineConfig, cpqConfig.quoteType) || !isArcMirrorLine(lineConfig.configuration, cpqConfig.quoteType)) {
435
+ throw new QuotingError(409, "Only subscription items marked for removal can be restored");
436
+ }
437
+ const plan = planArcLineRestore(lineConfig, lines, cpqConfig.quoteType);
438
+ if (!plan.ok) throw new QuotingError(409, "Restore the parent item first");
439
+ const itemIdOf = (line) => line.sourceSubscriptionItemId ?? line.configuration?._arcMirroredFromItemId ?? null;
440
+ const itemIds = plan.restore.map(itemIdOf).filter((id) => !!id);
441
+ const items = itemIds.length ? await em.find(CpqInventorySubscriptionItem, { id: { $in: itemIds }, organizationId: scope.organizationId, tenantId: scope.tenantId, deletedAt: null }) : [];
442
+ const itemById = new Map(items.map((item) => [item.id, item]));
443
+ const salesLines = await em.find(SalesQuoteLine, {
444
+ id: { $in: plan.restore.map((line) => line.quoteLineId) },
352
445
  organizationId: scope.organizationId,
353
446
  tenantId: scope.tenantId,
354
447
  deletedAt: null
355
448
  });
356
- for (const child of children) {
357
- child.deletedAt = now;
358
- const childSalesLine = await em.findOne(SalesQuoteLine, {
359
- id: child.quoteLineId,
360
- organizationId: scope.organizationId,
361
- tenantId: scope.tenantId
362
- });
363
- if (childSalesLine) childSalesLine.deletedAt = now;
364
- }
365
- const salesLine = await em.findOne(SalesQuoteLine, {
366
- id: lineId,
367
- organizationId: scope.organizationId,
368
- tenantId: scope.tenantId
449
+ const salesLineById = new Map(salesLines.map((line) => [line.id, line]));
450
+ const restores = plan.restore.map((line) => {
451
+ const itemId = itemIdOf(line);
452
+ const item = itemId ? itemById.get(itemId) : void 0;
453
+ if (!item || ["terminated", "expired", "superseded"].includes(item.status)) {
454
+ throw new QuotingError(409, "The subscription item is no longer active and cannot be restored");
455
+ }
456
+ return { line, item };
369
457
  });
370
- if (salesLine) salesLine.deletedAt = now;
371
- await em.flush();
458
+ const arcType = cpqConfig.quoteType;
459
+ await withAtomicFlush(em, [() => {
460
+ for (const { line, item } of restores) {
461
+ line.action = "modify";
462
+ line.configuration = buildArcMirrorConfiguration(item.configuration, item, arcType);
463
+ line.charges = pickArcMirrorCharges(item, cpqConfig.currencyCode);
464
+ line.quantity = item.quantity;
465
+ line.nrcTotal = String(item.nrcAmount ?? 0);
466
+ line.mrcTotal = String(item.mrcAmount);
467
+ line.isConfigured = true;
468
+ line.validationErrors = null;
469
+ const salesLine = salesLineById.get(line.quoteLineId);
470
+ if (salesLine) salesLine.quantity = String(item.quantity);
471
+ }
472
+ }]);
372
473
  return this.recalculateInternal(em, cpqConfig, salesQuote, scope, true);
373
474
  }
374
475
  // ─── Get Quote (read from DB, no recalculation) ────────────────
@@ -386,15 +487,42 @@ class DefaultCpqQuotingService {
386
487
  }
387
488
  // ─── Recalculate ───────────────────────────────────────────────
388
489
  async recalculate(quoteId, options, scope) {
389
- const em = options.save ? this.em.fork() : this.em;
390
- const cpqConfig = await this.loadCpqConfig(em, quoteId, scope);
490
+ if (options.save) {
491
+ return this.writeQuote(
492
+ quoteId,
493
+ scope,
494
+ (em, quote) => this.recalculateFromConfig(em, quote, scope, true),
495
+ void 0,
496
+ (quote) => {
497
+ if (TERMINAL_STATUSES.includes(quote.cpqStatus)) throw new QuotingError(409, `Cannot recalculate quote in '${quote.cpqStatus}' status`);
498
+ }
499
+ );
500
+ }
501
+ const cpqConfig = await this.loadCpqConfig(this.em, quoteId, scope);
502
+ return this.recalculateFromConfig(this.em, { ...cpqConfig }, scope, false);
503
+ }
504
+ async recalculateFromConfig(em, cpqConfig, scope, save) {
505
+ if (save && TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
506
+ throw new QuotingError(409, `Cannot recalculate quote in '${cpqConfig.cpqStatus}' status`);
507
+ }
391
508
  const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
392
- return this.recalculateInternal(em, cpqConfig, salesQuote, scope, options.save);
509
+ return this.recalculateInternal(em, cpqConfig, salesQuote, scope, save);
393
510
  }
394
511
  // ─── Clone Quote ───────────────────────────────────────────────
395
512
  async cloneQuote(quoteId, scope) {
396
- const em = this.em;
397
- const original = await this.loadCpqConfig(em, quoteId, scope);
513
+ const created = {};
514
+ const result = await this.writeQuote(
515
+ quoteId,
516
+ scope,
517
+ (em, quote) => this.cloneQuoteInTransaction(quoteId, scope, em, quote, created),
518
+ void 0,
519
+ void 0,
520
+ { pricingCause: "clone" }
521
+ );
522
+ if (created.quote) await emitQuoteWriteEvents(null, created.quote, { pricingCause: "clone" });
523
+ return result;
524
+ }
525
+ async cloneQuoteInTransaction(quoteId, scope, em, original, created = {}) {
398
526
  const originalQuote = await this.loadSalesQuote(em, original.quoteId, scope);
399
527
  const { number: quoteNumber } = await this.numberGenerator.generate({
400
528
  kind: "quote",
@@ -422,11 +550,13 @@ class DefaultCpqQuotingService {
422
550
  });
423
551
  em.persist(newSalesQuote);
424
552
  const newCpqConfig = em.create(CpqQuoteConfiguration, {
553
+ id: crypto.randomUUID(),
425
554
  organizationId: scope.organizationId,
426
555
  tenantId: scope.tenantId,
427
556
  quoteId: newSalesQuoteId,
428
557
  customerId: original.customerId,
429
558
  cpqStatus: "new",
559
+ pricingRevision: 1,
430
560
  version: original.version + 1,
431
561
  parentQuoteId: original.id,
432
562
  currencyCode: original.currencyCode,
@@ -435,6 +565,8 @@ class DefaultCpqQuotingService {
435
565
  pricingSummary: emptyPricingSummary(original.currencyCode)
436
566
  });
437
567
  em.persist(newCpqConfig);
568
+ created.quote = newCpqConfig;
569
+ await em.flush();
438
570
  const originalLines = await em.find(CpqQuoteLineConfiguration, {
439
571
  quoteConfigurationId: original.id,
440
572
  organizationId: scope.organizationId,
@@ -489,7 +621,8 @@ class DefaultCpqQuotingService {
489
621
  startDate: origLine.startDate,
490
622
  termMonths: origLine.termMonths,
491
623
  endDate: origLine.endDate,
492
- configuration: { ...origLine.configuration },
624
+ // A clone is a new quote: quote-only ARC pricing markers don't apply.
625
+ configuration: { ...stripQuoteOnlyArcMarkers(origLine.configuration) },
493
626
  quantity: origLine.quantity,
494
627
  charges: origLine.charges ? [...origLine.charges] : null,
495
628
  nrcTotal: origLine.nrcTotal,
@@ -516,12 +649,22 @@ class DefaultCpqQuotingService {
516
649
  }
517
650
  }
518
651
  await em.flush();
652
+ await cloneQuoteAdjustments(em, original.id, newCpqConfig, lineIdMap, scope);
653
+ await em.flush();
519
654
  return this.recalculateInternal(em, newCpqConfig, newSalesQuote, scope, true);
520
655
  }
521
656
  // ─── Transition Status ─────────────────────────────────────────
522
- async transitionStatus(quoteId, targetStatus, scope) {
523
- const em = this.em;
524
- const cpqConfig = await this.loadCpqConfig(em, quoteId, scope);
657
+ async transitionStatus(quoteId, targetStatus, scope, actorUserId = null) {
658
+ return this.writeQuote(
659
+ quoteId,
660
+ scope,
661
+ (em, quote) => this.transitionStatusInTransaction(quoteId, targetStatus, scope, em, quote),
662
+ void 0,
663
+ void 0,
664
+ { pricingCause: "recalculate", statusCause: "manual", actorUserId }
665
+ );
666
+ }
667
+ async transitionStatusInTransaction(quoteId, targetStatus, scope, em, cpqConfig) {
525
668
  const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
526
669
  const currentStatus = cpqConfig.cpqStatus;
527
670
  const allowed = ALLOWED_TRANSITIONS[currentStatus] ?? [];
@@ -531,7 +674,7 @@ class DefaultCpqQuotingService {
531
674
  const isSubmitForApproval = targetStatus === "in_approval" || targetStatus === "pre_approved" || targetStatus === "with_customer";
532
675
  const quoteType = cpqConfig.quoteType ?? "new";
533
676
  if (isSubmitForApproval && quoteType !== "new") {
534
- const validation = await this.validateArcQuote(quoteId, scope);
677
+ const validation = await this.validateArcQuote(quoteId, scope, em);
535
678
  if (!validation.ok) {
536
679
  throw new QuotingError(
537
680
  422,
@@ -544,8 +687,26 @@ class DefaultCpqQuotingService {
544
687
  const lines = await this.loadLineConfigs(em, cpqConfig.id, scope);
545
688
  return this.buildQuoteResult(em, cpqConfig, salesQuote, lines);
546
689
  }
690
+ async writeQuote(quoteId, scope, execute, expectedRevision, validate, events = { pricingCause: "recalculate" }) {
691
+ return withQuotePricingWrite(
692
+ this.em,
693
+ quoteId,
694
+ scope,
695
+ execute,
696
+ (status, message) => new QuotingError(status, message),
697
+ expectedRevision,
698
+ validate,
699
+ events
700
+ );
701
+ }
702
+ /** Called by adjustment commands that already own the quote lock/revision. */
703
+ async recalculateLockedQuote(em, quote, scope, save = true, adjustments) {
704
+ const salesQuote = await this.loadSalesQuote(em, quote.quoteId, scope);
705
+ const config = save ? quote : { ...quote };
706
+ return this.recalculateInternal(em, config, salesQuote, scope, save, adjustments);
707
+ }
547
708
  // ─── Internal: Recalculate ─────────────────────────────────────
548
- async recalculateInternal(em, cpqConfig, salesQuote, scope, save) {
709
+ async recalculateInternal(em, cpqConfig, salesQuote, scope, save, draftAdjustments) {
549
710
  const lineConfigs = await em.find(CpqQuoteLineConfiguration, {
550
711
  quoteConfigurationId: cpqConfig.id,
551
712
  organizationId: scope.organizationId,
@@ -574,7 +735,7 @@ class DefaultCpqQuotingService {
574
735
  mergedConfig.term_months = lineConfig.termMonths;
575
736
  }
576
737
  let validationErrors = [];
577
- if (lineConfig.offeringId) {
738
+ if (lineConfig.offeringId && !isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
578
739
  const validation = await this.validationService.validateOfferingConfiguration({
579
740
  offeringId: lineConfig.offeringId,
580
741
  configuration: lineConfig.configuration,
@@ -584,8 +745,11 @@ class DefaultCpqQuotingService {
584
745
  }
585
746
  const cfgRecord = lineConfig.configuration;
586
747
  const isArcMirrored = !!cfgRecord?._arcMirroredFromItemId;
748
+ const isFrozen = isFrozenAmendMirrorLine(cfgRecord, cpqConfig.quoteType);
587
749
  let charges;
588
- if (lineConfig.offeringId || lineConfig.productId) {
750
+ if (isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
751
+ charges = [];
752
+ } else if ((lineConfig.offeringId || lineConfig.productId) && !isFrozen) {
589
753
  charges = await this.pricingService.resolveProductCharges({
590
754
  offeringId: lineConfig.offeringId ?? void 0,
591
755
  productId: lineConfig.productId ?? void 0,
@@ -606,36 +770,74 @@ class DefaultCpqQuotingService {
606
770
  if (!isConfigured) allValid = false;
607
771
  allErrors.push(...validationErrors);
608
772
  computedLines.push({ lineConfig, charges, nrcTotal, mrcTotal, usageEstimates, usageTotalEstimated, validationErrors, isConfigured });
773
+ }
774
+ const adjustments = draftAdjustments ?? await loadQuoteAdjustments(em, cpqConfig.id, scope);
775
+ const adjusted = resolveManualAdjustments(computedLines.map(({ lineConfig, charges }) => ({
776
+ lineId: lineConfig.quoteLineId,
777
+ parentLineId: lineConfig.parentLineId ?? null,
778
+ enginePriced: !!(lineConfig.offeringId || lineConfig.productId),
779
+ mirrored: isArcMirrorLine(lineConfig.configuration, cpqConfig.quoteType),
780
+ charges
781
+ })), adjustments, cpqConfig.currencyCode);
782
+ const adjustedById = new Map(adjusted.lines.map((line) => [line.lineId, line]));
783
+ const computedIds = new Set(computedLines.map((line) => line.lineConfig.quoteLineId));
784
+ for (const computed of computedLines) {
785
+ const { lineConfig } = computed;
786
+ if (adjustments.length) computed.charges = adjustedById.get(lineConfig.quoteLineId).charges;
787
+ const adjustmentErrors = adjusted.errors.filter((error) => error.quoteLineId === lineConfig.quoteLineId);
788
+ if (adjustmentErrors.length) {
789
+ computed.isConfigured = false;
790
+ allValid = false;
791
+ const errors = adjustmentErrors.map((error) => ({ message: error.code, attribute: error.chargeCode, lineId: error.quoteLineId, adjustmentId: error.adjustmentId }));
792
+ computed.validationErrors.push(...errors);
793
+ allErrors.push(...errors);
794
+ }
795
+ Object.assign(computed, computeLineTotals(
796
+ computed.charges,
797
+ lineConfig.quantity,
798
+ lineConfig.usageEstimates?.map((entry) => ({ chargeCode: entry.chargeCode, estimatedQuantity: entry.estimatedQuantity }))
799
+ ));
800
+ const { charges, nrcTotal, mrcTotal, usageEstimates, usageTotalEstimated, validationErrors, isConfigured } = computed;
609
801
  if (save) {
610
- em.assign(lineConfig, {
611
- charges,
612
- nrcTotal: String(nrcTotal),
613
- mrcTotal: String(mrcTotal),
614
- usageEstimates: usageEstimates ?? null,
615
- usageTotalEstimated: usageTotalEstimated != null ? String(usageTotalEstimated) : null,
616
- validationErrors: validationErrors.length > 0 ? validationErrors : null,
617
- isConfigured
618
- });
619
- const salesLine = await em.findOne(SalesQuoteLine, {
620
- id: lineConfig.quoteLineId,
621
- organizationId: scope.organizationId,
622
- tenantId: scope.tenantId
623
- });
624
- if (salesLine) {
625
- const perInstanceNrc = charges.filter((c) => c.chargeType === "nrc").reduce((s, c) => s + (c.totalPrice ?? 0), 0);
626
- const perInstanceMrc = charges.filter((c) => c.chargeType === "mrc").reduce((s, c) => s + (c.totalPrice ?? 0), 0);
627
- const unitPriceNet = perInstanceNrc + perInstanceMrc;
628
- em.assign(salesLine, {
629
- quantity: String(lineConfig.quantity),
630
- unitPriceNet: String(unitPriceNet),
631
- unitPriceGross: String(unitPriceNet),
632
- totalNetAmount: String(unitPriceNet * lineConfig.quantity),
633
- totalGrossAmount: String(unitPriceNet * lineConfig.quantity),
634
- updatedAt: /* @__PURE__ */ new Date()
635
- });
636
- }
802
+ await withAtomicFlush(em, [
803
+ () => {
804
+ em.assign(lineConfig, {
805
+ charges,
806
+ nrcTotal: String(nrcTotal),
807
+ mrcTotal: String(mrcTotal),
808
+ usageEstimates: usageEstimates ?? null,
809
+ usageTotalEstimated: usageTotalEstimated != null ? String(usageTotalEstimated) : null,
810
+ validationErrors: validationErrors.length > 0 ? validationErrors : null,
811
+ isConfigured
812
+ });
813
+ },
814
+ async () => {
815
+ const salesLine = await em.findOne(SalesQuoteLine, {
816
+ id: lineConfig.quoteLineId,
817
+ organizationId: scope.organizationId,
818
+ tenantId: scope.tenantId
819
+ });
820
+ if (salesLine) {
821
+ const perInstanceNrc = charges.filter((charge) => charge.chargeType === "nrc").reduce((sum, charge) => sum + (charge.totalPrice ?? 0), 0);
822
+ const perInstanceMrc = charges.filter((charge) => charge.chargeType === "mrc").reduce((sum, charge) => sum + (charge.totalPrice ?? 0), 0);
823
+ const unitPriceNet = perInstanceNrc + perInstanceMrc;
824
+ em.assign(salesLine, {
825
+ quantity: String(lineConfig.quantity),
826
+ unitPriceNet: String(unitPriceNet),
827
+ unitPriceGross: String(unitPriceNet),
828
+ totalNetAmount: String(unitPriceNet * lineConfig.quantity),
829
+ totalGrossAmount: String(unitPriceNet * lineConfig.quantity),
830
+ updatedAt: /* @__PURE__ */ new Date()
831
+ });
832
+ }
833
+ }
834
+ ]);
637
835
  }
638
836
  }
837
+ for (const error of adjusted.errors.filter((entry) => !computedIds.has(entry.quoteLineId))) {
838
+ allValid = false;
839
+ allErrors.push({ message: error.code, attribute: error.chargeCode, lineId: error.quoteLineId, adjustmentId: error.adjustmentId });
840
+ }
639
841
  const relationshipErrors = await this.validationService.validateRelationships({
640
842
  lines: lineConfigs.map((l) => ({
641
843
  lineId: l.quoteLineId,
@@ -659,6 +861,13 @@ class DefaultCpqQuotingService {
659
861
  cpqConfig.cpqStatus = cpqStatus;
660
862
  }
661
863
  const pricingSummary = computePricingSummaryFromComputed(computedLines, cpqConfig.currencyCode);
864
+ Object.assign(pricingSummary, computeWaterfallSummary(computedLines.map(({ lineConfig, charges }) => ({
865
+ lineId: lineConfig.quoteLineId,
866
+ quantity: lineConfig.quantity,
867
+ termMonths: lineConfig.termMonths,
868
+ includeInDeal: !isArcMirrorLine(lineConfig.configuration, cpqConfig.quoteType),
869
+ charges
870
+ })), adjustments, cpqConfig.currencyCode));
662
871
  const validationResult = { valid: allValid, errors: allErrors };
663
872
  if (save) {
664
873
  cpqConfig.validationResult = validationResult;
@@ -674,7 +883,9 @@ class DefaultCpqQuotingService {
674
883
  });
675
884
  await em.flush();
676
885
  }
677
- return this.buildQuoteResultFromComputed(em, cpqConfig, salesQuote, computedLines, validationResult, pricingSummary);
886
+ const result = await this.buildQuoteResultFromComputed(em, cpqConfig, salesQuote, computedLines, validationResult, pricingSummary);
887
+ result.adjustmentErrors = adjusted.errors;
888
+ return result;
678
889
  }
679
890
  // ─── Helpers ───────────────────────────────────────────────────
680
891
  async loadCpqConfig(em, quoteId, scope) {
@@ -764,7 +975,7 @@ class DefaultCpqQuotingService {
764
975
  mrcTotal: Number(lc.mrcTotal),
765
976
  usageEstimates: lc.usageEstimates ?? null,
766
977
  usageTotalEstimated: lc.usageTotalEstimated != null ? Number(lc.usageTotalEstimated) : null,
767
- charges: lc.charges ?? [],
978
+ charges: (lc.charges ?? []).map(withChargeWaterfall),
768
979
  isConfigured: lc.isConfigured,
769
980
  validationErrors: lc.validationErrors ?? null,
770
981
  arcSource: pickArcSource(lc, arcSourceMap),
@@ -773,6 +984,16 @@ class DefaultCpqQuotingService {
773
984
  }
774
985
  const finalValidation = validationResult ?? cpqConfig.validationResult ?? { valid: true, errors: [] };
775
986
  const finalPricing = pricingSummary ?? computePricingSummary(lineConfigs, cpqConfig.currencyCode);
987
+ if (!pricingSummary) {
988
+ const adjustments = await loadQuoteAdjustments(em, cpqConfig.id, { organizationId: cpqConfig.organizationId, tenantId: cpqConfig.tenantId });
989
+ Object.assign(finalPricing, computeWaterfallSummary(lines.map((line) => ({
990
+ lineId: line.lineId,
991
+ quantity: line.quantity,
992
+ termMonths: line.termMonths,
993
+ includeInDeal: !isArcMirrorLine(line.configuration, cpqConfig.quoteType),
994
+ charges: line.charges
995
+ })), adjustments, cpqConfig.currencyCode));
996
+ }
776
997
  const { customerName, convertedOrderId } = await resolveQuoteSideData(em, cpqConfig);
777
998
  return {
778
999
  id: cpqConfig.id,
@@ -782,6 +1003,7 @@ class DefaultCpqQuotingService {
782
1003
  customerName,
783
1004
  cpqStatus: cpqConfig.cpqStatus,
784
1005
  version: cpqConfig.version,
1006
+ pricingRevision: cpqConfig.pricingRevision ?? 0,
785
1007
  parentQuoteId: cpqConfig.parentQuoteId ?? null,
786
1008
  currencyCode: cpqConfig.currencyCode,
787
1009
  convertedOrderId,
@@ -836,7 +1058,7 @@ class DefaultCpqQuotingService {
836
1058
  mrcTotal,
837
1059
  usageEstimates,
838
1060
  usageTotalEstimated,
839
- charges,
1061
+ charges: charges.map(withChargeWaterfall),
840
1062
  isConfigured,
841
1063
  validationErrors: validationErrors.length > 0 ? validationErrors : null,
842
1064
  arcSource: pickArcSource(lc, arcSourceMap),
@@ -852,6 +1074,7 @@ class DefaultCpqQuotingService {
852
1074
  customerName,
853
1075
  cpqStatus: cpqConfig.cpqStatus,
854
1076
  version: cpqConfig.version,
1077
+ pricingRevision: cpqConfig.pricingRevision ?? 0,
855
1078
  parentQuoteId: cpqConfig.parentQuoteId ?? null,
856
1079
  currencyCode: cpqConfig.currencyCode,
857
1080
  convertedOrderId,
@@ -941,6 +1164,7 @@ function aggregateAdjustmentTotals(allCharges) {
941
1164
  for (const charge of allCharges) {
942
1165
  if (!charge.adjustments) continue;
943
1166
  for (const adj of charge.adjustments) {
1167
+ if (adj.origin === "manual") continue;
944
1168
  if (adj.delta < 0) {
945
1169
  discountTotal += Math.abs(adj.delta) * (charge.quantity ?? 1);
946
1170
  } else if (adj.delta > 0) {
@@ -1021,8 +1245,12 @@ function computePricingSummaryFromComputed(computedLines, currencyCode) {
1021
1245
  currencyCode
1022
1246
  };
1023
1247
  }
1248
+ const LINE_EVENTS = { pricingCause: "line" };
1024
1249
  function emptyPricingSummary(currencyCode) {
1025
1250
  return {
1251
+ // A new quote starts with a real baseline hash, so its first priced line is
1252
+ // a pricing change; only pre-feature quotes have no stored hash.
1253
+ snapshotHash: computeWaterfallSummary([], [], currencyCode).snapshotHash,
1026
1254
  nrcTotal: 0,
1027
1255
  mrcTotal: 0,
1028
1256
  usageCharges: [],
@@ -1339,43 +1567,39 @@ DefaultCpqQuotingService.prototype.attachTargetSubscription = async function(quo
1339
1567
  };
1340
1568
  DefaultCpqQuotingService.prototype.detachTargetSubscription = async function(quoteId, targetId, scope) {
1341
1569
  const em = this.em;
1342
- const cpqConfig = await loadCpqConfigStrict(em, quoteId, scope);
1343
- ensureEditable(cpqConfig);
1344
- const target = await em.findOne(CpqQuoteTargetSubscription, {
1345
- id: targetId,
1346
- quoteId: cpqConfig.id,
1347
- organizationId: scope.organizationId,
1348
- tenantId: scope.tenantId,
1349
- deletedAt: null
1350
- });
1351
- if (!target) throw new QuotingError(404, "Target subscription not found");
1352
- const lines = await em.find(CpqQuoteLineConfiguration, {
1353
- quoteConfigurationId: cpqConfig.id,
1354
- targetSubscriptionId: target.subscriptionId,
1355
- organizationId: scope.organizationId,
1356
- tenantId: scope.tenantId,
1357
- deletedAt: null
1358
- });
1359
- const now = /* @__PURE__ */ new Date();
1360
- for (const line of lines) {
1361
- line.deletedAt = now;
1362
- const salesLine = await em.findOne(SalesQuoteLine, { id: line.quoteLineId });
1363
- if (salesLine) salesLine.deletedAt = now;
1364
- }
1365
- target.deletedAt = now;
1366
- if (target.quoteType === "renew") {
1367
- const remaining = await em.find(CpqQuoteTargetSubscription, {
1570
+ await withQuotePricingWrite(em, quoteId, scope, async (tx, cpqConfig) => {
1571
+ const target = await tx.findOne(CpqQuoteTargetSubscription, {
1572
+ id: targetId,
1368
1573
  quoteId: cpqConfig.id,
1369
1574
  organizationId: scope.organizationId,
1370
1575
  tenantId: scope.tenantId,
1371
- deletedAt: null,
1372
- id: { $ne: target.id }
1576
+ deletedAt: null
1577
+ });
1578
+ if (!target) throw new QuotingError(404, "Target subscription not found");
1579
+ const lines = await tx.find(CpqQuoteLineConfiguration, {
1580
+ quoteConfigurationId: cpqConfig.id,
1581
+ targetSubscriptionId: target.subscriptionId,
1582
+ organizationId: scope.organizationId,
1583
+ tenantId: scope.tenantId,
1584
+ deletedAt: null
1373
1585
  });
1374
- if (remaining.length === 1) {
1375
- remaining[0].mergeAction = "standalone";
1586
+ for (const line of lines) {
1587
+ if (line.deletedAt) continue;
1588
+ await removeQuoteLineTree(tx, cpqConfig.id, line, scope);
1376
1589
  }
1377
- }
1378
- await em.flush();
1590
+ const remaining = target.quoteType === "renew" ? await tx.find(CpqQuoteTargetSubscription, {
1591
+ quoteId: cpqConfig.id,
1592
+ organizationId: scope.organizationId,
1593
+ tenantId: scope.tenantId,
1594
+ deletedAt: null,
1595
+ id: { $ne: target.id }
1596
+ }) : [];
1597
+ await withAtomicFlush(tx, [() => {
1598
+ target.deletedAt = /* @__PURE__ */ new Date();
1599
+ if (remaining.length === 1) remaining[0].mergeAction = "standalone";
1600
+ }]);
1601
+ await this.recalculateLockedQuote(tx, cpqConfig, scope, true);
1602
+ }, (status, message) => new QuotingError(status, message), void 0, ensureEditable, { pricingCause: "line" });
1379
1603
  return { ok: true };
1380
1604
  };
1381
1605
  DefaultCpqQuotingService.prototype.updateTargetSubscription = async function(quoteId, targetId, patch, scope) {
@@ -1442,8 +1666,8 @@ DefaultCpqQuotingService.prototype.setMergeMeta = async function(quoteId, meta,
1442
1666
  const lines = await loadLines(em, cpqConfig.id, scope);
1443
1667
  return this.buildQuoteResult(em, cpqConfig, salesQuote, lines);
1444
1668
  };
1445
- DefaultCpqQuotingService.prototype.validateArcQuote = async function(quoteId, scope) {
1446
- const em = this.em;
1669
+ DefaultCpqQuotingService.prototype.validateArcQuote = async function(quoteId, scope, transactionEm) {
1670
+ const em = transactionEm ?? this.em;
1447
1671
  const cpqConfig = await loadCpqConfigStrict(em, quoteId, scope);
1448
1672
  const errors = [];
1449
1673
  if (cpqConfig.quoteType === "new") {
@@ -1669,6 +1893,7 @@ async function mapTargetView(em, target, scope) {
1669
1893
  }
1670
1894
  function emptyArcPricingSummary(currencyCode) {
1671
1895
  return {
1896
+ snapshotHash: computeWaterfallSummary([], [], currencyCode).snapshotHash,
1672
1897
  nrcTotal: 0,
1673
1898
  mrcTotal: 0,
1674
1899
  usageCharges: [],
@@ -1783,11 +2008,7 @@ async function mirrorTargetSubscriptionItems(em, cpqConfig, sub, salesQuote, quo
1783
2008
  startDate: null,
1784
2009
  termMonths: null,
1785
2010
  endDate: null,
1786
- configuration: {
1787
- ...item.configuration,
1788
- _arcMirroredFromItemId: item.id,
1789
- _arcMirroredName: item.name
1790
- },
2011
+ configuration: buildArcMirrorConfiguration(item.configuration, item, quoteType),
1791
2012
  quantity: item.quantity,
1792
2013
  charges: pickArcMirrorCharges(item, sub.currencyCode),
1793
2014
  nrcTotal: String(item.nrcAmount ?? 0),