@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # @dainamite/cpq
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d86651d: CPQ UI in English, Spanish, German and Polish.
8
+
9
+ - **Dictionaries.** New `i18n/{en,es,de,pl}.json` (1218 keys). CPQ previously shipped no dictionaries, so every screen rendered English in every locale.
10
+ - **Every UI string is translatable.** Hard-coded labels in quotes, orders, the quote wizard, specifications, offerings, inventory, price rules, pricing tables, wizard definitions, shared components and the injected widgets now go through `t()`. English text is unchanged.
11
+ - **Own generic labels.** `cpq.common.*` replaces borrowed `common.*` keys that only some unrelated modules shipped (`common.inactive` existed nowhere).
12
+ - **Sales tabs translated.** The quote / order tabs on sales documents use i18n keys as `groupLabel`. The customer "Inventory" tab stays English (legacy customer pages render `groupLabel` verbatim).
13
+ - **Row-action messages are noun-free** (`Delete selected items ({count})?`), because a spliced entity noun can't be grammatical in ES / DE / PL. `useCpqRowActions({ entityName })` is now optional and deprecated.
14
+ - **API change for direct importers:** `parsePricingTableXlsx` / `commitImportDiff` take a required `t` argument, and `StatusPath`'s `ariaLabel` is optional.
15
+ - **Guardrails:** tests fail on a missing or orphaned key, a dropped `{placeholder}`, or a new hard-coded UI string.
16
+
17
+ No migration. After upgrading run `yarn generate` so the app picks up the dictionaries.
18
+
19
+ ## 0.6.0
20
+
21
+ ### Minor Changes
22
+
23
+ - adcb50f: Negotiated prices on quotes (manual price adjustments) with a price waterfall.
24
+
25
+ - New table `cpq_quote_price_adjustments` and column
26
+ `cpq_quote_configurations.pricing_revision` (migration
27
+ `Migration20261110213052`). **Upgrade steps for existing tenants (required):**
28
+ `yarn mercato db migrate`, then `yarn mercato cpq seed-price-adjustments`
29
+ (rollout toggle + reason dictionary) and `yarn mercato auth sync-role-acls`
30
+ (`cpq.quotes.adjust_price` grant). Without them writes return `403` and the
31
+ UI hides the actions. New tenants get all three at initialization.
32
+ - Reps with `cpq.quotes.manage` + `cpq.quotes.adjust_price` can override a
33
+ charge's unit price, take an amount off a charge or give a percentage on a
34
+ charge or line (`/api/cpq/quotes/{quoteId}/price-adjustments`, plus a
35
+ no-write `preview`). Adjustments are permanent, never exceed the standard
36
+ price, never compound, and lock once the quote leaves New / Incomplete /
37
+ Ready. Rollout toggle `cpq.manual_price_adjustments` (seeded on) and reason
38
+ dictionary `cpq.price_adjustment_reason`.
39
+ - Charges gain additive `listUnitPrice`, `listTotalPrice`, `standardUnitPrice`,
40
+ `standardTotalPrice` and `adjustmentErrors`; adjustment entries gain
41
+ `origin: 'rule' | 'manual'`. `pricingSummary` gains list / standard totals,
42
+ rule and manual discount totals, deal value, discount depth and
43
+ `snapshotHash`; `QuoteResult` gains `pricingRevision`. `discountTotal` is
44
+ unchanged.
45
+ - New events `cpq.quote.pricing_changed` and `cpq.quote.status_changed`
46
+ (after commit, with the pricing snapshot) for project approval workflows.
47
+ - Order conversion copies stored prices and returns `422 ADJUSTMENT_INVALID`
48
+ when a stored adjustment is invalid. `POST /api/cpq/quotes/price` accepts
49
+ `includeWaterfall` and never applies negotiated prices.
50
+ - Quote, order and subscription pages show List / Offer prices and the
51
+ waterfall; the quote page adds the adjustment dialog and panel.
52
+
53
+ - 8d57b1b: Harden quote pricing ahead of manual price adjustments.
54
+
55
+ - `POST /api/cpq/quotes/{quoteId}/recalculate` now requires `cpq.quotes.manage`
56
+ (was `cpq.quotes.view`), and a saved recalculation (`save: true`) on an
57
+ accepted, rejected or cancelled quote returns `409`. Integrations that
58
+ recalculate with a view-only API key must be granted `cpq.quotes.manage`.
59
+ - Amend quotes no longer re-price items the operator does not touch: mirror
60
+ lines keep the subscription's stored price until their configuration,
61
+ quantity, term, dates or usage estimates change.
62
+ - Status transitions, line add / update / remove, saved recalculations and
63
+ quote-to-order conversion run every registered mutation guard
64
+ (resource kinds `cpq.quote` and `cpq.quote_line`), so projects can enforce
65
+ approvals. No behaviour change when no guard is registered.
66
+
3
67
  ## 0.5.0
4
68
 
5
69
  ### Minor Changes
package/MIGRATION.md CHANGED
@@ -166,6 +166,80 @@ the `@dainamite/cpq` package, per
166
166
 
167
167
  ## Schema / API changelog (track breaking changes here)
168
168
 
