@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
@@ -4,6 +4,8 @@ import { resolveCpqRouteContext } from "../../../../context.js";
4
4
  import { cpqUpdateQuoteItemSchema } from "../../../../../data/validators.js";
5
5
  import { isQuotingError } from "../../../../../services/cpqQuotingService.js";
6
6
  import { resolveQuotingService } from "../../../../resolveQuotingService.js";
7
+ import { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from "../../../../_helpers/quoteMutationGuard.js";
8
+ import { CPQ_GUARDED_WRITE_ERRORS } from "../../../../_helpers/quoteRouteOpenApi.js";
7
9
  const metadata = {
8
10
  PUT: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] },
9
11
  DELETE: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
@@ -15,8 +17,17 @@ async function PUT(req, { params }) {
15
17
  const { quoteId, lineId } = await params;
16
18
  const body = cpqUpdateQuoteItemSchema.parse(await req.json());
17
19
  const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
20
+ const guard = await runCpqMutationGuards(ctx, req, {
21
+ resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,
22
+ resourceId: lineId,
23
+ operation: "update",
24
+ mutationPayload: { quoteId, ...body }
25
+ });
26
+ if (!guard.ok) return guard.response;
27
+ const effective = guard.modifiedPayload ? cpqUpdateQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload }) : body;
18
28
  const service = resolveQuotingService(ctx);
19
- const result = await service.updateQuoteItem(quoteId, lineId, body, scope);
29
+ const result = await service.updateQuoteItem(quoteId, lineId, effective, scope);
30
+ await guard.runAfterSuccess();
20
31
  return NextResponse.json(result);
21
32
  } catch (err) {
22
33
  if (err instanceof z.ZodError) {
@@ -35,8 +46,16 @@ async function DELETE(req, { params }) {
35
46
  if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
36
47
  const { quoteId, lineId } = await params;
37
48
  const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
49
+ const guard = await runCpqMutationGuards(ctx, req, {
50
+ resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,
51
+ resourceId: lineId,
52
+ operation: "delete",
53
+ mutationPayload: { quoteId }
54
+ });
55
+ if (!guard.ok) return guard.response;
38
56
  const service = resolveQuotingService(ctx);
39
57
  const result = await service.removeQuoteItem(quoteId, lineId, scope);
58
+ await guard.runAfterSuccess();
40
59
  return NextResponse.json(result);
41
60
  } catch (err) {
42
61
  if (isQuotingError(err)) {
@@ -46,9 +65,30 @@ async function DELETE(req, { params }) {
46
65
  return NextResponse.json({ error: "Internal error" }, { status: 500 });
47
66
  }
48
67
  }
68
+ const openApi = {
69
+ tag: "CPQ Quotes",
70
+ summary: "CPQ quote line",
71
+ pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),
72
+ methods: {
73
+ PUT: {
74
+ summary: "Update a quote line (configuration, quantity, term)",
75
+ description: "Re-prices the line unless it is a frozen Amend mirror line whose pricing inputs are unchanged. Mutation guards run first (resource kind `cpq.quote_line`, update). `_arc*` configuration keys are server-owned and ignored.",
76
+ requestBody: { schema: cpqUpdateQuoteItemSchema },
77
+ responses: [{ status: 200, description: "Quote after the update" }],
78
+ errors: CPQ_GUARDED_WRITE_ERRORS
79
+ },
80
+ DELETE: {
81
+ summary: "Remove a quote line",
82
+ description: "On an Amend / Renew quote, a line mirroring a live subscription item is not deleted: it (and its mirrored components) becomes a `cancel` line with zero totals, and activation terminates the item; lines this quote added under it are deleted. 409 when the item is already marked for removal \u2014 use `POST .../restore` to undo. Other lines are deleted with their descendants. Mutation guards run first (resource kind `cpq.quote_line`, delete).",
83
+ responses: [{ status: 200, description: "Quote after the removal" }],
84
+ errors: CPQ_GUARDED_WRITE_ERRORS.filter((doc) => doc.status !== 400)
85
+ }
86
+ }
87
+ };
49
88
  export {
50
89
  DELETE,
51
90
  PUT,
52
- metadata
91
+ metadata,
92
+ openApi
53
93
  };
54
94
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/items/%5BlineId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../../context'\nimport { cpqUpdateQuoteItemSchema } from '../../../../../data/validators'\nimport { isQuotingError } from '../../../../../services/cpqQuotingService'\nimport { resolveQuotingService } from '../../../../resolveQuotingService'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function PUT(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 body = cpqUpdateQuoteItemSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = resolveQuotingService(ctx)\n const result = await service.updateQuoteItem(quoteId, lineId, body, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/items/[lineId].PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(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 service = resolveQuotingService(ctx)\n const result = await service.removeQuoteItem(quoteId, lineId, scope)\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].DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AAE/B,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AAAA,EACjE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACtE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA6D;AAC5G,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,OAAO,yBAAyB,MAAM,MAAM,IAAI,KAAK,CAAC;AAC5D,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,gBAAgB,SAAS,QAAQ,MAAM,KAAK;AACzE,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9F;AACA,QAAI,eAAe,GAAG,GAAG;AACvB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,6CAA6C,GAAG;AAC9D,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEA,eAAsB,OAAO,KAAc,EAAE,OAAO,GAA6D;AAC/G,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,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,gBAAgB,SAAS,QAAQ,KAAK;AACnE,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,gDAAgD,GAAG;AACjE,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 { resolveCpqRouteContext } from '../../../../context'\nimport { cpqUpdateQuoteItemSchema } from '../../../../../data/validators'\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 PUT: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function PUT(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 body = cpqUpdateQuoteItemSchema.parse(await req.json())\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, ...body },\n })\n if (!guard.ok) return guard.response\n const effective = guard.modifiedPayload\n ? cpqUpdateQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload })\n : body\n\n const service = resolveQuotingService(ctx)\n const result = await service.updateQuoteItem(quoteId, lineId, effective, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result)\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/items/[lineId].PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(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: 'delete',\n mutationPayload: { quoteId },\n })\n if (!guard.ok) return guard.response\n\n const service = resolveQuotingService(ctx)\n const result = await service.removeQuoteItem(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].DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'CPQ quote line',\n pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),\n methods: {\n PUT: {\n summary: 'Update a quote line (configuration, quantity, term)',\n description:\n 'Re-prices the line unless it is a frozen Amend mirror line whose pricing inputs are unchanged. Mutation guards run first (resource kind `cpq.quote_line`, update). `_arc*` configuration keys are server-owned and ignored.',\n requestBody: { schema: cpqUpdateQuoteItemSchema },\n responses: [{ status: 200, description: 'Quote after the update' }],\n errors: CPQ_GUARDED_WRITE_ERRORS,\n },\n DELETE: {\n summary: 'Remove a quote line',\n description:\n 'On an Amend / Renew quote, a line mirroring a live subscription item is not deleted: it (and its mirrored components) becomes a `cancel` line with zero totals, and activation terminates the item; lines this quote added under it are deleted. 409 when the item is already marked for removal \u2014 use `POST .../restore` to undo. Other lines are deleted with their descendants. Mutation guards run first (resource kind `cpq.quote_line`, delete).',\n responses: [{ status: 200, description: 'Quote after the removal' }],\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,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,8BAA8B,4BAA4B;AAEnE,SAAS,gCAAgC;AAElC,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AAAA,EACjE,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACtE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA6D;AAC5G,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,OAAO,yBAAyB,MAAM,MAAM,IAAI,KAAK,CAAC;AAC5D,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,GAAG,KAAK;AAAA,IACtC,CAAC;AACD,QAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAC5B,UAAM,YAAY,MAAM,kBACpB,yBAAyB,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,gBAAgB,CAAC,IACpE;AAEJ,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,gBAAgB,SAAS,QAAQ,WAAW,KAAK;AAC9E,UAAM,MAAM,gBAAgB;AAC5B,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9F;AACA,QAAI,eAAe,GAAG,GAAG;AACvB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,6CAA6C,GAAG;AAC9D,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEA,eAAsB,OAAO,KAAc,EAAE,OAAO,GAA6D;AAC/G,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,QAAQ;AAAA,IAC7B,CAAC;AACD,QAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAE5B,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,gBAAgB,SAAS,QAAQ,KAAK;AACnE,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,gDAAgD,GAAG;AACjE,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,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,aAAa,EAAE,QAAQ,yBAAyB;AAAA,MAChD,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,yBAAyB,CAAC;AAAA,MAClE,QAAQ;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,MACN,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
6
  "names": []
7
7
  }
@@ -3,6 +3,8 @@ import { resolveCpqRouteContext } from "../../../context.js";
3
3
  import { cpqAddQuoteItemSchema } from "../../../../data/validators.js";
4
4
  import { resolveQuotingService } from "../../../resolveQuotingService.js";
5
5
  import { handleArcError } from "../../../_helpers/handleArcError.js";
6
+ import { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from "../../../_helpers/quoteMutationGuard.js";
7
+ import { CPQ_GUARDED_WRITE_ERRORS, cpqQuoteIdPathParams } from "../../../_helpers/quoteRouteOpenApi.js";
6
8
  const metadata = {
7
9
  POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
8
10
  };
@@ -13,15 +15,38 @@ async function POST(req, { params }) {
13
15
  const { quoteId } = await params;
14
16
  const body = cpqAddQuoteItemSchema.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_LINE_RESOURCE_KIND,
20
+ operation: "create",
21
+ mutationPayload: { quoteId, ...body }
22
+ });
23
+ if (!guard.ok) return guard.response;
24
+ const effective = guard.modifiedPayload ? cpqAddQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload }) : body;
16
25
  const service = resolveQuotingService(ctx);
