@dainamite/cpq 0.5.0 → 0.6.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 (233) hide show
  1. package/CHANGELOG.md +48 -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/page.meta.js +1 -1
  34. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +1 -1
  35. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +22 -8
  36. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  37. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +89 -232
  38. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  39. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
  40. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
  41. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +205 -0
  42. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
  43. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
  44. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
  45. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
  46. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
  47. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
  48. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
  49. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
  50. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
  51. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +144 -221
  52. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
  53. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
  54. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
  55. package/dist/modules/cpq/ce.js +8 -0
  56. package/dist/modules/cpq/ce.js.map +2 -2
  57. package/dist/modules/cpq/cli.js +34 -1
  58. package/dist/modules/cpq/cli.js.map +2 -2
  59. package/dist/modules/cpq/commands/price-adjustments.js +107 -0
  60. package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
  61. package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
  62. package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
  63. package/dist/modules/cpq/components/ChargeBreakdownTable.js +154 -0
  64. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
  65. package/dist/modules/cpq/components/PriceWaterfall.js +66 -0
  66. package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
  67. package/dist/modules/cpq/components/PricingSummaryCard.js +117 -0
  68. package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
  69. package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
  70. package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
  71. package/dist/modules/cpq/data/entities.js +97 -0
  72. package/dist/modules/cpq/data/entities.js.map +2 -2
  73. package/dist/modules/cpq/data/price-adjustments.js +70 -0
  74. package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
  75. package/dist/modules/cpq/data/validators.js +4 -0
  76. package/dist/modules/cpq/data/validators.js.map +2 -2
  77. package/dist/modules/cpq/di.js +6 -0
  78. package/dist/modules/cpq/di.js.map +2 -2
  79. package/dist/modules/cpq/events.js +47 -0
  80. package/dist/modules/cpq/events.js.map +2 -2
  81. package/dist/modules/cpq/index.js +1 -1
  82. package/dist/modules/cpq/index.js.map +2 -2
  83. package/dist/modules/cpq/lib/auditActor.js +10 -0
  84. package/dist/modules/cpq/lib/auditActor.js.map +7 -0
  85. package/dist/modules/cpq/lib/formatMoney.js +7 -0
  86. package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
  87. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
  88. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
  89. package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
  90. package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
  91. package/dist/modules/cpq/lib/quotePermissions.js +13 -0
  92. package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
  93. package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
  94. package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
  95. package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
  96. package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
  97. package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
  98. package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
  99. package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
  100. package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
  101. package/dist/modules/cpq/services/cpqOrderService.js +25 -28
  102. package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
  103. package/dist/modules/cpq/services/cpqPricingService.js +7 -0
  104. package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
  105. package/dist/modules/cpq/services/cpqQuotingService.js +330 -109
  106. package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
  107. package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
  108. package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
  109. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
  110. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
  111. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
  112. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
  113. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
  114. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
  115. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
  116. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
  117. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
  118. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
  119. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
  120. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
  121. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
  122. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
  123. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
  124. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
  125. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
  126. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
  127. package/dist/modules/cpq/services/types.js.map +2 -2
  128. package/dist/modules/cpq/setup.js +6 -0
  129. package/dist/modules/cpq/setup.js.map +2 -2
  130. package/package.json +1 -1
  131. package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
  132. package/src/modules/cpq/acl.ts +1 -0
  133. package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
  134. package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
  135. package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
  136. package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
  137. package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
  138. package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
  139. package/src/modules/cpq/api/orders/[orderId]/route.ts +18 -0
  140. package/src/modules/cpq/api/orders/route.ts +51 -1
  141. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
  142. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +48 -1
  143. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
  144. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
  145. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
  146. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
  147. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
  148. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +40 -1
  149. package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
  150. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
  151. package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
  152. package/src/modules/cpq/api/quotes/price/route.ts +28 -2
  153. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +1 -1
  154. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
  155. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +60 -269
  156. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
  157. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
  158. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
  159. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
  160. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
  161. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
  162. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
  163. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +174 -263
  164. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
  165. package/src/modules/cpq/ce.ts +8 -0
  166. package/src/modules/cpq/cli.ts +48 -1
  167. package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
  168. package/src/modules/cpq/commands/price-adjustments.ts +90 -0
  169. package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
  170. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +201 -0
  171. package/src/modules/cpq/components/PriceWaterfall.tsx +94 -0
  172. package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
  173. package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
  174. package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
  175. package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
  176. package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
  177. package/src/modules/cpq/data/entities.ts +83 -0
  178. package/src/modules/cpq/data/price-adjustments.ts +67 -0
  179. package/src/modules/cpq/data/validators.ts +3 -0
  180. package/src/modules/cpq/di.ts +6 -0
  181. package/src/modules/cpq/events.ts +53 -2
  182. package/src/modules/cpq/index.ts +1 -1
  183. package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
  184. package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
  185. package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
  186. package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
  187. package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
  188. package/src/modules/cpq/lib/auditActor.ts +13 -0
  189. package/src/modules/cpq/lib/formatMoney.ts +4 -0
  190. package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
  191. package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
  192. package/src/modules/cpq/lib/quotePermissions.ts +22 -0
  193. package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
  194. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
  195. package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
  196. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
  197. package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
  198. package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
  199. package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
  200. package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
  201. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
  202. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
  203. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
  204. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
  205. package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
  206. package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
  207. package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
  208. package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
  209. package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
  210. package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
  211. package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
  212. package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
  213. package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
  214. package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
  215. package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
  216. package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
  217. package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
  218. package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
  219. package/src/modules/cpq/services/cpqOrderService.ts +22 -28
  220. package/src/modules/cpq/services/cpqPricingService.ts +8 -0
  221. package/src/modules/cpq/services/cpqQuotingService.ts +374 -125
  222. package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
  223. package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
  224. package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
  225. package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
  226. package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
  227. package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
  228. package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
  229. package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
  230. package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
  231. package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
  232. package/src/modules/cpq/services/types.ts +34 -1
  233. package/src/modules/cpq/setup.ts +7 -1
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Frozen Amend mirror lines.
3
+ *
4
+ * An Amend quote mirrors every live subscription item as a `modify` line.
5
+ * Engine-priced mirror lines used to be re-priced at today's catalog price on
6
+ * every recalculation, and activation wrote that price back to the
7
+ * subscription item — so amending one item silently moved every other item's
8
+ * price. A mirror line on an Amend quote is therefore created **frozen**: it
9
+ * keeps the source item's stored charges until a pricing input of that line
10
+ * changes, at which point it is re-priced as before.
11
+ *
12
+ * The flag lives in the line configuration jsonb next to the existing
13
+ * `_arcMirrored*` markers, so no schema change is needed.
14
+ */
15
+
16
+ export const ARC_PRICE_FROZEN_KEY = '_arcPriceFrozen'
17
+
18
+ /**
19
+ * Set when the server unfreezes an Amend mirror line because a pricing input
20
+ * changed — drives the "re-priced at the current catalog price" notice (an
21
+ * explicit marker, so Amend quotes created before freezing existed don't
22
+ * show it).
23
+ */
24
+ export const ARC_REPRICED_KEY = '_arcRepriced'
25
+
26
+ /** Markers that describe quote-time pricing state and never leave the quote. */
27
+ const QUOTE_ONLY_ARC_KEYS = [ARC_PRICE_FROZEN_KEY, ARC_REPRICED_KEY] as const
28
+
29
+ const ARC_MARKER_PREFIX = '_arc'
30
+
31
+ export type ArcMirrorQuoteType = 'amend' | 'cancel' | 'renew'
32
+
33
+ export function isArcPriceFrozen(configuration: Record<string, unknown> | null | undefined): boolean {
34
+ return configuration?.[ARC_PRICE_FROZEN_KEY] === true
35
+ }
36
+
37
+ /**
38
+ * Whether recalculation must keep a line's stored charges. Only server-made
39
+ * mirror lines on Amend quotes qualify — the flag alone is never trusted
40
+ * (client input is stripped of `_arc*` keys, and a clone of an Amend quote
41
+ * is a new quote).
42
+ */
43
+ export function isArcMirrorLine(configuration: Record<string, unknown> | null | undefined, quoteType: string | null | undefined): boolean {
44
+ return (quoteType === 'amend' || quoteType === 'renew' || quoteType === 'cancel') && typeof configuration?._arcMirroredFromItemId === 'string'
45
+ }
46
+
47
+ export function isFrozenAmendMirrorLine(
48
+ configuration: Record<string, unknown> | null | undefined,
49
+ quoteType: string | null | undefined,
50
+ ): boolean {
51
+ return (
52
+ quoteType === 'amend' &&
53
+ typeof configuration?._arcMirroredFromItemId === 'string' &&
54
+ isArcPriceFrozen(configuration)
55
+ )
56
+ }
57
+
58
+ /**
59
+ * Configuration for a mirror line: the item's configuration plus the ARC
60
+ * markers. Only Amend lines are frozen — Renew re-prices by product decision
61
+ * and Cancel lines are not priced.
62
+ */
63
+ export function buildArcMirrorConfiguration(
64
+ itemConfiguration: Record<string, unknown> | null | undefined,
65
+ item: { id: string; name: string },
66
+ quoteType: ArcMirrorQuoteType,
67
+ ): Record<string, unknown> {
68
+ return {
69
+ // An item's configuration can carry markers from an earlier ARC quote
70
+ // (activation copies line configuration onto the item); never inherit them.
71
+ ...stripArcMarkers(itemConfiguration),
72
+ _arcMirroredFromItemId: item.id,
73
+ _arcMirroredName: item.name,
74
+ ...(quoteType === 'amend' ? { [ARC_PRICE_FROZEN_KEY]: true } : {}),
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Line configuration as it may leave the quote (order lines, subscription
80
+ * items, clones): quote-only pricing markers are dropped; the descriptive
81
+ * `_arcMirrored*` markers stay.
82
+ */
83
+ export function stripQuoteOnlyArcMarkers(
84
+ configuration: Record<string, unknown> | null | undefined,
85
+ ): Record<string, unknown> | null | undefined {
86
+ if (!configuration || !QUOTE_ONLY_ARC_KEYS.some((key) => key in configuration)) return configuration
87
+ const out: Record<string, unknown> = {}
88
+ for (const [key, value] of Object.entries(configuration)) {
89
+ if (!(QUOTE_ONLY_ARC_KEYS as readonly string[]).includes(key)) out[key] = value
90
+ }
91
+ return out
92
+ }
93
+
94
+ /**
95
+ * Configuration without any `_arc*` marker. ARC markers are server-owned:
96
+ * client-supplied configuration goes through this before it is stored.
97
+ */
98
+ export function stripArcMarkers(configuration: Record<string, unknown> | null | undefined): Record<string, unknown> {
99
+ const out: Record<string, unknown> = {}
100
+ for (const [key, value] of Object.entries(configuration ?? {})) {
101
+ if (!key.startsWith(ARC_MARKER_PREFIX)) out[key] = value
102
+ }
103
+ return out
104
+ }
105
+
106
+ function canonical(value: unknown): string {
107
+ if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
108
+ if (value && typeof value === 'object') {
109
+ const entries = Object.keys(value as Record<string, unknown>)
110
+ .sort()
111
+ .map((key) => `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key])}`)
112
+ return `{${entries.join(',')}}`
113
+ }
114
+ return JSON.stringify(value ?? null)
115
+ }
116
+
117
+ function isoDay(value: Date | string | null | undefined): string | null {
118
+ if (!value) return null
119
+ if (value instanceof Date) return value.toISOString().slice(0, 10)
120
+ return String(value).slice(0, 10)
121
+ }
122
+
123
+ export type ArcLinePricingState = {
124
+ configuration: Record<string, unknown> | null | undefined
125
+ quantity: number
126
+ termMonths?: number | null
127
+ startDate?: Date | string | null
128
+ endDate?: Date | string | null
129
+ usageEstimates?: Array<{ chargeCode: string; estimatedQuantity: number }> | null
130
+ }
131
+
132
+ export type ArcLinePricingUpdate = {
133
+ configuration: Record<string, unknown>
134
+ quantity?: number
135
+ termMonths?: number
136
+ startDate?: string
137
+ endDate?: string
138
+ usageEstimates?: Array<{ chargeCode: string; estimatedQuantity: number }>
139
+ }
140
+
141
+ /**
142
+ * Whether an update changes any input the pricing engine reads. The quote UI
143
+ * always resubmits the full configuration, so "a field was sent" is not a
144
+ * change — values are compared (ARC markers ignored, key order ignored).
145
+ * Fields the update omits keep their stored value.
146
+ */
147
+ export function arcPricingInputsChanged(before: ArcLinePricingState, update: ArcLinePricingUpdate): boolean {
148
+ if (canonical(stripArcMarkers(before.configuration)) !== canonical(stripArcMarkers(update.configuration))) {
149
+ return true
150
+ }
151
+ if (update.quantity !== undefined && update.quantity !== before.quantity) return true
152
+ if (update.termMonths !== undefined && update.termMonths !== (before.termMonths ?? undefined)) return true
153
+ if (update.startDate !== undefined && isoDay(update.startDate) !== isoDay(before.startDate)) return true
154
+ if (update.endDate !== undefined && isoDay(update.endDate) !== isoDay(before.endDate)) return true
155
+ if (update.usageEstimates !== undefined) {
156
+ const normalize = (list: Array<{ chargeCode: string; estimatedQuantity: number }> | null | undefined) =>
157
+ canonical(
158
+ [...(list ?? [])]
159
+ .map((e) => ({ chargeCode: e.chargeCode, estimatedQuantity: e.estimatedQuantity }))
160
+ .sort((a, b) => a.chargeCode.localeCompare(b.chargeCode)),
161
+ )
162
+ if (normalize(update.usageEstimates) !== normalize(before.usageEstimates)) return true
163
+ }
164
+ return false
165
+ }
@@ -1,3 +1,5 @@
1
+ import { storedAdjustmentFailure } from './priceAdjustments/storedAdjustmentFailure'
2
+ import { withQuotePricingWrite } from './priceAdjustments/quotePricingWrite'
1
3
  import type { EntityManager } from '@mikro-orm/postgresql'
2
4
  import { SalesOrder, SalesOrderLine } from '@open-mercato/core/modules/sales/data/entities'
3
5
  import type { SalesDocumentNumberGenerator } from '@open-mercato/core/modules/sales/services/salesDocumentNumberGenerator'
@@ -25,6 +27,8 @@ import {
25
27
  type TenantScope,
26
28
  } from './types'
27
29
  import { emitCpqEvent } from '../events'
30
+ import { stripQuoteOnlyArcMarkers } from './arcPriceFreeze'
31
+ import { summarizeOrderPricing, type OrderWaterfallSummary } from './orderPricingSummary'
28
32
 
29
33
  // ─── Types ──────────────────────────────────────────────────────
30
34
 
@@ -89,11 +93,12 @@ interface OrderResult {
89
93
  * order detail page to render the AMEND / RENEW / CANCEL badge. */
90
94
  quoteType: string
91
95
  currencyCode: string
96
+ /** NRC / MRC totals plus the source quote's list / discount / deal-value figures. */
92
97
  pricingSummary: {
93
98
  nrcTotal: number
94
99
  mrcTotal: number
95
100
  currencyCode: string
96
- }
101
+ } & OrderWaterfallSummary
97
102
  activatedAt: string | null
98
103
  createdAt: string
99
104
  lines: OrderLineResult[]
@@ -129,29 +134,12 @@ export class DefaultCpqOrderService {
129
134
  // ─── Convert Quote to Order ─────────────────────────────────
130
135
 
131
136
  async convertQuoteToOrder(quoteId: string, scope: TenantScope): Promise<OrderResult> {
132
- const em = this.em
133
-
134
- const cpqConfig = await em.findOne(CpqQuoteConfiguration, {
135
- id: quoteId,
136
- organizationId: scope.organizationId,
137
- tenantId: scope.tenantId,
138
- deletedAt: null,
139
- })
140
- if (!cpqConfig) {
141
- const byQuoteId = await em.findOne(CpqQuoteConfiguration, {
142
- quoteId,
143
- organizationId: scope.organizationId,
144
- tenantId: scope.tenantId,
145
- deletedAt: null,
146
- })
147
- if (!byQuoteId) throw new OrderError(404, 'Quote not found')
148
- return this.convertFromConfig(byQuoteId, scope)
149
- }
150
- return this.convertFromConfig(cpqConfig, scope)
137
+ return withQuotePricingWrite(this.em, quoteId, scope,
138
+ (em, quote) => this.convertFromConfig(quote, scope, em),
139
+ (status, message) => new OrderError(status, message))
151
140
  }
152
141
 
153
- private async convertFromConfig(cpqConfig: CpqQuoteConfiguration, scope: TenantScope): Promise<OrderResult> {
154
- const em = this.em
142
+ private async convertFromConfig(cpqConfig: CpqQuoteConfiguration, scope: TenantScope, em: EntityManager): Promise<OrderResult> {
155
143
 
156
144
  const blockedStatuses = ['cancelled', 'rejected']
157
145
  if (blockedStatuses.includes(cpqConfig.cpqStatus)) {
@@ -186,6 +174,9 @@ export class DefaultCpqOrderService {
186
174
  throw new OrderError(422, 'Cannot convert a quote with no line items')
187
175
  }
188
176
 
177
+ const invalidAdjustment = storedAdjustmentFailure(cpqConfig, quoteLines)
178
+ if (invalidAdjustment) throw new OrderError(422, 'ADJUSTMENT_INVALID', invalidAdjustment)
179
+
189
180
  const { number: orderNumber } = await this.numberGenerator.generate({
190
181
  kind: 'order',
191
182
  organizationId: scope.organizationId,
@@ -309,7 +300,8 @@ export class DefaultCpqOrderService {
309
300
  startDate: ql.startDate,
310
301
  termMonths: ql.termMonths ?? null,
311
302
  endDate: ql.endDate,
312
- configuration: { ...ql.configuration, offeringName },
303
+ // Quote-only ARC pricing markers stay on the quote.
304
+ configuration: { ...stripQuoteOnlyArcMarkers(ql.configuration), offeringName },
313
305
  quantity: ql.quantity,
314
306
  charges: ql.charges ? [...ql.charges] : null,
315
307
  nrcTotal: ql.nrcTotal,
@@ -641,7 +633,7 @@ export class DefaultCpqOrderService {
641
633
  offeringId: line.offeringId ?? undefined,
642
634
  specId: line.specId ?? undefined,
643
635
  name: this.getLineName(line),
644
- configuration: line.configuration ?? {},
636
+ configuration: stripQuoteOnlyArcMarkers(line.configuration) ?? {},
645
637
  charges: (line.charges ?? []) as Array<Record<string, unknown>>,
646
638
  mrcAmount: Number(line.mrcTotal),
647
639
  nrcAmount: Number(line.nrcTotal),
@@ -711,7 +703,7 @@ export class DefaultCpqOrderService {
711
703
  purchasePrice: Number(line.nrcTotal),
712
704
  quantity: line.quantity,
713
705
  purchaseDate: new Date().toISOString().split('T')[0],
714
- configuration: line.configuration ?? {},
706
+ configuration: stripQuoteOnlyArcMarkers(line.configuration) ?? {},
715
707
  charges: (line.charges ?? []) as Array<Record<string, unknown>>,
716
708
  sourceOrderId: orderConfig.id,
717
709
  sourceQuoteLineId: line.sourceQuoteLineId ?? undefined,
@@ -976,7 +968,7 @@ export class DefaultCpqOrderService {
976
968
  action: lc.action,
977
969
  parentLineId: lc.parentLineId ?? null,
978
970
  quantity: lc.quantity,
979
- configuration: lc.configuration ?? {},
971
+ configuration: stripQuoteOnlyArcMarkers(lc.configuration) ?? {},
980
972
  startDate: lc.startDate?.toISOString() ?? null,
981
973
  termMonths: lc.termMonths ?? null,
982
974
  endDate: lc.endDate?.toISOString() ?? null,
@@ -1018,6 +1010,7 @@ export class DefaultCpqOrderService {
1018
1010
  nrcTotal: Number((pricing as Record<string, unknown>).nrcTotal ?? 0),
1019
1011
  mrcTotal: Number((pricing as Record<string, unknown>).mrcTotal ?? 0),
1020
1012
  currencyCode: orderConfig.currencyCode,
1013
+ ...summarizeOrderPricing(lines, quoteType, orderConfig.currencyCode),
1021
1014
  },
1022
1015
  activatedAt: orderConfig.activatedAt?.toISOString() ?? null,
1023
1016
  createdAt: orderConfig.createdAt.toISOString(),
@@ -1055,6 +1048,7 @@ export class OrderError extends Error {
1055
1048
  constructor(
1056
1049
  public status: number,
1057
1050
  message: string,
1051
+ public details?: Record<string, unknown>,
1058
1052
  ) {
1059
1053
  super(message)
1060
1054
  this.name = 'OrderError'
@@ -1082,7 +1076,7 @@ function buildArcLineChange(
1082
1076
  offeringId: line.offeringId ?? undefined,
1083
1077
  specId: line.specId ?? undefined,
1084
1078
  name: resolveLineName(line, specMap),
1085
- configuration: line.configuration ?? {},
1079
+ configuration: stripQuoteOnlyArcMarkers(line.configuration) ?? {},
1086
1080
  charges: (line.charges ?? null) as Array<Record<string, unknown>> | undefined,
1087
1081
  mrcAmount: Number(line.mrcTotal),
1088
1082
  nrcAmount: Number(line.nrcTotal),
@@ -1109,7 +1103,7 @@ function buildArcLineChange(
1109
1103
  action,
1110
1104
  sourceSubscriptionItemId: line.sourceSubscriptionItemId ?? null,
1111
1105
  modifyPatch: {
1112
- configuration: line.configuration ?? undefined,
1106
+ configuration: stripQuoteOnlyArcMarkers(line.configuration) ?? undefined,
1113
1107
  charges: (line.charges ?? undefined) as Array<Record<string, unknown>> | undefined,
1114
1108
  mrcAmount: Number(line.mrcTotal),
1115
1109
  nrcAmount: Number(line.nrcTotal),
@@ -119,6 +119,8 @@ export class DefaultCpqPricingService {
119
119
  if (currencyCode) {
120
120
  resolved.currencyCode = currencyCode
121
121
  }
122
+ resolved.listUnitPrice = resolved.unitPrice
123
+ resolved.listTotalPrice = resolved.totalPrice
122
124
  resolved.adjustments = []
123
125
  results.push(resolved)
124
126
  }
@@ -133,6 +135,11 @@ export class DefaultCpqPricingService {
133
135
  this.applyPriceRules(results, rules, configuration)
134
136
  }
135
137
 
138
+ for (const resolved of results) {
139
+ resolved.standardUnitPrice = resolved.unitPrice
140
+ resolved.standardTotalPrice = resolved.totalPrice
141
+ }
142
+
136
143
  return results
137
144
  }
138
145
 
@@ -219,6 +226,7 @@ export class DefaultCpqPricingService {
219
226
  }
220
227
 
221
228
  const adjustment: RuleAdjustment = {
229
+ origin: 'rule',
222
230
  ruleCode: rule.code,
223
231
  ruleName: rule.name,
224
232
  ruleType: rule.ruleType,