169
+ ### 2026-11-10 — Manual price adjustments and price waterfall (price adjustments, Phase 1)
170
+
171
+ Spec: `.ai/specs/2026-11-10-cpq-manual-price-adjustments.md`. Manual:
172
+ `manuals/cpq-price-adjustments.md`.
173
+
174
+ **Schema (additive).** Migration `Migration20261110213052` adds
175
+ `cpq_quote_price_adjustments` (tenant-scoped, soft-deleted, FK-string ids only)
176
+ and `cpq_quote_configurations.pricing_revision int default 0`. Rollback drops
177
+ both. No data migration: existing quotes keep their stored charges; list /
178
+ standard prices are derived for responses until the next saved recalculation.
179
+
180
+ **Upgrade steps for existing tenants (required).** The toggle, the reason
181
+ dictionary and the role grant are created by `setup.ts` at tenant
182
+ initialization only. After `yarn mercato db migrate`, run
183
+ `yarn mercato cpq seed-price-adjustments` (idempotent; `--tenant` / `--org`
184
+ to target one) and `yarn mercato auth sync-role-acls`. Skipping them leaves the
185
+ feature fail-closed: writes `403`, actions hidden, and every reason
186
+ `REASON_NOT_FOUND` if the toggle is switched on by hand.
187
+
188
+ **Write serialization.** Every pricing write (line add / update / remove, ARC
189
+ target detach, saved recalculation, clone, status change, adjustment CRUD,
190
+ order conversion) runs in
191
+ a transaction that locks the quote row (`SELECT … FOR UPDATE`) and bumps
192
+ `pricing_revision`. Adjustment writes accept `expectedRevision` and return
193
+ `409 STALE_REVISION` on mismatch.
194
+
195
+ **Response shapes (additive).** See the changeset. `discountTotal` keeps its
196
+ old meaning (rule discounts only) for existing consumers; use
197
+ `ruleDiscountTotal` / `manualDiscountTotal` for the split.
198
+
199
+ **Events.** `cpq.quote.pricing_changed` fires after a committed save whose
200
+ `snapshotHash` changed; `cpq.quote.status_changed` after every committed status
201
+ change (`cause: manual | auto`). Quotes saved before this release have no
202
+ stored hash: their first save records a baseline silently. New and ARC quotes
203
+ start with an empty-quote hash, so their first priced line is a signal.
204
+ `transitionStatus(quoteId, status, scope, actorUserId?)` gained an optional
205
+ actor argument.
206
+
207
+ **Conversion.** A quote whose stored waterfall contains an invalid manual
208
+ adjustment can no longer be converted (`422 ADJUSTMENT_INVALID` with
209
+ `lineId` / `code`).
210
+
211
+ ### 2026-11-10 — Quote pricing integrity (price adjustments, Phase 1)
212
+
213
+ Spec: `.ai/specs/2026-11-10-cpq-quote-pricing-integrity.md`. No schema change.
214
+
215
+ **Recalculate needs `cpq.quotes.manage` (breaking for view-only callers).**
216
+ `POST /api/cpq/quotes/{quoteId}/recalculate` required only `cpq.quotes.view`,
217
+ so a read-only user or API key could persist re-priced quotes. It now requires
218
+ `cpq.quotes.manage`. A saved recalculation (`save: true`) on an `accepted`,
219
+ `rejected` or `cancelled` quote returns `409` — an accepted quote's prices are
220
+ the basis for order conversion. Dry runs (`save: false`) on terminal quotes
221
+ still work for managers.
222
+
223
+ **Amend mirror lines are frozen (behaviour change).** Engine-priced mirror
224
+ lines on Amend quotes were re-priced at today's catalog price on every
225
+ recalculation, and activation wrote that price back to the subscription item.
226
+ They now carry `_arcPriceFrozen: true` in the line `configuration` and keep the
227
+ source item's stored charges until a pricing input of the line changes
228
+ (configuration — ARC markers and key order ignored — quantity, `termMonths`,
229
+ `startDate`, `endDate`, `usageEstimates`); then the marker is dropped and the
230
+ line is re-priced as before. Renew and Cancel quotes are unchanged. Amend
231
+ quotes created before this release keep the old behaviour. The marker never
232
+ reaches subscription items (stripped at activation) and mirror lines ignore any
233
+ stale `_arc*` keys on an item's configuration.
234
+
235
+ **Mutation guards on quote writes.** The status, items, recalculate and
236
+ order-conversion routes now call `runRouteMutationGuards`
237
+ (`@open-mercato/shared/lib/crud/route-mutation-guard`) with resource kinds
238
+ `cpq.quote` (actions `transition_status`, `recalculate`, `convert_to_order`)
239
+ and `cpq.quote_line` (create / update / delete). A guard can block the write or
240
+ modify the line payload; after-success hooks run once the write commits.
241
+ These routes now export `openApi`.
242
+
169
243
  ### 2026-11-03 — Open Mercato 0.9.0 peers; `isQuotingError` / `isOrderError`
170
244
 
171
245
  **Peer ranges (breaking for consumers).** `@open-mercato/*` `^0.9.0`,