17
- const result = await service.addQuoteItem(quoteId, body, scope);
26
+ const result = await service.addQuoteItem(quoteId, effective, scope);
27
+ await guard.runAfterSuccess();
18
28
  return NextResponse.json(result, { status: 201 });
19
29
  } catch (err) {
20
30
  return handleArcError(err, "cpq/quotes/[quoteId]/items.POST");
21
31
  }
22
32
  }
33
+ const openApi = {
34
+ tag: "CPQ Quotes",
35
+ summary: "CPQ quote lines",
36
+ pathParams: cpqQuoteIdPathParams,
37
+ methods: {
38
+ POST: {
39
+ summary: "Add a configured line to a CPQ quote",
40
+ description: "Prices the line and recalculates the quote. Mutation guards run first (resource kind `cpq.quote_line`, create). `_arc*` configuration keys are server-owned and ignored.",
41
+ requestBody: { schema: cpqAddQuoteItemSchema },
42
+ responses: [{ status: 201, description: "Quote after the line was added" }],
43
+ errors: CPQ_GUARDED_WRITE_ERRORS
44
+ }
45
+ }
46
+ };
23
47
  export {
24
48
  POST,
25
- metadata
49
+ metadata,
50
+ openApi
26
51
  };
27
52
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/items/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqAddQuoteItemSchema } from '../../../../data/validators'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\nimport { handleArcError } from '../../../_helpers/handleArcError'\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 }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqAddQuoteItemSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = resolveQuotingService(ctx)\n const result = await service.addQuoteItem(quoteId, body, scope)\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n return handleArcError(err, 'cpq/quotes/[quoteId]/items.POST')\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAExB,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,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,OAAO,sBAAsB,MAAM,MAAM,IAAI,KAAK,CAAC;AACzD,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,aAAa,SAAS,MAAM,KAAK;AAC9D,WAAO,aAAa,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClD,SAAS,KAAK;AACZ,WAAO,eAAe,KAAK,iCAAiC;AAAA,EAC9D;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqAddQuoteItemSchema } from '../../../../data/validators'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\nimport { handleArcError } from '../../../_helpers/handleArcError'\nimport { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from '../../../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CPQ_GUARDED_WRITE_ERRORS, cpqQuoteIdPathParams } 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 }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqAddQuoteItemSchema.parse(await req.json())\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 operation: 'create',\n mutationPayload: { quoteId, ...body },\n })\n if (!guard.ok) return guard.response\n const effective = guard.modifiedPayload\n ? cpqAddQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload })\n : body\n\n const service = resolveQuotingService(ctx)\n const result = await service.addQuoteItem(quoteId, effective, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n return handleArcError(err, 'cpq/quotes/[quoteId]/items.POST')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'CPQ quote lines',\n pathParams: cpqQuoteIdPathParams,\n methods: {\n POST: {\n summary: 'Add a configured line to a CPQ quote',\n description:\n 'Prices the line and recalculates the quote. Mutation guards run first (resource kind `cpq.quote_line`, create). `_arc*` configuration keys are server-owned and ignored.',\n requestBody: { schema: cpqAddQuoteItemSchema },\n responses: [{ status: 201, description: 'Quote after the line was added' }],\n errors: CPQ_GUARDED_WRITE_ERRORS,\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,8BAA8B,4BAA4B;AAEnE,SAAS,0BAA0B,4BAA4B;AAExD,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,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,OAAO,sBAAsB,MAAM,MAAM,IAAI,KAAK,CAAC;AACzD,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,MACjD,cAAc;AAAA,MACd,WAAW;AAAA,MACX,iBAAiB,EAAE,SAAS,GAAG,KAAK;AAAA,IACtC,CAAC;AACD,QAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAC5B,UAAM,YAAY,MAAM,kBACpB,sBAAsB,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,gBAAgB,CAAC,IACjE;AAEJ,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,aAAa,SAAS,WAAW,KAAK;AACnE,UAAM,MAAM,gBAAgB;AAC5B,WAAO,aAAa,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClD,SAAS,KAAK;AACZ,WAAO,eAAe,KAAK,iCAAiC;AAAA,EAC9D;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,aAAa,EAAE,QAAQ,sBAAsB;AAAA,MAC7C,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,iCAAiC,CAAC;AAAA,MAC1E,QAAQ;AAAA,IACV;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,47 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { resolveCpqRouteContext } from "../../../../context.js";
4
+ import { cpqPriceAdjustmentPatchSchema } from "../../../../../data/price-adjustments.js";
5
+ import { adjustmentCommandRoute } from "../../../../_helpers/adjustmentCommandRoute.js";
6
+ import { handleArcError } from "../../../../_helpers/handleArcError.js";
7
+ import { cpqErrorDoc } from "../../../../_helpers/quoteRouteOpenApi.js";
8
+ const metadata = {
9
+ PATCH: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] },
10
+ DELETE: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] }
11
+ };
12
+ const pathParams = z.object({ quoteId: z.string().uuid(), adjustmentId: z.string().uuid() });
13
+ const deletionSchema = z.object({ expectedRevision: z.coerce.number().int().min(0).optional() }).strict();
14
+ async function PATCH(req, { params }) {
15
+ try {
16
+ const ctx = await resolveCpqRouteContext(req);
17
+ if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
18
+ const { quoteId, adjustmentId } = pathParams.parse(await params);
19
+ const body = cpqPriceAdjustmentPatchSchema.parse(await req.json());
20
+ return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentPatchSchema, "update", adjustmentId);
21
+ } catch (error) {
22
+ return handleArcError(error, "cpq/price-adjustments.PATCH");
23
+ }
24
+ }
25
+ async function DELETE(req, { params }) {
26
+ try {
27
+ const ctx = await resolveCpqRouteContext(req);
28
+ if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
29
+ const { quoteId, adjustmentId } = pathParams.parse(await params);
30
+ const body = deletionSchema.parse(Object.fromEntries(new URL(req.url).searchParams));
31
+ return await adjustmentCommandRoute(req, ctx, quoteId, body, deletionSchema, "delete", adjustmentId);
32
+ } catch (error) {
33
+ return handleArcError(error, "cpq/price-adjustments.DELETE");
34
+ }
35
+ }
36
+ const errors = [cpqErrorDoc(400, "Invalid input"), cpqErrorDoc(403, "Missing permission or disabled feature"), cpqErrorDoc(404, "Quote or adjustment not found"), cpqErrorDoc(409, "Adjustment locked, stale revision or changed undo target"), cpqErrorDoc(422, "Invalid adjustment or guard rejection")];
37
+ const openApi = { tag: "CPQ Quotes", pathParams, methods: {
38
+ PATCH: { summary: "Update a negotiated quote price adjustment", requestBody: { schema: cpqPriceAdjustmentPatchSchema }, responses: [{ status: 200, description: "Adjustment and updated quote" }], errors },
39
+ DELETE: { summary: "Remove a negotiated quote price adjustment", query: deletionSchema, responses: [{ status: 200, description: "Updated quote" }], errors }
40
+ } };
41
+ export {
42
+ DELETE,
43
+ PATCH,
44
+ metadata,
45
+ openApi
46
+ };
47
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/price-adjustments/%5BadjustmentId%5D/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveCpqRouteContext } from '../../../../context'\nimport { cpqPriceAdjustmentPatchSchema } from '../../../../../data/price-adjustments'\nimport { adjustmentCommandRoute } from '../../../../_helpers/adjustmentCommandRoute'\nimport { handleArcError } from '../../../../_helpers/handleArcError'\nimport { cpqErrorDoc } from '../../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] },\n}\nconst pathParams = z.object({ quoteId: z.string().uuid(), adjustmentId: z.string().uuid() })\nconst deletionSchema = z.object({ expectedRevision: z.coerce.number().int().min(0).optional() }).strict()\ntype Params = { params: Promise<{ quoteId: string; adjustmentId: string }> }\nexport async function PATCH(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId, adjustmentId } = pathParams.parse(await params)\n const body = cpqPriceAdjustmentPatchSchema.parse(await req.json())\n return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentPatchSchema, 'update', adjustmentId)\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.PATCH') }\n}\nexport async function DELETE(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId, adjustmentId } = pathParams.parse(await params)\n const body = deletionSchema.parse(Object.fromEntries(new URL(req.url).searchParams))\n return await adjustmentCommandRoute(req, ctx, quoteId, body, deletionSchema, 'delete', adjustmentId)\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.DELETE') }\n}\nconst errors = [cpqErrorDoc(400, 'Invalid input'), cpqErrorDoc(403, 'Missing permission or disabled feature'), cpqErrorDoc(404, 'Quote or adjustment not found'), cpqErrorDoc(409, 'Adjustment locked, stale revision or changed undo target'), cpqErrorDoc(422, 'Invalid adjustment or guard rejection')]\nexport const openApi: OpenApiRouteDoc = { tag: 'CPQ Quotes', pathParams, methods: {\n PATCH: { summary: 'Update a negotiated quote price adjustment', requestBody: { schema: cpqPriceAdjustmentPatchSchema }, responses: [{ status: 200, description: 'Adjustment and updated quote' }], errors },\n DELETE: { summary: 'Remove a negotiated quote price adjustment', query: deletionSchema, responses: [{ status: 200, description: 'Updated quote' }], errors },\n} }\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,qCAAqC;AAC9C,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAErB,MAAM,WAAW;AAAA,EACtB,OAAO,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE;AAAA,EAC9F,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE;AACjG;AACA,MAAM,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC3F,MAAM,iBAAiB,EAAE,OAAO,EAAE,kBAAkB,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO;AAExG,eAAsB,MAAM,KAAc,EAAE,OAAO,GAAW;AAC5D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,SAAS,aAAa,IAAI,WAAW,MAAM,MAAM,MAAM;AAC/D,UAAM,OAAO,8BAA8B,MAAM,MAAM,IAAI,KAAK,CAAC;AACjE,WAAO,MAAM,uBAAuB,KAAK,KAAK,SAAS,MAAM,+BAA+B,UAAU,YAAY;AAAA,EACpH,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,6BAA6B;AAAA,EAAE;AAChF;AACA,eAAsB,OAAO,KAAc,EAAE,OAAO,GAAW;AAC7D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,SAAS,aAAa,IAAI,WAAW,MAAM,MAAM,MAAM;AAC/D,UAAM,OAAO,eAAe,MAAM,OAAO,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,YAAY,CAAC;AACnF,WAAO,MAAM,uBAAuB,KAAK,KAAK,SAAS,MAAM,gBAAgB,UAAU,YAAY;AAAA,EACrG,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,8BAA8B;AAAA,EAAE;AACjF;AACA,MAAM,SAAS,CAAC,YAAY,KAAK,eAAe,GAAG,YAAY,KAAK,wCAAwC,GAAG,YAAY,KAAK,+BAA+B,GAAG,YAAY,KAAK,0DAA0D,GAAG,YAAY,KAAK,uCAAuC,CAAC;AAClS,MAAM,UAA2B,EAAE,KAAK,cAAc,YAAY,SAAS;AAAA,EAChF,OAAO,EAAE,SAAS,8CAA8C,aAAa,EAAE,QAAQ,8BAA8B,GAAG,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,+BAA+B,CAAC,GAAG,OAAO;AAAA,EAC1M,QAAQ,EAAE,SAAS,8CAA8C,OAAO,gBAAgB,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,gBAAgB,CAAC,GAAG,OAAO;AAC7J,EAAE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,30 @@
1
+ import { NextResponse } from "next/server";
2
+ import { resolveCpqRouteContext } from "../../../../context.js";
3
+ import { cpqPriceAdjustmentPreviewSchema } from "../../../../../data/price-adjustments.js";
4
+ import { handleArcError } from "../../../../_helpers/handleArcError.js";
5
+ import { cpqErrorDoc, cpqQuoteIdPathParams } from "../../../../_helpers/quoteRouteOpenApi.js";
6
+ const metadata = { POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] } };
7
+ async function POST(req, { params }) {
8
+ try {
9
+ const ctx = await resolveCpqRouteContext(req);
10
+ if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
11
+ const { quoteId } = cpqQuoteIdPathParams.parse(await params);
12
+ const body = cpqPriceAdjustmentPreviewSchema.parse(await req.json());
13
+ const service = ctx.container.resolve("cpqAdjustmentService");
14
+ return NextResponse.json(await service.preview(quoteId, body.draft, body.replaceAdjustmentId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId }));
15
+ } catch (error) {
16
+ return handleArcError(error, "cpq/price-adjustments/preview.POST");
17
+ }
18
+ }
19
+ const openApi = { tag: "CPQ Quotes", pathParams: cpqQuoteIdPathParams, methods: { POST: {
20
+ summary: "Preview a draft adjustment without saving or emitting events",
21
+ requestBody: { schema: cpqPriceAdjustmentPreviewSchema },
22
+ responses: [{ status: 200, description: "Draft quote and adjustment errors; nothing persisted" }],
23
+ errors: [cpqErrorDoc(400, "Invalid draft"), cpqErrorDoc(403, "Missing permission or disabled feature"), cpqErrorDoc(404, "Quote, line or replaced adjustment not found"), cpqErrorDoc(409, "Adjustment locked or stale revision"), cpqErrorDoc(422, "Ineligible target or invalid reason")]
24
+ } } };
25
+ export {
26
+ POST,
27
+ metadata,
28
+ openApi
29
+ };
30
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/price-adjustments/preview/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveCpqRouteContext } from '../../../../context'\nimport { cpqPriceAdjustmentPreviewSchema } from '../../../../../data/price-adjustments'\nimport type { CpqAdjustmentService } from '../../../../../services/priceAdjustments/adjustmentService'\nimport { handleArcError } from '../../../../_helpers/handleArcError'\nimport { cpqErrorDoc, cpqQuoteIdPathParams } from '../../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = { POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] } }\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId } = cpqQuoteIdPathParams.parse(await params)\n const body = cpqPriceAdjustmentPreviewSchema.parse(await req.json())\n const service = ctx.container.resolve<CpqAdjustmentService>('cpqAdjustmentService')\n return NextResponse.json(await service.preview(quoteId, body.draft, body.replaceAdjustmentId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId }))\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments/preview.POST') }\n}\nexport const openApi: OpenApiRouteDoc = { tag: 'CPQ Quotes', pathParams: cpqQuoteIdPathParams, methods: { POST: {\n summary: 'Preview a draft adjustment without saving or emitting events', requestBody: { schema: cpqPriceAdjustmentPreviewSchema },\n responses: [{ status: 200, description: 'Draft quote and adjustment errors; nothing persisted' }],\n errors: [cpqErrorDoc(400, 'Invalid draft'), cpqErrorDoc(403, 'Missing permission or disabled feature'), cpqErrorDoc(404, 'Quote, line or replaced adjustment not found'), cpqErrorDoc(409, 'Adjustment locked or stale revision'), cpqErrorDoc(422, 'Ineligible target or invalid reason')],\n} } }\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,uCAAuC;AAEhD,SAAS,sBAAsB;AAC/B,SAAS,aAAa,4BAA4B;AAE3C,MAAM,WAAW,EAAE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE,EAAE;AACzH,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,QAAQ,IAAI,qBAAqB,MAAM,MAAM,MAAM;AAC3D,UAAM,OAAO,gCAAgC,MAAM,MAAM,IAAI,KAAK,CAAC;AACnE,UAAM,UAAU,IAAI,UAAU,QAA8B,sBAAsB;AAClF,WAAO,aAAa,KAAK,MAAM,QAAQ,QAAQ,SAAS,KAAK,OAAO,KAAK,qBAAqB,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe,CAAC,CAAC;AAAA,EAC/J,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,oCAAoC;AAAA,EAAE;AACvF;AACO,MAAM,UAA2B,EAAE,KAAK,cAAc,YAAY,sBAAsB,SAAS,EAAE,MAAM;AAAA,EAC9G,SAAS;AAAA,EAAgE,aAAa,EAAE,QAAQ,gCAAgC;AAAA,EAChI,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,uDAAuD,CAAC;AAAA,EAChG,QAAQ,CAAC,YAAY,KAAK,eAAe,GAAG,YAAY,KAAK,wCAAwC,GAAG,YAAY,KAAK,8CAA8C,GAAG,YAAY,KAAK,qCAAqC,GAAG,YAAY,KAAK,qCAAqC,CAAC;AAC5R,EAAE,EAAE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,47 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { resolveCpqRouteContext } from "../../../context.js";
4
+ import { cpqPriceAdjustmentInputSchema } from "../../../../data/price-adjustments.js";
5
+ import { adjustmentCommandRoute } from "../../../_helpers/adjustmentCommandRoute.js";
6
+ import { handleArcError } from "../../../_helpers/handleArcError.js";
7
+ import { cpqErrorDoc, cpqQuoteIdPathParams } from "../../../_helpers/quoteRouteOpenApi.js";
8
+ const metadata = {
9
+ GET: { requireAuth: true, requireFeatures: ["cpq.quotes.view"] },
10
+ POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] }
11
+ };
12
+ const listQuery = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(25) });
13
+ async function GET(req, { params }) {
14
+ try {
15
+ const ctx = await resolveCpqRouteContext(req);
16
+ if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
17
+ const { quoteId } = cpqQuoteIdPathParams.parse(await params);
18
+ const { page, pageSize } = listQuery.parse(Object.fromEntries(new URL(req.url).searchParams));
19
+ const service = ctx.container.resolve("cpqAdjustmentService");
20
+ return NextResponse.json(await service.list(quoteId, { organizationId: ctx.organizationId, tenantId: ctx.tenantId }, page, pageSize));
21
+ } catch (error) {
22
+ return handleArcError(error, "cpq/price-adjustments.GET");
23
+ }
24
+ }
25
+ async function POST(req, { params }) {
26
+ try {
27
+ const ctx = await resolveCpqRouteContext(req);
28
+ if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
29
+ const { quoteId } = cpqQuoteIdPathParams.parse(await params);
30
+ const body = cpqPriceAdjustmentInputSchema.parse(await req.json());
31
+ return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentInputSchema, "create");
32
+ } catch (error) {
33
+ return handleArcError(error, "cpq/price-adjustments.POST");
34
+ }
35
+ }
36
+ const errors = [cpqErrorDoc(400, "Invalid input"), cpqErrorDoc(403, "Missing permission or disabled feature"), cpqErrorDoc(404, "Quote not found"), cpqErrorDoc(409, "Adjustment locked or stale revision"), cpqErrorDoc(422, "Invalid adjustment or guard rejection")];
37
+ const openApi = { tag: "CPQ Quotes", pathParams: cpqQuoteIdPathParams, methods: {
38
+ GET: { summary: "List quote price adjustments", query: listQuery, responses: [{ status: 200, description: "Paged adjustments, pricing revision, feature state and the reason options (`reasons: [{ value, label }]`)" }], errors },
39
+ POST: { summary: "Create a negotiated quote price adjustment", requestBody: { schema: cpqPriceAdjustmentInputSchema }, responses: [{ status: 201, description: "Adjustment and updated quote" }], errors }
40
+ } };
41
+ export {
42
+ GET,
43
+ POST,
44
+ metadata,
45
+ openApi
46
+ };
47
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/price-adjustments/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqPriceAdjustmentInputSchema } from '../../../../data/price-adjustments'\nimport type { CpqAdjustmentService } from '../../../../services/priceAdjustments/adjustmentService'\nimport { adjustmentCommandRoute } from '../../../_helpers/adjustmentCommandRoute'\nimport { handleArcError } from '../../../_helpers/handleArcError'\nimport { cpqErrorDoc, cpqQuoteIdPathParams } from '../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.quotes.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] },\n}\nconst listQuery = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(25) })\ntype Params = { params: Promise<{ quoteId: string }> }\nexport async function GET(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId } = cpqQuoteIdPathParams.parse(await params)\n const { page, pageSize } = listQuery.parse(Object.fromEntries(new URL(req.url).searchParams))\n const service = ctx.container.resolve<CpqAdjustmentService>('cpqAdjustmentService')\n return NextResponse.json(await service.list(quoteId, { organizationId: ctx.organizationId, tenantId: ctx.tenantId }, page, pageSize))\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.GET') }\n}\nexport async function POST(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId } = cpqQuoteIdPathParams.parse(await params)\n const body = cpqPriceAdjustmentInputSchema.parse(await req.json())\n return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentInputSchema, 'create')\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.POST') }\n}\nconst errors = [cpqErrorDoc(400, 'Invalid input'), cpqErrorDoc(403, 'Missing permission or disabled feature'), cpqErrorDoc(404, 'Quote not found'), cpqErrorDoc(409, 'Adjustment locked or stale revision'), cpqErrorDoc(422, 'Invalid adjustment or guard rejection')]\nexport const openApi: OpenApiRouteDoc = { tag: 'CPQ Quotes', pathParams: cpqQuoteIdPathParams, methods: {\n GET: { summary: 'List quote price adjustments', query: listQuery, responses: [{ status: 200, description: 'Paged adjustments, pricing revision, feature state and the reason options (`reasons: [{ value, label }]`)' }], errors },\n POST: { summary: 'Create a negotiated quote price adjustment', requestBody: { schema: cpqPriceAdjustmentInputSchema }, responses: [{ status: 201, description: 'Adjustment and updated quote' }], errors },\n} }\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,qCAAqC;AAE9C,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,aAAa,4BAA4B;AAE3C,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE;AAC/F;AACA,MAAM,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,GAAG,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC;AAE7I,eAAsB,IAAI,KAAc,EAAE,OAAO,GAAW;AAC1D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,QAAQ,IAAI,qBAAqB,MAAM,MAAM,MAAM;AAC3D,UAAM,EAAE,MAAM,SAAS,IAAI,UAAU,MAAM,OAAO,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,YAAY,CAAC;AAC5F,UAAM,UAAU,IAAI,UAAU,QAA8B,sBAAsB;AAClF,WAAO,aAAa,KAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS,GAAG,MAAM,QAAQ,CAAC;AAAA,EACtI,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,2BAA2B;AAAA,EAAE;AAC9E;AACA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAAW;AAC3D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,QAAQ,IAAI,qBAAqB,MAAM,MAAM,MAAM;AAC3D,UAAM,OAAO,8BAA8B,MAAM,MAAM,IAAI,KAAK,CAAC;AACjE,WAAO,MAAM,uBAAuB,KAAK,KAAK,SAAS,MAAM,+BAA+B,QAAQ;AAAA,EACtG,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,4BAA4B;AAAA,EAAE;AAC/E;AACA,MAAM,SAAS,CAAC,YAAY,KAAK,eAAe,GAAG,YAAY,KAAK,wCAAwC,GAAG,YAAY,KAAK,iBAAiB,GAAG,YAAY,KAAK,qCAAqC,GAAG,YAAY,KAAK,uCAAuC,CAAC;AAC/P,MAAM,UAA2B,EAAE,KAAK,cAAc,YAAY,sBAAsB,SAAS;AAAA,EACtG,KAAK,EAAE,SAAS,gCAAgC,OAAO,WAAW,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,4GAA4G,CAAC,GAAG,OAAO;AAAA,EACjO,MAAM,EAAE,SAAS,8CAA8C,aAAa,EAAE,QAAQ,8BAA8B,GAAG,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,+BAA+B,CAAC,GAAG,OAAO;AAC3M,EAAE;",
6
+ "names": []
7
+ }
@@ -4,8 +4,10 @@ import { resolveCpqRouteContext } from "../../../context.js";
4
4
  import { cpqRecalculateSchema } from "../../../../data/validators.js";