@@ -1,4 +1,5 @@
1
1
  const features = [
2
+ { id: "cpq.quotes.adjust_price", title: "Adjust CPQ quote prices", module: "cpq" },
2
3
  { id: "cpq.quotes.view", title: "View CPQ Quotes", module: "cpq" },
3
4
  { id: "cpq.quotes.manage", title: "Manage CPQ Quotes", module: "cpq" },
4
5
  { id: "cpq.pricing.view", title: "View Pricing", module: "cpq" },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/cpq/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'cpq.quotes.view', title: 'View CPQ Quotes', module: 'cpq' },\n { id: 'cpq.quotes.manage', title: 'Manage CPQ Quotes', module: 'cpq' },\n { id: 'cpq.pricing.view', title: 'View Pricing', module: 'cpq' },\n { id: 'cpq.pricing.manage', title: 'Manage Pricing', module: 'cpq' },\n { id: 'cpq.products.view', title: 'View CPQ Product Config', module: 'cpq' },\n { id: 'cpq.products.manage', title: 'Manage CPQ Product Config', module: 'cpq' },\n { id: 'cpq.specifications.view', title: 'View Product Specifications', module: 'cpq' },\n { id: 'cpq.specifications.manage', title: 'Manage Product Specifications', module: 'cpq' },\n { id: 'cpq.offerings.view', title: 'View Product Offerings', module: 'cpq' },\n { id: 'cpq.offerings.manage', title: 'Manage Product Offerings', module: 'cpq' },\n { id: 'cpq.wizards.view', title: 'View Wizards', module: 'cpq' },\n { id: 'cpq.wizards.manage', title: 'Manage Wizards', module: 'cpq' },\n { id: 'cpq.orders.view', title: 'View CPQ Orders', module: 'cpq' },\n { id: 'cpq.orders.manage', title: 'Manage CPQ Orders', module: 'cpq' },\n // XD-250 ARC (Amend / Renew / Cancel)\n { id: 'cpq.arc.amend.manage', title: 'Manage Amend Quotes', module: 'cpq' },\n { id: 'cpq.arc.renew.manage', title: 'Manage Renew Quotes', module: 'cpq' },\n { id: 'cpq.arc.cancel.manage', title: 'Manage Cancel Quotes', module: 'cpq' },\n { id: 'cpq.arc.changelog.view', title: 'View Subscription Change History', module: 'cpq' },\n // Expiring-subscriptions feature retired \u2014 the dedicated /expiring page\n // was replaced by an \"Expiring within (days)\" filter + saved perspective\n // on the main subscriptions list, which uses `cpq.inventory.view`.\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACrE,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,qBAAqB,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,uBAAuB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,2BAA2B,OAAO,+BAA+B,QAAQ,MAAM;AAAA,EACrF,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACzF,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA;AAAA,EAErE,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,yBAAyB,OAAO,wBAAwB,QAAQ,MAAM;AAAA,EAC5E,EAAE,IAAI,0BAA0B,OAAO,oCAAoC,QAAQ,MAAM;AAAA;AAAA;AAAA;AAI3F;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n { id: 'cpq.quotes.adjust_price', title: 'Adjust CPQ quote prices', module: 'cpq' },\n { id: 'cpq.quotes.view', title: 'View CPQ Quotes', module: 'cpq' },\n { id: 'cpq.quotes.manage', title: 'Manage CPQ Quotes', module: 'cpq' },\n { id: 'cpq.pricing.view', title: 'View Pricing', module: 'cpq' },\n { id: 'cpq.pricing.manage', title: 'Manage Pricing', module: 'cpq' },\n { id: 'cpq.products.view', title: 'View CPQ Product Config', module: 'cpq' },\n { id: 'cpq.products.manage', title: 'Manage CPQ Product Config', module: 'cpq' },\n { id: 'cpq.specifications.view', title: 'View Product Specifications', module: 'cpq' },\n { id: 'cpq.specifications.manage', title: 'Manage Product Specifications', module: 'cpq' },\n { id: 'cpq.offerings.view', title: 'View Product Offerings', module: 'cpq' },\n { id: 'cpq.offerings.manage', title: 'Manage Product Offerings', module: 'cpq' },\n { id: 'cpq.wizards.view', title: 'View Wizards', module: 'cpq' },\n { id: 'cpq.wizards.manage', title: 'Manage Wizards', module: 'cpq' },\n { id: 'cpq.orders.view', title: 'View CPQ Orders', module: 'cpq' },\n { id: 'cpq.orders.manage', title: 'Manage CPQ Orders', module: 'cpq' },\n // XD-250 ARC (Amend / Renew / Cancel)\n { id: 'cpq.arc.amend.manage', title: 'Manage Amend Quotes', module: 'cpq' },\n { id: 'cpq.arc.renew.manage', title: 'Manage Renew Quotes', module: 'cpq' },\n { id: 'cpq.arc.cancel.manage', title: 'Manage Cancel Quotes', module: 'cpq' },\n { id: 'cpq.arc.changelog.view', title: 'View Subscription Change History', module: 'cpq' },\n // Expiring-subscriptions feature retired \u2014 the dedicated /expiring page\n // was replaced by an \"Expiring within (days)\" filter + saved perspective\n // on the main subscriptions list, which uses `cpq.inventory.view`.\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,2BAA2B,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EACjF,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACrE,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,qBAAqB,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,uBAAuB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,2BAA2B,OAAO,+BAA+B,QAAQ,MAAM;AAAA,EACrF,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACzF,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA;AAAA,EAErE,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,yBAAyB,OAAO,wBAAwB,QAAQ,MAAM;AAAA,EAC5E,EAAE,IAAI,0BAA0B,OAAO,oCAAoC,QAAQ,MAAM;AAAA;AAAA;AAAA;AAI3F;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,45 @@
1
+ import { NextResponse } from "next/server";
2
+ import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
3
+ import { runCpqMutationGuards } from "./quoteMutationGuard.js";
4
+ async function adjustmentCommandRoute(req, ctx, quoteId, body, schema, operation, adjustmentId) {
5
+ const guard = await runCpqMutationGuards(ctx, req, {
6
+ resourceKind: "cpq.quote_price_adjustment",
7
+ resourceId: adjustmentId,
8
+ operation,
9
+ mutationPayload: { quoteId, ...body }
10
+ });
11
+ if (!guard.ok) return guard.response;
12
+ const modified = Object.fromEntries(Object.entries(guard.modifiedPayload ?? {}).filter(([key]) => key !== "quoteId" && key !== "adjustmentId"));
13
+ const effective = schema.parse({ ...body, ...modified });
14
+ const input = operation === "create" ? { quoteId, adjustment: effective } : operation === "update" ? { quoteId, adjustmentId, patch: effective } : { quoteId, adjustmentId, ...effective };
15
+ const commandCtx = {
16
+ container: ctx.container,
17
+ auth: ctx.auth,
18
+ organizationScope: ctx.organizationScope,
19
+ selectedOrganizationId: ctx.organizationId,
20
+ organizationIds: [ctx.organizationId],
21
+ request: req
22
+ };
23
+ const bus = ctx.container.resolve("commandBus");
24
+ const commandId = `cpq.quote_price_adjustment.${operation}`;
25
+ const { result, logEntry } = await bus.execute(commandId, { input, ctx: commandCtx });
26
+ if (!result) throw new Error("Adjustment command did not return a result");
27
+ await guard.runAfterSuccess();
28
+ const response = NextResponse.json(operation === "delete" ? { quote: result.quote } : { adjustment: result.adjustment, quote: result.quote }, { status: operation === "create" ? 201 : 200 });
29
+ if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {
30
+ response.headers.set("x-om-operation", serializeOperationMetadata({
31
+ id: logEntry.id,
32
+ undoToken: logEntry.undoToken,
33
+ commandId: logEntry.commandId,
34
+ actionLabel: logEntry.actionLabel ?? null,
35
+ resourceKind: "cpq.quote_price_adjustment",
36
+ resourceId: result.adjustment.id,
37
+ executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : typeof logEntry.createdAt === "string" ? logEntry.createdAt : (/* @__PURE__ */ new Date()).toISOString()
38
+ }));
39
+ }
40
+ return response;
41
+ }
42
+ export {
43
+ adjustmentCommandRoute
44
+ };
45
+ //# sourceMappingURL=adjustmentCommandRoute.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { z } from 'zod'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CpqRouteContext } from '../context'\nimport { runCpqMutationGuards } from './quoteMutationGuard'\nimport type { AdjustmentWriteResult } from '../../services/priceAdjustments/adjustmentService'\n\nexport async function adjustmentCommandRoute<T extends object>(\n req: Request, ctx: CpqRouteContext, quoteId: string, body: T,\n schema: z.ZodType<T>, operation: 'create' | 'update' | 'delete', adjustmentId?: string,\n): Promise<NextResponse | Response> {\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: 'cpq.quote_price_adjustment', resourceId: adjustmentId,\n operation, mutationPayload: { quoteId, ...body },\n })\n if (!guard.ok) return guard.response\n // Path identifiers belong to the server, even when a project guard returns\n // the full payload including the original quoteId metadata.\n const modified = Object.fromEntries(Object.entries(guard.modifiedPayload ?? {})\n .filter(([key]) => key !== 'quoteId' && key !== 'adjustmentId'))\n const effective = schema.parse({ ...body, ...modified })\n const input = operation === 'create' ? { quoteId, adjustment: effective }\n : operation === 'update' ? { quoteId, adjustmentId, patch: effective }\n : { quoteId, adjustmentId, ...effective }\n const commandCtx: CommandRuntimeContext = {\n container: ctx.container, auth: ctx.auth, organizationScope: ctx.organizationScope,\n selectedOrganizationId: ctx.organizationId, organizationIds: [ctx.organizationId], request: req,\n }\n const bus = ctx.container.resolve<CommandBus>('commandBus')\n const commandId = `cpq.quote_price_adjustment.${operation}`\n const { result, logEntry } = await bus.execute<typeof input, AdjustmentWriteResult>(commandId, { input, ctx: commandCtx })\n if (!result) throw new Error('Adjustment command did not return a result')\n await guard.runAfterSuccess()\n const response = NextResponse.json(operation === 'delete' ? { quote: result.quote }\n : { adjustment: result.adjustment, quote: result.quote }, { status: operation === 'create' ? 201 : 200 })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set('x-om-operation', serializeOperationMetadata({ id: logEntry.id, undoToken: logEntry.undoToken,\n commandId: logEntry.commandId, actionLabel: logEntry.actionLabel ?? null,\n resourceKind: 'cpq.quote_price_adjustment', resourceId: result.adjustment.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string' ? logEntry.createdAt : new Date().toISOString() }))\n }\n return response\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAG7B,SAAS,kCAAkC;AAE3C,SAAS,4BAA4B;AAGrC,eAAsB,uBACpB,KAAc,KAAsB,SAAiB,MACrD,QAAsB,WAA2C,cAC/B;AAClC,QAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,IACjD,cAAc;AAAA,IAA8B,YAAY;AAAA,IACxD;AAAA,IAAW,iBAAiB,EAAE,SAAS,GAAG,KAAK;AAAA,EACjD,CAAC;AACD,MAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAG5B,QAAM,WAAW,OAAO,YAAY,OAAO,QAAQ,MAAM,mBAAmB,CAAC,CAAC,EAC3E,OAAO,CAAC,CAAC,GAAG,MAAM,QAAQ,aAAa,QAAQ,cAAc,CAAC;AACjE,QAAM,YAAY,OAAO,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;AACvD,QAAM,QAAQ,cAAc,WAAW,EAAE,SAAS,YAAY,UAAU,IACpE,cAAc,WAAW,EAAE,SAAS,cAAc,OAAO,UAAU,IACjE,EAAE,SAAS,cAAc,GAAG,UAAU;AAC5C,QAAM,aAAoC;AAAA,IACxC,WAAW,IAAI;AAAA,IAAW,MAAM,IAAI;AAAA,IAAM,mBAAmB,IAAI;AAAA,IACjE,wBAAwB,IAAI;AAAA,IAAgB,iBAAiB,CAAC,IAAI,cAAc;AAAA,IAAG,SAAS;AAAA,EAC9F;AACA,QAAM,MAAM,IAAI,UAAU,QAAoB,YAAY;AAC1D,QAAM,YAAY,8BAA8B,SAAS;AACzD,QAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,IAAI,QAA6C,WAAW,EAAE,OAAO,KAAK,WAAW,CAAC;AACzH,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,4CAA4C;AACzE,QAAM,MAAM,gBAAgB;AAC5B,QAAM,WAAW,aAAa,KAAK,cAAc,WAAW,EAAE,OAAO,OAAO,MAAM,IAC9E,EAAE,YAAY,OAAO,YAAY,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,cAAc,WAAW,MAAM,IAAI,CAAC;AAC1G,MAAI,UAAU,aAAa,SAAS,MAAM,SAAS,WAAW;AAC5D,aAAS,QAAQ,IAAI,kBAAkB,2BAA2B;AAAA,MAAE,IAAI,SAAS;AAAA,MAAI,WAAW,SAAS;AAAA,MACvG,WAAW,SAAS;AAAA,MAAW,aAAa,SAAS,eAAe;AAAA,MACpE,cAAc;AAAA,MAA8B,YAAY,OAAO,WAAW;AAAA,MAC1E,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAC5E,OAAO,SAAS,cAAc,WAAW,SAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAAE,CAAC,CAAC;AAAA,EACjG;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,23 @@
1
+ import {
2
+ runRouteMutationGuards
3
+ } from "@open-mercato/shared/lib/crud/route-mutation-guard";
4
+ const CPQ_QUOTE_RESOURCE_KIND = "cpq.quote";
5
+ const CPQ_QUOTE_LINE_RESOURCE_KIND = "cpq.quote_line";
6
+ function runCpqMutationGuards(ctx, req, input) {
7
+ return runRouteMutationGuards({
8
+ container: ctx.container,
9
+ req,
10
+ auth: {
11
+ userId: ctx.auth?.sub ?? "",
12
+ tenantId: ctx.tenantId,
13
+ organizationId: ctx.organizationId || null
14
+ },
15
+ input
16
+ });
17
+ }
18
+ export {
19
+ CPQ_QUOTE_LINE_RESOURCE_KIND,
20
+ CPQ_QUOTE_RESOURCE_KIND,
21
+ runCpqMutationGuards
22
+ };
23
+ //# sourceMappingURL=quoteMutationGuard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/cpq/api/_helpers/quoteMutationGuard.ts"],
4
+ "sourcesContent": ["import {\n runRouteMutationGuards,\n type RouteMutationGuardInput,\n type RouteMutationGuardResult,\n} from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport type { CpqRouteContext } from '../context'\n\n/** Mutation-guard resource kinds for CPQ quote writes (`module.entity`). */\nexport const CPQ_QUOTE_RESOURCE_KIND = 'cpq.quote'\nexport const CPQ_QUOTE_LINE_RESOURCE_KIND = 'cpq.quote_line'\n\n/**\n * Run every registered mutation guard for a CPQ custom write route.\n *\n * Quote routes are hand-written (not `makeCrudRoute`), so they wire guards\n * explicitly through the framework's registry-based wrapper. Projects use\n * this hook to enforce rules CPQ does not own \u2014 e.g. blocking\n * `ready \u2192 with_customer` or order conversion until an approval workflow\n * signs a discounted quote off. With no guard registered the call passes.\n *\n * Call `runAfterSuccess()` on the passed result only after the write commits.\n */\nexport function runCpqMutationGuards(\n ctx: Pick<CpqRouteContext, 'container' | 'auth' | 'tenantId' | 'organizationId'>,\n req: Request,\n input: RouteMutationGuardInput,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: ctx.container,\n req,\n auth: {\n userId: ctx.auth?.sub ?? '',\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId || null,\n },\n input,\n })\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,OAGK;AAIA,MAAM,0BAA0B;AAChC,MAAM,+BAA+B;AAarC,SAAS,qBACd,KACA,KACA,OACmC;AACnC,SAAO,uBAAuB;AAAA,IAC5B,WAAW,IAAI;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,QAAQ,IAAI,MAAM,OAAO;AAAA,MACzB,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI,kBAAkB;AAAA,IACxC;AAAA,IACA;AAAA,EACF,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ const cpqErrorResponseSchema = z.object({ error: z.string() }).passthrough();
3
+ function cpqErrorDoc(status, description) {
4
+ return { status, description, schema: cpqErrorResponseSchema };
5
+ }
6
+ const cpqQuoteIdPathParams = z.object({ quoteId: z.string().uuid() });
7
+ const CPQ_GUARDED_WRITE_ERRORS = [
8
+ cpqErrorDoc(400, "Validation error"),
9
+ cpqErrorDoc(404, "Quote or line not found"),
10
+ cpqErrorDoc(409, "Quote is in a terminal status"),
11
+ cpqErrorDoc(422, "Blocked by a mutation guard")
12
+ ];
13
+ export {
14
+ CPQ_GUARDED_WRITE_ERRORS,
15
+ cpqErrorDoc,
16
+ cpqErrorResponseSchema,
17
+ cpqQuoteIdPathParams
18
+ };
19
+ //# sourceMappingURL=quoteRouteOpenApi.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiResponseDoc } from '@open-mercato/shared/lib/openapi'\n\n/** Error body every CPQ quote route returns (`{ error, ... }`). */\nexport const cpqErrorResponseSchema = z.object({ error: z.string() }).passthrough()\n\nexport function cpqErrorDoc(status: number, description: string): OpenApiResponseDoc {\n return { status, description, schema: cpqErrorResponseSchema }\n}\n\nexport const cpqQuoteIdPathParams = z.object({ quoteId: z.string().uuid() })\n\n/** Shared error docs for quote writes guarded by mutation guards. */\nexport const CPQ_GUARDED_WRITE_ERRORS: OpenApiResponseDoc[] = [\n cpqErrorDoc(400, 'Validation error'),\n cpqErrorDoc(404, 'Quote or line not found'),\n cpqErrorDoc(409, 'Quote is in a terminal status'),\n cpqErrorDoc(422, 'Blocked by a mutation guard'),\n]\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAIX,MAAM,yBAAyB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY;AAE3E,SAAS,YAAY,QAAgB,aAAyC;AACnF,SAAO,EAAE,QAAQ,aAAa,QAAQ,uBAAuB;AAC/D;AAEO,MAAM,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAGpE,MAAM,2BAAiD;AAAA,EAC5D,YAAY,KAAK,kBAAkB;AAAA,EACnC,YAAY,KAAK,yBAAyB;AAAA,EAC1C,YAAY,KAAK,+BAA+B;AAAA,EAChD,YAAY,KAAK,6BAA6B;AAChD;",
6
+ "names": []
7
+ }
@@ -1,4 +1,6 @@
1
1
  import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { cpqErrorDoc } from "../../_helpers/quoteRouteOpenApi.js";