5
5
  import { isQuotingError } from "../../../../services/cpqQuotingService.js";
6
6
  import { resolveQuotingService } from "../../../resolveQuotingService.js";
7
+ import { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from "../../../_helpers/quoteMutationGuard.js";
8
+ import { cpqErrorDoc, cpqQuoteIdPathParams } from "../../../_helpers/quoteRouteOpenApi.js";
7
9
  const metadata = {
8
- POST: { requireAuth: true, requireFeatures: ["cpq.quotes.view"] }
10
+ POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
9
11
  };
10
12
  async function POST(req, { params }) {
11
13
  try {
@@ -14,8 +16,16 @@ async function POST(req, { params }) {
14
16
  const { quoteId } = await params;
15
17
  const body = cpqRecalculateSchema.parse(await req.json());
16
18
  const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
19
+ const guard = body.save ? await runCpqMutationGuards(ctx, req, {
20
+ resourceKind: CPQ_QUOTE_RESOURCE_KIND,
21
+ resourceId: quoteId,
22
+ operation: "custom",
23
+ mutationPayload: { action: "recalculate" }
24
+ }) : null;
25
+ if (guard && !guard.ok) return guard.response;
17
26
  const service = resolveQuotingService(ctx);
18
27
  const result = await service.recalculate(quoteId, body, scope);
28
+ if (guard) await guard.runAfterSuccess();
19
29
  return NextResponse.json(result);
20
30
  } catch (err) {
21
31
  if (err instanceof z.ZodError) {
@@ -28,8 +38,28 @@ async function POST(req, { params }) {
28
38
  return NextResponse.json({ error: "Internal error" }, { status: 500 });
29
39
  }
30
40
  }
41
+ const openApi = {
42
+ tag: "CPQ Quotes",
43
+ summary: "CPQ quote recalculation",
44
+ pathParams: cpqQuoteIdPathParams,
45
+ methods: {
46
+ POST: {
47
+ summary: "Re-price every line of a CPQ quote",
48
+ description: "Re-resolves charges and price rules. With `save: true` the result is persisted (refused on accepted, rejected and cancelled quotes) and mutation guards (resource kind `cpq.quote`, action `recalculate`) run first. Frozen Amend mirror lines keep their stored price.",
49
+ requestBody: { schema: cpqRecalculateSchema },
50
+ responses: [{ status: 200, description: "Recalculated quote" }],
51
+ errors: [
52
+ cpqErrorDoc(400, "Validation error"),
53
+ cpqErrorDoc(404, "Quote not found"),
54
+ cpqErrorDoc(409, "Saved recalculation on a terminal quote"),
55
+ cpqErrorDoc(422, "Blocked by a mutation guard")
56
+ ]
57
+ }
58
+ }
59
+ };
31
60
  export {
32
61
  POST,
33
- metadata
62
+ metadata,
63
+ openApi
34
64
  };
35
65
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/recalculate/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqRecalculateSchema } from '../../../../data/validators'\nimport { isQuotingError } from '../../../../services/cpqQuotingService'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.view'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqRecalculateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = resolveQuotingService(ctx)\n const result = await service.recalculate(quoteId, body, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/recalculate.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AAE/B,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAClE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,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,OAAO,qBAAqB,MAAM,MAAM,IAAI,KAAK,CAAC;AACxD,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,YAAY,SAAS,MAAM,KAAK;AAC7D,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9F;AACA,QAAI,eAAe,GAAG,GAAG;AACvB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,2CAA2C,GAAG;AAC5D,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 { resolveCpqRouteContext } from '../../../context'\nimport { cpqRecalculateSchema } from '../../../../data/validators'\nimport { isQuotingError } from '../../../../services/cpqQuotingService'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\nimport { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from '../../../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { cpqErrorDoc, cpqQuoteIdPathParams } from '../../../_helpers/quoteRouteOpenApi'\n\n// Recalculation re-resolves and (with `save: true`) persists prices, so it\n// needs the same feature as any other quote write. The quote page always\n// saves; viewers read stored prices through GET /api/cpq/quotes/{quoteId}.\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqRecalculateSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Only a saved recalculation writes; a dry run needs no guard.\n const guard = body.save\n ? await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_RESOURCE_KIND,\n resourceId: quoteId,\n operation: 'custom',\n mutationPayload: { action: 'recalculate' },\n })\n : null\n if (guard && !guard.ok) return guard.response\n\n const service = resolveQuotingService(ctx)\n const result = await service.recalculate(quoteId, body, scope)\n if (guard) await guard.runAfterSuccess()\n return NextResponse.json(result)\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/recalculate.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'CPQ quote recalculation',\n pathParams: cpqQuoteIdPathParams,\n methods: {\n POST: {\n summary: 'Re-price every line of a CPQ quote',\n description:\n 'Re-resolves charges and price rules. With `save: true` the result is persisted (refused on accepted, rejected and cancelled quotes) and mutation guards (resource kind `cpq.quote`, action `recalculate`) run first. Frozen Amend mirror lines keep their stored price.',\n requestBody: { schema: cpqRecalculateSchema },\n responses: [{ status: 200, description: 'Recalculated quote' }],\n errors: [\n cpqErrorDoc(400, 'Validation error'),\n cpqErrorDoc(404, 'Quote not found'),\n cpqErrorDoc(409, 'Saved recalculation on a terminal quote'),\n cpqErrorDoc(422, 'Blocked by a mutation guard'),\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,4BAA4B;AACrC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,yBAAyB,4BAA4B;AAE9D,SAAS,aAAa,4BAA4B;AAK3C,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,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,OAAO,qBAAqB,MAAM,MAAM,IAAI,KAAK,CAAC;AACxD,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAG3E,UAAM,QAAQ,KAAK,OACf,MAAM,qBAAqB,KAAK,KAAK;AAAA,MACnC,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,iBAAiB,EAAE,QAAQ,cAAc;AAAA,IAC3C,CAAC,IACD;AACJ,QAAI,SAAS,CAAC,MAAM,GAAI,QAAO,MAAM;AAErC,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,YAAY,SAAS,MAAM,KAAK;AAC7D,QAAI,MAAO,OAAM,MAAM,gBAAgB;AACvC,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9F;AACA,QAAI,eAAe,GAAG,GAAG;AACvB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,2CAA2C,GAAG;AAC5D,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;AAAA,EACZ,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,aAAa,EAAE,QAAQ,qBAAqB;AAAA,MAC5C,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,qBAAqB,CAAC;AAAA,MAC9D,QAAQ;AAAA,QACN,YAAY,KAAK,kBAAkB;AAAA,QACnC,YAAY,KAAK,iBAAiB;AAAA,QAClC,YAAY,KAAK,yCAAyC;AAAA,QAC1D,YAAY,KAAK,6BAA6B;AAAA,MAChD;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,11 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { resolveCpqRouteContext } from "../../../context.js";
3
+ import { resolveAuditActor } from "../../../../lib/auditActor.js";
3
4
  import { cpqTransitionStatusSchema } from "../../../../data/validators.js";
4
5
  import { resolveQuotingService } from "../../../resolveQuotingService.js";
5
6
  import { handleArcError } from "../../../_helpers/handleArcError.js";
7
+ import { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from "../../../_helpers/quoteMutationGuard.js";
8
+ import { cpqErrorDoc, cpqQuoteIdPathParams } from "../../../_helpers/quoteRouteOpenApi.js";
6
9
  const metadata = {
7
10
  POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
8
11
  };
@@ -13,15 +16,43 @@ async function POST(req, { params }) {
13
16
  const { quoteId } = await params;
14
17
  const body = cpqTransitionStatusSchema.parse(await req.json());
15
18
  const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
19
+ const guard = await runCpqMutationGuards(ctx, req, {
20
+ resourceKind: CPQ_QUOTE_RESOURCE_KIND,
21
+ resourceId: quoteId,
22
+ operation: "custom",
23
+ mutationPayload: { action: "transition_status", targetStatus: body.targetStatus }
24
+ });
25
+ if (!guard.ok) return guard.response;
16
26
  const service = resolveQuotingService(ctx);
17
- const result = await service.transitionStatus(quoteId, body.targetStatus, scope);
27
+ const result = await service.transitionStatus(quoteId, body.targetStatus, scope, resolveAuditActor(ctx.auth));
28
+ await guard.runAfterSuccess();
18
29
  return NextResponse.json(result);
19
30
  } catch (err) {
20
31
  return handleArcError(err, "cpq/quotes/[quoteId]/status.POST");
21
32
  }
22
33
  }
34
+ const openApi = {
35
+ tag: "CPQ Quotes",
36
+ summary: "CPQ quote status",
37
+ pathParams: cpqQuoteIdPathParams,
38
+ methods: {
39
+ POST: {
40
+ summary: "Transition a CPQ quote to another status",
41
+ description: "Operator-driven status change. Registered mutation guards (resource kind `cpq.quote`, action `transition_status`) run first \u2014 projects use them to enforce approvals.",
42
+ requestBody: { schema: cpqTransitionStatusSchema },
43
+ responses: [{ status: 200, description: "Quote with its new status" }],
44
+ errors: [
45
+ cpqErrorDoc(400, "Validation error"),
46
+ cpqErrorDoc(404, "Quote not found"),
47
+ cpqErrorDoc(409, "Transition not allowed"),
48
+ cpqErrorDoc(422, "ARC validation failed or blocked by a mutation guard")
49
+ ]
50
+ }
51
+ }
52
+ };
23
53
  export {
24
54
  POST,
25
- metadata
55
+ metadata,
56
+ openApi
26
57
  };
27
58
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/status/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqTransitionStatusSchema } from '../../../../data/validators'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\nimport { handleArcError } from '../../../_helpers/handleArcError'\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 }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqTransitionStatusSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = resolveQuotingService(ctx)\n const result = await service.transitionStatus(quoteId, body.targetStatus, scope)\n return NextResponse.json(result)\n } catch (err) {\n return handleArcError(err, 'cpq/quotes/[quoteId]/status.POST')\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAExB,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,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,OAAO,0BAA0B,MAAM,MAAM,IAAI,KAAK,CAAC;AAC7D,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,iBAAiB,SAAS,KAAK,cAAc,KAAK;AAC/E,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,WAAO,eAAe,KAAK,kCAAkC;AAAA,EAC/D;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { resolveAuditActor } from '../../../../lib/auditActor'\nimport { cpqTransitionStatusSchema } from '../../../../data/validators'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\nimport { handleArcError } from '../../../_helpers/handleArcError'\nimport { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from '../../../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { cpqErrorDoc, cpqQuoteIdPathParams } 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 }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqTransitionStatusSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Projects enforce approval rules here (e.g. block `ready \u2192 with_customer`\n // until a discounted quote is approved).\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_RESOURCE_KIND,\n resourceId: quoteId,\n operation: 'custom',\n mutationPayload: { action: 'transition_status', targetStatus: body.targetStatus },\n })\n if (!guard.ok) return guard.response\n\n const service = resolveQuotingService(ctx)\n const result = await service.transitionStatus(quoteId, body.targetStatus, scope, resolveAuditActor(ctx.auth))\n await guard.runAfterSuccess()\n return NextResponse.json(result)\n } catch (err) {\n return handleArcError(err, 'cpq/quotes/[quoteId]/status.POST')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'CPQ quote status',\n pathParams: cpqQuoteIdPathParams,\n methods: {\n POST: {\n summary: 'Transition a CPQ quote to another status',\n description:\n 'Operator-driven status change. Registered mutation guards (resource kind `cpq.quote`, action `transition_status`) run first \u2014 projects use them to enforce approvals.',\n requestBody: { schema: cpqTransitionStatusSchema },\n responses: [{ status: 200, description: 'Quote with its new status' }],\n errors: [\n cpqErrorDoc(400, 'Validation error'),\n cpqErrorDoc(404, 'Quote not found'),\n cpqErrorDoc(409, 'Transition not allowed'),\n cpqErrorDoc(422, 'ARC validation failed or blocked by a mutation guard'),\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;AAClC,SAAS,iCAAiC;AAC1C,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,yBAAyB,4BAA4B;AAE9D,SAAS,aAAa,4BAA4B;AAE3C,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,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,OAAO,0BAA0B,MAAM,MAAM,IAAI,KAAK,CAAC;AAC7D,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAI3E,UAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,MACjD,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,iBAAiB,EAAE,QAAQ,qBAAqB,cAAc,KAAK,aAAa;AAAA,IAClF,CAAC;AACD,QAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAE5B,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,iBAAiB,SAAS,KAAK,cAAc,OAAO,kBAAkB,IAAI,IAAI,CAAC;AAC5G,UAAM,MAAM,gBAAgB;AAC5B,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,WAAO,eAAe,KAAK,kCAAkC;AAAA,EAC/D;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,aAAa,EAAE,QAAQ,0BAA0B;AAAA,MACjD,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,4BAA4B,CAAC;AAAA,MACrE,QAAQ;AAAA,QACN,YAAY,KAAK,kBAAkB;AAAA,QACnC,YAAY,KAAK,iBAAiB;AAAA,QAClC,YAAY,KAAK,wBAAwB;AAAA,QACzC,YAAY,KAAK,sDAAsD;AAAA,MACzE;AAAA,IACF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,8 +1,10 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
+ import { cpqErrorDoc } from "../../_helpers/quoteRouteOpenApi.js";
3
4
  import { resolveCpqRouteContext } from "../../context.js";
4
5
  import { cpqPricePreviewSchema } from "../../../data/validators.js";
5
6
  import { getBaseCurrencyCode } from "../../../services/types.js";
7
+ import { withChargeWaterfall, withoutChargeWaterfall } from "../../../services/priceAdjustments/chargeWaterfall.js";
6
8
  const metadata = {
7
9
  POST: { requireAuth: true, requireFeatures: ["cpq.quotes.view"] }
8
10
  };
@@ -48,9 +50,10 @@ async function POST(req) {
48
50
  unitPrice: c.unitPrice,
49
51
  note: c.note ?? "Usage-based charge \u2014 billed on actual consumption."
50
52
  }));