2
4
  import { resolveCpqRouteContext } from "../../context.js";
3
5
  import { isOrderError } from "../../../services/cpqOrderService.js";
4
6
  const metadata = {
@@ -21,8 +23,22 @@ async function GET(req, { params }) {
21
23
  return NextResponse.json({ error: "Internal error" }, { status: 500 });
22
24
  }
23
25
  }
26
+ const openApi = {
27
+ tag: "CPQ Orders",
28
+ summary: "CPQ order",
29
+ pathParams: z.object({ orderId: z.string().uuid() }),
30
+ methods: {
31
+ GET: {
32
+ summary: "Get a CPQ order with its lines and pricing summary",
33
+ description: "`pricingSummary` carries NRC / MRC totals plus the source quote's waterfall figures (`listNrcTotal`, `listMrcTotal`, `standard*Total`, `ruleDiscountTotal`, `manualDiscountTotal`, `dealValue`, `discountDepth`), computed from the line charges copied at conversion. ARC lines carry `arcSource` (the subscription item before the change).",
34
+ responses: [{ status: 200, description: "The order" }],
35
+ errors: [cpqErrorDoc(401, "Unauthorized"), cpqErrorDoc(404, "Order not found")]
36
+ }
37
+ }
38
+ };
24
39
  export {
25
40
  GET,
26
- metadata
41
+ metadata,
42
+ openApi
27
43
  };