53
+ const presentCharge = body.includeWaterfall ? withChargeWaterfall : withoutChargeWaterfall;
51
54
  return NextResponse.json({
52
- primaryCharges,
53
- childItems,
55
+ primaryCharges: primaryCharges.map(presentCharge),
56
+ childItems: childItems.map((item) => ({ ...item, charges: item.charges.map(presentCharge) })),
54
57
  totals: { nrcTotal, mrcTotal, usageCharges },
55
58
  currencyCode
56
59
  });
@@ -62,8 +65,45 @@ async function POST(req) {
62
65
  return NextResponse.json({ error: "Internal error" }, { status: 500 });
63
66
  }
64
67
  }
68
+ const chargeDoc = z.object({
69
+ chargeCode: z.string(),
70
+ chargeName: z.string(),
71
+ chargeType: z.enum(["nrc", "mrc", "usage"]),
72
+ pricingMethod: z.enum(["flat", "per_unit", "volume", "tiered"]),
73
+ currencyCode: z.string(),
74
+ unitPrice: z.number(),
75
+ quantity: z.number().nullable(),
76
+ totalPrice: z.number().nullable(),
77
+ listUnitPrice: z.number().optional(),
78
+ listTotalPrice: z.number().nullable().optional(),
79
+ standardUnitPrice: z.number().optional(),
80
+ standardTotalPrice: z.number().nullable().optional(),
81
+ adjustments: z.array(z.object({
82
+ origin: z.literal("rule"),
83
+ ruleCode: z.string(),
84
+ ruleName: z.string(),
85
+ ruleType: z.string(),
86
+ value: z.number(),
87
+ unitPriceBefore: z.number(),
88
+ unitPriceAfter: z.number(),
89
+ delta: z.number()
90
+ }).passthrough()).nullish()
91
+ }).passthrough();
92
+ const openApi = { tag: "CPQ Pricing", methods: { POST: {
93
+ summary: "Calculate catalog prices and price rules without saving",
94
+ description: "Manual quote adjustments are never applied. Set includeWaterfall=true to return list and standard prices; these fields are omitted by default.",
95
+ requestBody: { schema: cpqPricePreviewSchema },
96
+ responses: [{ status: 200, description: "Resolved rule prices and optional waterfall", schema: z.object({
97
+ primaryCharges: z.array(chargeDoc),
98
+ childItems: z.array(z.object({ offeringId: z.string().nullable(), productId: z.string().nullable(), charges: z.array(chargeDoc) })),
99
+ totals: z.object({ nrcTotal: z.number(), mrcTotal: z.number(), usageCharges: z.array(z.unknown()) }),
100
+ currencyCode: z.string()
101
+ }) }],
102
+ errors: [cpqErrorDoc(400, "Invalid pricing request"), cpqErrorDoc(500, "Price calculation failed")]
103
+ } } };
65
104
  export {
66
105
  POST,
67
- metadata
106
+ metadata,
107
+ openApi
68
108
  };
69
109
  //# sourceMappingURL=route.js.map