28
44
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/api/orders/%5BorderId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../context'\nimport { isOrderError } from '../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../services/cpqOrderService'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.getOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/orders/[orderId].GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAGtB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA6C;AAC5F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,SAAS,SAAS,KAAK;AACpD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,8BAA8B,GAAG;AAC/C,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { cpqErrorDoc } from '../../_helpers/quoteRouteOpenApi'\nimport { resolveCpqRouteContext } from '../../context'\nimport { isOrderError } from '../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../services/cpqOrderService'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.getOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/orders/[orderId].GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Orders',\n summary: 'CPQ order',\n pathParams: z.object({ orderId: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'Get a CPQ order with its lines and pricing summary',\n description:\n '`pricingSummary` carries NRC / MRC totals plus the source quote\\'s waterfall figures (`listNrcTotal`, `listMrcTotal`, `standard*Total`, `ruleDiscountTotal`, `manualDiscountTotal`, `dealValue`, `discountDepth`), computed from the line charges copied at conversion. ARC lines carry `arcSource` (the subscription item before the change).',\n responses: [{ status: 200, description: 'The order' }],\n errors: [cpqErrorDoc(401, 'Unauthorized'), cpqErrorDoc(404, 'Order not found')],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAGtB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA6C;AAC5F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,SAAS,SAAS,KAAK;AACpD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,8BAA8B,GAAG;AAC/C,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,EACnD,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,YAAY,CAAC;AAAA,MACrD,QAAQ,CAAC,YAAY,KAAK,cAAc,GAAG,YAAY,KAAK,iBAAiB,CAAC;AAAA,IAChF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -3,6 +3,8 @@ import { z } from "zod";
3
3
  import { resolveCpqRouteContext } from "../context.js";
4
4
  import { cpqConvertQuoteToOrderSchema } from "../../data/validators.js";
5
5
  import { isOrderError } from "../../services/cpqOrderService.js";
6
+ import { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from "../_helpers/quoteMutationGuard.js";
7
+ import { cpqErrorDoc } from "../_helpers/quoteRouteOpenApi.js";
6
8
  const metadata = {
7
9
  POST: { requireAuth: true, requireFeatures: ["cpq.orders.manage"] },
8
10
  GET: { requireAuth: true, requireFeatures: ["cpq.orders.view"] }
@@ -13,8 +15,16 @@ async function POST(req) {
13
15
  if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
14
16
  const body = cpqConvertQuoteToOrderSchema.parse(await req.json());
15
17
  const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
18
+ const guard = await runCpqMutationGuards(ctx, req, {
19
+ resourceKind: CPQ_QUOTE_RESOURCE_KIND,
20
+ resourceId: body.quoteId,
21
+ operation: "custom",
22
+ mutationPayload: { action: "convert_to_order" }
23
+ });
24
+ if (!guard.ok) return guard.response;
16
25
  const service = ctx.container.resolve("cpqOrderService");
17
26
  const result = await service.convertQuoteToOrder(body.quoteId, scope);
27
+ await guard.runAfterSuccess();
18
28
  return NextResponse.json(result, { status: 201 });
19
29
  } catch (err) {
20
30
  return handleError(err, "cpq/orders.POST");
@@ -54,7 +64,7 @@ function handleError(err, context) {
54
64
  return NextResponse.json({ error: "Validation error", details: err.issues }, { status: 400 });
55
65
  }
56
66
  if (isOrderError(err)) {
57
- return NextResponse.json({ error: err.message }, { status: err.status });
67
+ return NextResponse.json({ error: err.message, ...err.details }, { status: err.status });
58
68
  }
59
69
  const errObj = err;
60
70
  if (typeof errObj?.status === "number" && typeof errObj?.error === "string") {
@@ -63,9 +73,44 @@ function handleError(err, context) {
63
73
  console.error(`[${context}]`, err);
64
74
  return NextResponse.json({ error: "Internal error" }, { status: 500 });
65
75
  }
76
+ const openApi = {
77
+ tag: "CPQ Orders",
78
+ summary: "CPQ orders",
79
+ methods: {
80
+ POST: {
81
+ summary: "Convert a CPQ quote into an order",
82
+ description: "Creates the sales order and CPQ order configuration from the quote. Refused for cancelled or rejected quotes; mutation guards (resource kind `cpq.quote`, action `convert_to_order`) run first.",
83
+ requestBody: { schema: cpqConvertQuoteToOrderSchema },
84
+ responses: [{ status: 201, description: "Created order" }],
85
+ errors: [
86
+ cpqErrorDoc(400, "Validation error"),
87
+ cpqErrorDoc(404, "Quote not found"),
88
+ cpqErrorDoc(409, "Quote cannot be converted in its status or is already converted"),
89
+ cpqErrorDoc(422, "Quote has no lines or has invalid stored adjustments, or blocked by a mutation guard")
90
+ ]
91
+ },
92
+ GET: {
93
+ summary: "List CPQ orders",
94
+ query: z.object({
95
+ customerId: z.string().uuid().optional(),
96
+ sourceQuoteId: z.string().uuid().optional(),
97
+ salesOrderId: z.string().uuid().optional(),
98
+ cpqStatus: z.string().optional(),
99
+ currencyCode: z.string().optional(),
100
+ search: z.string().optional(),
101
+ sortField: z.enum(["createdAt", "updatedAt", "cpqStatus", "activatedAt", "currencyCode"]).optional(),
102
+ sortDir: z.enum(["asc", "desc"]).optional(),
103
+ page: z.coerce.number().int().min(1).optional(),
104
+ pageSize: z.coerce.number().int().min(1).max(100).optional()
105
+ }),
106
+ responses: [{ status: 200, description: "Paged list of CPQ orders" }]
107
+ }
108
+ }
109
+ };
66
110
  export {
67
111
  GET,
68
112
  POST,
69
- metadata
113
+ metadata,
114
+ openApi
70
115
  };
71
116
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/api/orders/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { cpqConvertQuoteToOrderSchema } from '../../data/validators'\nimport { isOrderError } from '../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../services/cpqOrderService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqConvertQuoteToOrderSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.convertQuoteToOrder(body.quoteId, scope)\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n return handleError(err, 'cpq/orders.POST')\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n const url = new URL(req.url)\n const ALLOWED_SORT_FIELDS = ['createdAt', 'updatedAt', 'cpqStatus', 'activatedAt', 'currencyCode'] as const\n const sortFieldParam = url.searchParams.get('sortField') ?? ''\n const sortField = (ALLOWED_SORT_FIELDS as readonly string[]).includes(sortFieldParam)\n ? (sortFieldParam as (typeof ALLOWED_SORT_FIELDS)[number])\n : undefined\n const sortDir = url.searchParams.get('sortDir') === 'asc' ? 'asc' : 'desc'\n\n const filters = {\n customerId: url.searchParams.get('customerId') ?? undefined,\n sourceQuoteId: url.searchParams.get('sourceQuoteId') ?? undefined,\n salesOrderId: url.searchParams.get('salesOrderId') ?? undefined,\n cpqStatus: url.searchParams.get('cpqStatus') ?? undefined,\n currencyCode: url.searchParams.get('currencyCode') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField,\n sortDir,\n page: url.searchParams.has('page') ? Number(url.searchParams.get('page')) : undefined,\n pageSize: url.searchParams.has('pageSize') ? Number(url.searchParams.get('pageSize')) : undefined,\n } as const\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.listOrders(filters, scope)\n return NextResponse.json(result)\n } catch (err) {\n return handleError(err, 'cpq/orders.GET')\n }\n}\n\nfunction handleError(err: unknown, context: string) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error(`[${context}]`, err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { cpqConvertQuoteToOrderSchema } from '../../data/validators'\nimport { isOrderError } from '../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../services/cpqOrderService'\nimport { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from '../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { cpqErrorDoc } from '../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqConvertQuoteToOrderSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Conversion turns a quote into an order \u2014 projects can block it (e.g.\n // until an approval workflow has signed a discounted quote off).\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_RESOURCE_KIND,\n resourceId: body.quoteId,\n operation: 'custom',\n mutationPayload: { action: 'convert_to_order' },\n })\n if (!guard.ok) return guard.response\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.convertQuoteToOrder(body.quoteId, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n return handleError(err, 'cpq/orders.POST')\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n const url = new URL(req.url)\n const ALLOWED_SORT_FIELDS = ['createdAt', 'updatedAt', 'cpqStatus', 'activatedAt', 'currencyCode'] as const\n const sortFieldParam = url.searchParams.get('sortField') ?? ''\n const sortField = (ALLOWED_SORT_FIELDS as readonly string[]).includes(sortFieldParam)\n ? (sortFieldParam as (typeof ALLOWED_SORT_FIELDS)[number])\n : undefined\n const sortDir = url.searchParams.get('sortDir') === 'asc' ? 'asc' : 'desc'\n\n const filters = {\n customerId: url.searchParams.get('customerId') ?? undefined,\n sourceQuoteId: url.searchParams.get('sourceQuoteId') ?? undefined,\n salesOrderId: url.searchParams.get('salesOrderId') ?? undefined,\n cpqStatus: url.searchParams.get('cpqStatus') ?? undefined,\n currencyCode: url.searchParams.get('currencyCode') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField,\n sortDir,\n page: url.searchParams.has('page') ? Number(url.searchParams.get('page')) : undefined,\n pageSize: url.searchParams.has('pageSize') ? Number(url.searchParams.get('pageSize')) : undefined,\n } as const\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.listOrders(filters, scope)\n return NextResponse.json(result)\n } catch (err) {\n return handleError(err, 'cpq/orders.GET')\n }\n}\n\nfunction handleError(err: unknown, context: string) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message, ...err.details }, { status: err.status })\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error(`[${context}]`, err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Orders',\n summary: 'CPQ orders',\n methods: {\n POST: {\n summary: 'Convert a CPQ quote into an order',\n description:\n 'Creates the sales order and CPQ order configuration from the quote. Refused for cancelled or rejected quotes; mutation guards (resource kind `cpq.quote`, action `convert_to_order`) run first.',\n requestBody: { schema: cpqConvertQuoteToOrderSchema },\n responses: [{ status: 201, description: 'Created order' }],\n errors: [\n cpqErrorDoc(400, 'Validation error'),\n cpqErrorDoc(404, 'Quote not found'),\n cpqErrorDoc(409, 'Quote cannot be converted in its status or is already converted'),\n cpqErrorDoc(422, 'Quote has no lines or has invalid stored adjustments, or blocked by a mutation guard'),\n ],\n },\n GET: {\n summary: 'List CPQ orders',\n query: z.object({\n customerId: z.string().uuid().optional(),\n sourceQuoteId: z.string().uuid().optional(),\n salesOrderId: z.string().uuid().optional(),\n cpqStatus: z.string().optional(),\n currencyCode: z.string().optional(),\n search: z.string().optional(),\n sortField: z.enum(['createdAt', 'updatedAt', 'cpqStatus', 'activatedAt', 'currencyCode']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n }),\n responses: [{ status: 200, description: 'Paged list of CPQ orders' }],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,54 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { resolveCpqRouteContext } from "../../../../../context.js";
4
+ import { isQuotingError } from "../../../../../../services/cpqQuotingService.js";
5
+ import { resolveQuotingService } from "../../../../../resolveQuotingService.js";
6
+ import { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from "../../../../../_helpers/quoteMutationGuard.js";
7
+ import { CPQ_GUARDED_WRITE_ERRORS } from "../../../../../_helpers/quoteRouteOpenApi.js";
8
+ const metadata = {
9
+ POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
10
+ };
11
+ async function POST(req, { params }) {
12
+ try {
13
+ const ctx = await resolveCpqRouteContext(req);
14
+ if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
15
+ const { quoteId, lineId } = await params;
16
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
17
+ const guard = await runCpqMutationGuards(ctx, req, {
18
+ resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,
19
+ resourceId: lineId,
20
+ operation: "update",
21
+ mutationPayload: { quoteId, restore: true }
22
+ });
23
+ if (!guard.ok) return guard.response;
24
+ const service = resolveQuotingService(ctx);
25
+ const result = await service.restoreQuoteItem(quoteId, lineId, scope);
26
+ await guard.runAfterSuccess();
27
+ return NextResponse.json(result);
28
+ } catch (err) {
29
+ if (isQuotingError(err)) {
30
+ return NextResponse.json({ error: err.message }, { status: err.status });
31
+ }
32
+ console.error("[cpq/quotes/[quoteId]/items/[lineId]/restore.POST]", err);
33
+ return NextResponse.json({ error: "Internal error" }, { status: 500 });
34
+ }
35
+ }
36
+ const openApi = {
37
+ tag: "CPQ Quotes",
38
+ summary: "Restore a subscription item marked for removal",
39
+ pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),
40
+ methods: {
41
+ POST: {
42
+ summary: "Restore a removed subscription item on an Amend / Renew quote",
43
+ description: "Turns a `cancel` line that mirrors a live subscription item back into a `modify` line, rebuilt from the item (configuration, quantity, stored price). Its cancelled mirrored components are restored with it. 409 when the line is not a removed item, its parent is still removed, or the item is no longer active. Mutation guards run first (resource kind `cpq.quote_line`, update).",
44
+ responses: [{ status: 200, description: "Quote after the restore" }],
45
+ errors: CPQ_GUARDED_WRITE_ERRORS.filter((doc) => doc.status !== 400)
46
+ }
47
+ }
48
+ };
49
+ export {
50
+ POST,
51
+ metadata,
52
+ openApi
53
+ };
54
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/items/%5BlineId%5D/restore/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../../../context'\nimport { isQuotingError } from '../../../../../../services/cpqQuotingService'\nimport { resolveQuotingService } from '../../../../../resolveQuotingService'\nimport { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from '../../../../../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CPQ_GUARDED_WRITE_ERRORS } from '../../../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string; lineId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId, lineId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,\n resourceId: lineId,\n operation: 'update',\n mutationPayload: { quoteId, restore: true },\n })\n if (!guard.ok) return guard.response\n\n const service = resolveQuotingService(ctx)\n const result = await service.restoreQuoteItem(quoteId, lineId, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result)\n } catch (err) {\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/items/[lineId]/restore.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'Restore a subscription item marked for removal',\n pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),\n methods: {\n POST: {\n summary: 'Restore a removed subscription item on an Amend / Renew quote',\n description:\n 'Turns a `cancel` line that mirrors a live subscription item back into a `modify` line, rebuilt from the item (configuration, quantity, stored price). Its cancelled mirrored components are restored with it. 409 when the line is not a removed item, its parent is still removed, or the item is no longer active. Mutation guards run first (resource kind `cpq.quote_line`, update).',\n responses: [{ status: 200, description: 'Quote after the restore' }],\n errors: CPQ_GUARDED_WRITE_ERRORS.filter((doc) => doc.status !== 400),\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,8BAA8B,4BAA4B;AAEnE,SAAS,gCAAgC;AAElC,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6D;AAC7G,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,SAAS,OAAO,IAAI,MAAM;AAClC,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,MACjD,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,iBAAiB,EAAE,SAAS,SAAS,KAAK;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAE5B,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,iBAAiB,SAAS,QAAQ,KAAK;AACpE,UAAM,MAAM,gBAAgB;AAC5B,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,GAAG,GAAG;AACvB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,sDAAsD,GAAG;AACvE,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,EAC9E,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,0BAA0B,CAAC;AAAA,MACnE,QAAQ,yBAAyB,OAAO,CAAC,QAAQ,IAAI,WAAW,GAAG;AAAA,IACrE;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }