@dainamite/cpq 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/MIGRATION.md +101 -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/handleArcError.js +2 -2
  8. package/dist/modules/cpq/api/_helpers/handleArcError.js.map +2 -2
  9. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js +23 -0
  10. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js.map +7 -0
  11. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js +19 -0
  12. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js.map +7 -0
  13. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js +2 -2
  14. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +2 -2
  15. package/dist/modules/cpq/api/orders/[orderId]/route.js +19 -3
  16. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
  17. package/dist/modules/cpq/api/orders/[orderId]/status/route.js +2 -2
  18. package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +2 -2
  19. package/dist/modules/cpq/api/orders/route.js +49 -4
  20. package/dist/modules/cpq/api/orders/route.js.map +2 -2
  21. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +2 -2
  22. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js.map +2 -2
  23. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js +54 -0
  24. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js.map +7 -0
  25. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +45 -5
  26. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
  27. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -2
  28. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +2 -2
  29. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js +47 -0
  30. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js.map +7 -0
  31. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js +30 -0
  32. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js.map +7 -0
  33. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js +47 -0
  34. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js.map +7 -0
  35. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +34 -4
  36. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
  37. package/dist/modules/cpq/api/quotes/[quoteId]/route.js +2 -2
  38. package/dist/modules/cpq/api/quotes/[quoteId]/route.js.map +2 -2
  39. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +33 -2
  40. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +2 -2
  41. package/dist/modules/cpq/api/quotes/price/route.js +43 -3
  42. package/dist/modules/cpq/api/quotes/price/route.js.map +2 -2
  43. package/dist/modules/cpq/api/quotes/route.js +2 -2
  44. package/dist/modules/cpq/api/quotes/route.js.map +2 -2
  45. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +1 -1
  46. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +1 -1
  47. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +1 -1
  48. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +1 -1
  49. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +22 -8
  50. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  51. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +1 -1
  52. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +89 -232
  53. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  54. package/dist/modules/cpq/backend/cpq/orders/page.js.map +1 -1
  55. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
  56. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
  57. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +1 -1
  58. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +1 -1
  59. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +205 -0
  60. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
  61. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
  62. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
  63. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
  64. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
  65. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
  66. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
  67. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
  68. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
  69. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +144 -221
  70. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
  71. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +1 -1
  72. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
  73. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
  74. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +1 -1
  75. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +1 -1
  76. package/dist/modules/cpq/ce.js +8 -0
  77. package/dist/modules/cpq/ce.js.map +2 -2
  78. package/dist/modules/cpq/cli.js +34 -1
  79. package/dist/modules/cpq/cli.js.map +2 -2
  80. package/dist/modules/cpq/commands/price-adjustments.js +107 -0
  81. package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
  82. package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
  83. package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
  84. package/dist/modules/cpq/components/ChargeBreakdownTable.js +154 -0
  85. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
  86. package/dist/modules/cpq/components/CpqListView.js.map +2 -2
  87. package/dist/modules/cpq/components/PriceWaterfall.js +66 -0
  88. package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
  89. package/dist/modules/cpq/components/PricingSummaryCard.js +117 -0
  90. package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
  91. package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
  92. package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
  93. package/dist/modules/cpq/data/entities.js +97 -0
  94. package/dist/modules/cpq/data/entities.js.map +2 -2
  95. package/dist/modules/cpq/data/price-adjustments.js +70 -0
  96. package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
  97. package/dist/modules/cpq/data/validators.js +4 -0
  98. package/dist/modules/cpq/data/validators.js.map +2 -2
  99. package/dist/modules/cpq/di.js +6 -0
  100. package/dist/modules/cpq/di.js.map +2 -2
  101. package/dist/modules/cpq/events.js +47 -0
  102. package/dist/modules/cpq/events.js.map +2 -2
  103. package/dist/modules/cpq/index.js +1 -1
  104. package/dist/modules/cpq/index.js.map +2 -2
  105. package/dist/modules/cpq/lib/auditActor.js +10 -0
  106. package/dist/modules/cpq/lib/auditActor.js.map +7 -0
  107. package/dist/modules/cpq/lib/formatMoney.js +7 -0
  108. package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
  109. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
  110. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
  111. package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
  112. package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
  113. package/dist/modules/cpq/lib/quotePermissions.js +13 -0
  114. package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
  115. package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
  116. package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
  117. package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
  118. package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
  119. package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
  120. package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
  121. package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
  122. package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
  123. package/dist/modules/cpq/services/cpqOrderService.js +34 -30
  124. package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
  125. package/dist/modules/cpq/services/cpqPricingService.js +7 -0
  126. package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
  127. package/dist/modules/cpq/services/cpqQuotingService.js +339 -111
  128. package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
  129. package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
  130. package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
  131. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
  132. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
  133. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
  134. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
  135. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
  136. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
  137. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
  138. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
  139. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
  140. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
  141. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
  142. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
  143. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
  144. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
  145. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
  146. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
  147. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
  148. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
  149. package/dist/modules/cpq/services/types.js.map +2 -2
  150. package/dist/modules/cpq/setup.js +6 -0
  151. package/dist/modules/cpq/setup.js.map +2 -2
  152. package/package.json +9 -9
  153. package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +8 -5
  154. package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
  155. package/src/modules/cpq/acl.ts +1 -0
  156. package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
  157. package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
  158. package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
  159. package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
  160. package/src/modules/cpq/api/_helpers/handleArcError.ts +12 -12
  161. package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
  162. package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
  163. package/src/modules/cpq/api/orders/[orderId]/activate/route.ts +2 -2
  164. package/src/modules/cpq/api/orders/[orderId]/route.ts +20 -2
  165. package/src/modules/cpq/api/orders/[orderId]/status/route.ts +2 -2
  166. package/src/modules/cpq/api/orders/route.ts +53 -3
  167. package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +2 -2
  168. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
  169. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +51 -4
  170. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
  171. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
  172. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
  173. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
  174. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
  175. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +42 -3
  176. package/src/modules/cpq/api/quotes/[quoteId]/route.ts +2 -2
  177. package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
  178. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
  179. package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
  180. package/src/modules/cpq/api/quotes/price/route.ts +28 -2
  181. package/src/modules/cpq/api/quotes/route.ts +2 -2
  182. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +1 -1
  183. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +1 -1
  184. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +1 -1
  185. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
  186. package/src/modules/cpq/backend/cpq/offerings/page.tsx +1 -1
  187. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +60 -269
  188. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
  189. package/src/modules/cpq/backend/cpq/orders/page.tsx +1 -1
  190. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +1 -1
  191. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  192. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
  193. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
  194. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
  195. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
  196. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
  197. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
  198. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +178 -266
  199. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
  200. package/src/modules/cpq/backend/cpq/quotes/page.tsx +1 -1
  201. package/src/modules/cpq/backend/cpq/specifications/page.tsx +1 -1
  202. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  203. package/src/modules/cpq/ce.ts +8 -0
  204. package/src/modules/cpq/cli.ts +48 -1
  205. package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
  206. package/src/modules/cpq/commands/price-adjustments.ts +90 -0
  207. package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
  208. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +201 -0
  209. package/src/modules/cpq/components/CpqListView.tsx +2 -1
  210. package/src/modules/cpq/components/PriceWaterfall.tsx +94 -0
  211. package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
  212. package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
  213. package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
  214. package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
  215. package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
  216. package/src/modules/cpq/data/entities.ts +83 -0
  217. package/src/modules/cpq/data/price-adjustments.ts +67 -0
  218. package/src/modules/cpq/data/validators.ts +3 -0
  219. package/src/modules/cpq/di.ts +6 -0
  220. package/src/modules/cpq/events.ts +53 -2
  221. package/src/modules/cpq/index.ts +1 -1
  222. package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
  223. package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
  224. package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
  225. package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
  226. package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
  227. package/src/modules/cpq/lib/auditActor.ts +13 -0
  228. package/src/modules/cpq/lib/formatMoney.ts +4 -0
  229. package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
  230. package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
  231. package/src/modules/cpq/lib/quotePermissions.ts +22 -0
  232. package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
  233. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
  234. package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
  235. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
  236. package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
  237. package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
  238. package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
  239. package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
  240. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
  241. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
  242. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
  243. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
  244. package/src/modules/cpq/services/__tests__/errorBrands.test.ts +72 -0
  245. package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
  246. package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
  247. package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
  248. package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
  249. package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
  250. package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
  251. package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
  252. package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
  253. package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
  254. package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
  255. package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
  256. package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
  257. package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
  258. package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
  259. package/src/modules/cpq/services/cpqOrderService.ts +32 -28
  260. package/src/modules/cpq/services/cpqPricingService.ts +8 -0
  261. package/src/modules/cpq/services/cpqQuotingService.ts +388 -125
  262. package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
  263. package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
  264. package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
  265. package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
  266. package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
  267. package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
  268. package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
  269. package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
  270. package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
  271. package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
  272. package/src/modules/cpq/services/types.ts +34 -1
  273. package/src/modules/cpq/setup.ts +7 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,75 @@
1
1
  # @dainamite/cpq
2
2
 
3
+ ## 0.6.0
4
+
5
+ ### Minor Changes
6
+
7
+ - adcb50f: Negotiated prices on quotes (manual price adjustments) with a price waterfall.
8
+
9
+ - New table `cpq_quote_price_adjustments` and column
10
+ `cpq_quote_configurations.pricing_revision` (migration
11
+ `Migration20261110213052`). **Upgrade steps for existing tenants (required):**
12
+ `yarn mercato db migrate`, then `yarn mercato cpq seed-price-adjustments`
13
+ (rollout toggle + reason dictionary) and `yarn mercato auth sync-role-acls`
14
+ (`cpq.quotes.adjust_price` grant). Without them writes return `403` and the
15
+ UI hides the actions. New tenants get all three at initialization.
16
+ - Reps with `cpq.quotes.manage` + `cpq.quotes.adjust_price` can override a
17
+ charge's unit price, take an amount off a charge or give a percentage on a
18
+ charge or line (`/api/cpq/quotes/{quoteId}/price-adjustments`, plus a
19
+ no-write `preview`). Adjustments are permanent, never exceed the standard
20
+ price, never compound, and lock once the quote leaves New / Incomplete /
21
+ Ready. Rollout toggle `cpq.manual_price_adjustments` (seeded on) and reason
22
+ dictionary `cpq.price_adjustment_reason`.
23
+ - Charges gain additive `listUnitPrice`, `listTotalPrice`, `standardUnitPrice`,
24
+ `standardTotalPrice` and `adjustmentErrors`; adjustment entries gain
25
+ `origin: 'rule' | 'manual'`. `pricingSummary` gains list / standard totals,
26
+ rule and manual discount totals, deal value, discount depth and
27
+ `snapshotHash`; `QuoteResult` gains `pricingRevision`. `discountTotal` is
28
+ unchanged.
29
+ - New events `cpq.quote.pricing_changed` and `cpq.quote.status_changed`
30
+ (after commit, with the pricing snapshot) for project approval workflows.
31
+ - Order conversion copies stored prices and returns `422 ADJUSTMENT_INVALID`
32
+ when a stored adjustment is invalid. `POST /api/cpq/quotes/price` accepts
33
+ `includeWaterfall` and never applies negotiated prices.
34
+ - Quote, order and subscription pages show List / Offer prices and the
35
+ waterfall; the quote page adds the adjustment dialog and panel.
36
+
37
+ - 8d57b1b: Harden quote pricing ahead of manual price adjustments.
38
+
39
+ - `POST /api/cpq/quotes/{quoteId}/recalculate` now requires `cpq.quotes.manage`
40
+ (was `cpq.quotes.view`), and a saved recalculation (`save: true`) on an
41
+ accepted, rejected or cancelled quote returns `409`. Integrations that
42
+ recalculate with a view-only API key must be granted `cpq.quotes.manage`.
43
+ - Amend quotes no longer re-price items the operator does not touch: mirror
44
+ lines keep the subscription's stored price until their configuration,
45
+ quantity, term, dates or usage estimates change.
46
+ - Status transitions, line add / update / remove, saved recalculations and
47
+ quote-to-order conversion run every registered mutation guard
48
+ (resource kinds `cpq.quote` and `cpq.quote_line`), so projects can enforce
49
+ approvals. No behaviour change when no guard is registered.
50
+
51
+ ## 0.5.0
52
+
53
+ ### Minor Changes
54
+
55
+ - d7ce539: Require Open Mercato `0.9.0`. Peer ranges move from `^0.6.7` to `^0.9.0` for
56
+ `@open-mercato/core`, `shared`, `ui`, `events` and `queue`, and the MikroORM
57
+ peers from `^7.1.5` to `^7.1.14`. Consumers on Open Mercato `0.6.x`–`0.8.x`
58
+ must upgrade before taking these versions.
59
+
60
+ `@dainamite/cpq` and `@dainamite/billing` now peer on `@tanstack/react-table`
61
+ `^9.0.0` (was `^8.0.0 || ^9.0.0`): Open Mercato 0.7 moved its `DataTable` to
62
+ TanStack Table v9, and list pages now import `LegacyColumnDef` from
63
+ `@tanstack/react-table/legacy`, which v8 does not have.
64
+
65
+ `@dainamite/cpq` also fixes CPQ quote and order API routes answering `500`
66
+ instead of the service's typed status (e.g. `404 Quote not found`). Next
67
+ bundles the package once per server layer and the DI container is shared, so
68
+ the route could catch a `QuotingError` / `OrderError` from the other copy of
69
+ the class and `instanceof` failed. Both classes now carry a `Symbol.for`
70
+ brand; new `isQuotingError` / `isOrderError` guards are exported alongside
71
+ them and should be used instead of `instanceof` in route handlers.
72
+
3
73
  ## 0.4.0
4
74
 
5
75
  ### Minor Changes
package/MIGRATION.md CHANGED
@@ -166,6 +166,107 @@ the `@dainamite/cpq` package, per
166
166
 
167
167
  ## Schema / API changelog (track breaking changes here)
168
168
 
169
+ ### 2026-11-10 — Manual price adjustments and price waterfall (price adjustments, Phase 1)
170
+
171
+ Spec: `.ai/specs/2026-11-10-cpq-manual-price-adjustments.md`. Manual:
172
+ `manuals/cpq-price-adjustments.md`.
173
+
174
+ **Schema (additive).** Migration `Migration20261110213052` adds
175
+ `cpq_quote_price_adjustments` (tenant-scoped, soft-deleted, FK-string ids only)
176
+ and `cpq_quote_configurations.pricing_revision int default 0`. Rollback drops
177
+ both. No data migration: existing quotes keep their stored charges; list /
178
+ standard prices are derived for responses until the next saved recalculation.
179
+
180
+ **Upgrade steps for existing tenants (required).** The toggle, the reason
181
+ dictionary and the role grant are created by `setup.ts` at tenant
182
+ initialization only. After `yarn mercato db migrate`, run
183
+ `yarn mercato cpq seed-price-adjustments` (idempotent; `--tenant` / `--org`
184
+ to target one) and `yarn mercato auth sync-role-acls`. Skipping them leaves the
185
+ feature fail-closed: writes `403`, actions hidden, and every reason
186
+ `REASON_NOT_FOUND` if the toggle is switched on by hand.
187
+
188
+ **Write serialization.** Every pricing write (line add / update / remove, ARC
189
+ target detach, saved recalculation, clone, status change, adjustment CRUD,
190
+ order conversion) runs in
191
+ a transaction that locks the quote row (`SELECT … FOR UPDATE`) and bumps
192
+ `pricing_revision`. Adjustment writes accept `expectedRevision` and return
193
+ `409 STALE_REVISION` on mismatch.
194
+
195
+ **Response shapes (additive).** See the changeset. `discountTotal` keeps its
196
+ old meaning (rule discounts only) for existing consumers; use
197
+ `ruleDiscountTotal` / `manualDiscountTotal` for the split.
198
+
199
+ **Events.** `cpq.quote.pricing_changed` fires after a committed save whose
200
+ `snapshotHash` changed; `cpq.quote.status_changed` after every committed status
201
+ change (`cause: manual | auto`). Quotes saved before this release have no
202
+ stored hash: their first save records a baseline silently. New and ARC quotes
203
+ start with an empty-quote hash, so their first priced line is a signal.
204
+ `transitionStatus(quoteId, status, scope, actorUserId?)` gained an optional
205
+ actor argument.
206
+
207
+ **Conversion.** A quote whose stored waterfall contains an invalid manual
208
+ adjustment can no longer be converted (`422 ADJUSTMENT_INVALID` with
209
+ `lineId` / `code`).
210
+
211
+ ### 2026-11-10 — Quote pricing integrity (price adjustments, Phase 1)
212
+
213
+ Spec: `.ai/specs/2026-11-10-cpq-quote-pricing-integrity.md`. No schema change.
214
+
215
+ **Recalculate needs `cpq.quotes.manage` (breaking for view-only callers).**
216
+ `POST /api/cpq/quotes/{quoteId}/recalculate` required only `cpq.quotes.view`,
217
+ so a read-only user or API key could persist re-priced quotes. It now requires
218
+ `cpq.quotes.manage`. A saved recalculation (`save: true`) on an `accepted`,
219
+ `rejected` or `cancelled` quote returns `409` — an accepted quote's prices are
220
+ the basis for order conversion. Dry runs (`save: false`) on terminal quotes
221
+ still work for managers.
222
+
223
+ **Amend mirror lines are frozen (behaviour change).** Engine-priced mirror
224
+ lines on Amend quotes were re-priced at today's catalog price on every
225
+ recalculation, and activation wrote that price back to the subscription item.
226
+ They now carry `_arcPriceFrozen: true` in the line `configuration` and keep the
227
+ source item's stored charges until a pricing input of the line changes
228
+ (configuration — ARC markers and key order ignored — quantity, `termMonths`,
229
+ `startDate`, `endDate`, `usageEstimates`); then the marker is dropped and the
230
+ line is re-priced as before. Renew and Cancel quotes are unchanged. Amend
231
+ quotes created before this release keep the old behaviour. The marker never
232
+ reaches subscription items (stripped at activation) and mirror lines ignore any
233
+ stale `_arc*` keys on an item's configuration.
234
+
235
+ **Mutation guards on quote writes.** The status, items, recalculate and
236
+ order-conversion routes now call `runRouteMutationGuards`
237
+ (`@open-mercato/shared/lib/crud/route-mutation-guard`) with resource kinds
238
+ `cpq.quote` (actions `transition_status`, `recalculate`, `convert_to_order`)
239
+ and `cpq.quote_line` (create / update / delete). A guard can block the write or
240
+ modify the line payload; after-success hooks run once the write commits.
241
+ These routes now export `openApi`.
242
+
243
+ ### 2026-11-03 — Open Mercato 0.9.0 peers; `isQuotingError` / `isOrderError`
244
+
245
+ **Peer ranges (breaking for consumers).** `@open-mercato/*` `^0.9.0`,
246
+ MikroORM `^7.1.14`, `@tanstack/react-table` `^9.0.0` (list pages import
247
+ `LegacyColumnDef` from `@tanstack/react-table/legacy`, absent in v8). See
248
+ `.changeset/open-mercato-090-peers.md`.
249
+
250
+ **Do not use `instanceof QuotingError` / `instanceof OrderError` across the
251
+ DI boundary.** Next bundles this package once per server layer (page SSR vs
252
+ route handler) while the DI container is shared through `globalThis`, so a
253
+ route can catch an error built from the other copy of the class and
254
+ `instanceof` is `false` — a typed 404/409 becomes a 500. This was already
255
+ true before this release; it was just undetected. Both classes now carry a
256
+ `Symbol.for` brand, and two guards are exported next to them:
257
+
258
+ ```ts
259
+ import { isQuotingError } from '@dainamite/cpq/modules/cpq/services/cpqQuotingService'
260
+ import { isOrderError } from '@dainamite/cpq/modules/cpq/services/cpqOrderService'
261
+
262
+ } catch (err) {
263
+ if (isQuotingError(err)) return NextResponse.json({ error: err.message }, { status: err.status })
264
+ ```
265
+
266
+ Consumer code that copied CPQ's old route catch blocks should switch to the
267
+ guards. `instanceof` remains fine inside the service layer itself, where the
268
+ thrower and the catcher are the same module copy.
269
+
169
270
  ### 2026-06-02 — XD-297 Charge model / pricing method split
170
271
 
171
272
  Splits the conflated `cpq_product_charges.pricing_method` into two orthogonal
@@ -1,4 +1,5 @@
1
1
  const features = [
2
+ { id: "cpq.quotes.adjust_price", title: "Adjust CPQ quote prices", module: "cpq" },
2
3
  { id: "cpq.quotes.view", title: "View CPQ Quotes", module: "cpq" },
3
4
  { id: "cpq.quotes.manage", title: "Manage CPQ Quotes", module: "cpq" },
4
5
  { id: "cpq.pricing.view", title: "View Pricing", module: "cpq" },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/modules/cpq/acl.ts"],
4
- "sourcesContent": ["export const features = [\n { id: 'cpq.quotes.view', title: 'View CPQ Quotes', module: 'cpq' },\n { id: 'cpq.quotes.manage', title: 'Manage CPQ Quotes', module: 'cpq' },\n { id: 'cpq.pricing.view', title: 'View Pricing', module: 'cpq' },\n { id: 'cpq.pricing.manage', title: 'Manage Pricing', module: 'cpq' },\n { id: 'cpq.products.view', title: 'View CPQ Product Config', module: 'cpq' },\n { id: 'cpq.products.manage', title: 'Manage CPQ Product Config', module: 'cpq' },\n { id: 'cpq.specifications.view', title: 'View Product Specifications', module: 'cpq' },\n { id: 'cpq.specifications.manage', title: 'Manage Product Specifications', module: 'cpq' },\n { id: 'cpq.offerings.view', title: 'View Product Offerings', module: 'cpq' },\n { id: 'cpq.offerings.manage', title: 'Manage Product Offerings', module: 'cpq' },\n { id: 'cpq.wizards.view', title: 'View Wizards', module: 'cpq' },\n { id: 'cpq.wizards.manage', title: 'Manage Wizards', module: 'cpq' },\n { id: 'cpq.orders.view', title: 'View CPQ Orders', module: 'cpq' },\n { id: 'cpq.orders.manage', title: 'Manage CPQ Orders', module: 'cpq' },\n // XD-250 ARC (Amend / Renew / Cancel)\n { id: 'cpq.arc.amend.manage', title: 'Manage Amend Quotes', module: 'cpq' },\n { id: 'cpq.arc.renew.manage', title: 'Manage Renew Quotes', module: 'cpq' },\n { id: 'cpq.arc.cancel.manage', title: 'Manage Cancel Quotes', module: 'cpq' },\n { id: 'cpq.arc.changelog.view', title: 'View Subscription Change History', module: 'cpq' },\n // Expiring-subscriptions feature retired \u2014 the dedicated /expiring page\n // was replaced by an \"Expiring within (days)\" filter + saved perspective\n // on the main subscriptions list, which uses `cpq.inventory.view`.\n]\n\nexport default features\n"],
5
- "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACrE,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,qBAAqB,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,uBAAuB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,2BAA2B,OAAO,+BAA+B,QAAQ,MAAM;AAAA,EACrF,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACzF,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA;AAAA,EAErE,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,yBAAyB,OAAO,wBAAwB,QAAQ,MAAM;AAAA,EAC5E,EAAE,IAAI,0BAA0B,OAAO,oCAAoC,QAAQ,MAAM;AAAA;AAAA;AAAA;AAI3F;AAEA,IAAO,cAAQ;",
4
+ "sourcesContent": ["export const features = [\n { id: 'cpq.quotes.adjust_price', title: 'Adjust CPQ quote prices', module: 'cpq' },\n { id: 'cpq.quotes.view', title: 'View CPQ Quotes', module: 'cpq' },\n { id: 'cpq.quotes.manage', title: 'Manage CPQ Quotes', module: 'cpq' },\n { id: 'cpq.pricing.view', title: 'View Pricing', module: 'cpq' },\n { id: 'cpq.pricing.manage', title: 'Manage Pricing', module: 'cpq' },\n { id: 'cpq.products.view', title: 'View CPQ Product Config', module: 'cpq' },\n { id: 'cpq.products.manage', title: 'Manage CPQ Product Config', module: 'cpq' },\n { id: 'cpq.specifications.view', title: 'View Product Specifications', module: 'cpq' },\n { id: 'cpq.specifications.manage', title: 'Manage Product Specifications', module: 'cpq' },\n { id: 'cpq.offerings.view', title: 'View Product Offerings', module: 'cpq' },\n { id: 'cpq.offerings.manage', title: 'Manage Product Offerings', module: 'cpq' },\n { id: 'cpq.wizards.view', title: 'View Wizards', module: 'cpq' },\n { id: 'cpq.wizards.manage', title: 'Manage Wizards', module: 'cpq' },\n { id: 'cpq.orders.view', title: 'View CPQ Orders', module: 'cpq' },\n { id: 'cpq.orders.manage', title: 'Manage CPQ Orders', module: 'cpq' },\n // XD-250 ARC (Amend / Renew / Cancel)\n { id: 'cpq.arc.amend.manage', title: 'Manage Amend Quotes', module: 'cpq' },\n { id: 'cpq.arc.renew.manage', title: 'Manage Renew Quotes', module: 'cpq' },\n { id: 'cpq.arc.cancel.manage', title: 'Manage Cancel Quotes', module: 'cpq' },\n { id: 'cpq.arc.changelog.view', title: 'View Subscription Change History', module: 'cpq' },\n // Expiring-subscriptions feature retired \u2014 the dedicated /expiring page\n // was replaced by an \"Expiring within (days)\" filter + saved perspective\n // on the main subscriptions list, which uses `cpq.inventory.view`.\n]\n\nexport default features\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,2BAA2B,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EACjF,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACrE,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,qBAAqB,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,uBAAuB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,2BAA2B,OAAO,+BAA+B,QAAQ,MAAM;AAAA,EACrF,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACzF,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA;AAAA,EAErE,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,yBAAyB,OAAO,wBAAwB,QAAQ,MAAM;AAAA,EAC5E,EAAE,IAAI,0BAA0B,OAAO,oCAAoC,QAAQ,MAAM;AAAA;AAAA;AAAA;AAI3F;AAEA,IAAO,cAAQ;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,45 @@
1
+ import { NextResponse } from "next/server";
2
+ import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
3
+ import { runCpqMutationGuards } from "./quoteMutationGuard.js";
4
+ async function adjustmentCommandRoute(req, ctx, quoteId, body, schema, operation, adjustmentId) {
5
+ const guard = await runCpqMutationGuards(ctx, req, {
6
+ resourceKind: "cpq.quote_price_adjustment",
7
+ resourceId: adjustmentId,
8
+ operation,
9
+ mutationPayload: { quoteId, ...body }
10
+ });
11
+ if (!guard.ok) return guard.response;
12
+ const modified = Object.fromEntries(Object.entries(guard.modifiedPayload ?? {}).filter(([key]) => key !== "quoteId" && key !== "adjustmentId"));
13
+ const effective = schema.parse({ ...body, ...modified });
14
+ const input = operation === "create" ? { quoteId, adjustment: effective } : operation === "update" ? { quoteId, adjustmentId, patch: effective } : { quoteId, adjustmentId, ...effective };
15
+ const commandCtx = {
16
+ container: ctx.container,
17
+ auth: ctx.auth,
18
+ organizationScope: ctx.organizationScope,
19
+ selectedOrganizationId: ctx.organizationId,
20
+ organizationIds: [ctx.organizationId],
21
+ request: req
22
+ };
23
+ const bus = ctx.container.resolve("commandBus");
24
+ const commandId = `cpq.quote_price_adjustment.${operation}`;
25
+ const { result, logEntry } = await bus.execute(commandId, { input, ctx: commandCtx });
26
+ if (!result) throw new Error("Adjustment command did not return a result");
27
+ await guard.runAfterSuccess();
28
+ const response = NextResponse.json(operation === "delete" ? { quote: result.quote } : { adjustment: result.adjustment, quote: result.quote }, { status: operation === "create" ? 201 : 200 });
29
+ if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {
30
+ response.headers.set("x-om-operation", serializeOperationMetadata({
31
+ id: logEntry.id,
32
+ undoToken: logEntry.undoToken,
33
+ commandId: logEntry.commandId,
34
+ actionLabel: logEntry.actionLabel ?? null,
35
+ resourceKind: "cpq.quote_price_adjustment",
36
+ resourceId: result.adjustment.id,
37
+ executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : typeof logEntry.createdAt === "string" ? logEntry.createdAt : (/* @__PURE__ */ new Date()).toISOString()
38
+ }));
39
+ }
40
+ return response;
41
+ }
42
+ export {
43
+ adjustmentCommandRoute
44
+ };
45
+ //# sourceMappingURL=adjustmentCommandRoute.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { z } from 'zod'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CpqRouteContext } from '../context'\nimport { runCpqMutationGuards } from './quoteMutationGuard'\nimport type { AdjustmentWriteResult } from '../../services/priceAdjustments/adjustmentService'\n\nexport async function adjustmentCommandRoute<T extends object>(\n req: Request, ctx: CpqRouteContext, quoteId: string, body: T,\n schema: z.ZodType<T>, operation: 'create' | 'update' | 'delete', adjustmentId?: string,\n): Promise<NextResponse | Response> {\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: 'cpq.quote_price_adjustment', resourceId: adjustmentId,\n operation, mutationPayload: { quoteId, ...body },\n })\n if (!guard.ok) return guard.response\n // Path identifiers belong to the server, even when a project guard returns\n // the full payload including the original quoteId metadata.\n const modified = Object.fromEntries(Object.entries(guard.modifiedPayload ?? {})\n .filter(([key]) => key !== 'quoteId' && key !== 'adjustmentId'))\n const effective = schema.parse({ ...body, ...modified })\n const input = operation === 'create' ? { quoteId, adjustment: effective }\n : operation === 'update' ? { quoteId, adjustmentId, patch: effective }\n : { quoteId, adjustmentId, ...effective }\n const commandCtx: CommandRuntimeContext = {\n container: ctx.container, auth: ctx.auth, organizationScope: ctx.organizationScope,\n selectedOrganizationId: ctx.organizationId, organizationIds: [ctx.organizationId], request: req,\n }\n const bus = ctx.container.resolve<CommandBus>('commandBus')\n const commandId = `cpq.quote_price_adjustment.${operation}`\n const { result, logEntry } = await bus.execute<typeof input, AdjustmentWriteResult>(commandId, { input, ctx: commandCtx })\n if (!result) throw new Error('Adjustment command did not return a result')\n await guard.runAfterSuccess()\n const response = NextResponse.json(operation === 'delete' ? { quote: result.quote }\n : { adjustment: result.adjustment, quote: result.quote }, { status: operation === 'create' ? 201 : 200 })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set('x-om-operation', serializeOperationMetadata({ id: logEntry.id, undoToken: logEntry.undoToken,\n commandId: logEntry.commandId, actionLabel: logEntry.actionLabel ?? null,\n resourceKind: 'cpq.quote_price_adjustment', resourceId: result.adjustment.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string' ? logEntry.createdAt : new Date().toISOString() }))\n }\n return response\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAG7B,SAAS,kCAAkC;AAE3C,SAAS,4BAA4B;AAGrC,eAAsB,uBACpB,KAAc,KAAsB,SAAiB,MACrD,QAAsB,WAA2C,cAC/B;AAClC,QAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,IACjD,cAAc;AAAA,IAA8B,YAAY;AAAA,IACxD;AAAA,IAAW,iBAAiB,EAAE,SAAS,GAAG,KAAK;AAAA,EACjD,CAAC;AACD,MAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAG5B,QAAM,WAAW,OAAO,YAAY,OAAO,QAAQ,MAAM,mBAAmB,CAAC,CAAC,EAC3E,OAAO,CAAC,CAAC,GAAG,MAAM,QAAQ,aAAa,QAAQ,cAAc,CAAC;AACjE,QAAM,YAAY,OAAO,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;AACvD,QAAM,QAAQ,cAAc,WAAW,EAAE,SAAS,YAAY,UAAU,IACpE,cAAc,WAAW,EAAE,SAAS,cAAc,OAAO,UAAU,IACjE,EAAE,SAAS,cAAc,GAAG,UAAU;AAC5C,QAAM,aAAoC;AAAA,IACxC,WAAW,IAAI;AAAA,IAAW,MAAM,IAAI;AAAA,IAAM,mBAAmB,IAAI;AAAA,IACjE,wBAAwB,IAAI;AAAA,IAAgB,iBAAiB,CAAC,IAAI,cAAc;AAAA,IAAG,SAAS;AAAA,EAC9F;AACA,QAAM,MAAM,IAAI,UAAU,QAAoB,YAAY;AAC1D,QAAM,YAAY,8BAA8B,SAAS;AACzD,QAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,IAAI,QAA6C,WAAW,EAAE,OAAO,KAAK,WAAW,CAAC;AACzH,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,4CAA4C;AACzE,QAAM,MAAM,gBAAgB;AAC5B,QAAM,WAAW,aAAa,KAAK,cAAc,WAAW,EAAE,OAAO,OAAO,MAAM,IAC9E,EAAE,YAAY,OAAO,YAAY,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,cAAc,WAAW,MAAM,IAAI,CAAC;AAC1G,MAAI,UAAU,aAAa,SAAS,MAAM,SAAS,WAAW;AAC5D,aAAS,QAAQ,IAAI,kBAAkB,2BAA2B;AAAA,MAAE,IAAI,SAAS;AAAA,MAAI,WAAW,SAAS;AAAA,MACvG,WAAW,SAAS;AAAA,MAAW,aAAa,SAAS,eAAe;AAAA,MACpE,cAAc;AAAA,MAA8B,YAAY,OAAO,WAAW;AAAA,MAC1E,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAC5E,OAAO,SAAS,cAAc,WAAW,SAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAAE,CAAC,CAAC;AAAA,EACjG;AACA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { OptimisticLockError } from "@mikro-orm/core";
4
- import { QuotingError } from "../../services/cpqQuotingService.js";
4
+ import { isQuotingError } from "../../services/cpqQuotingService.js";
5
5
  function handleArcError(err, ctx) {
6
6
  if (err instanceof z.ZodError) {
7
7
  return NextResponse.json(
@@ -9,7 +9,7 @@ function handleArcError(err, ctx) {
9
9
  { status: 400 }
10
10
  );
11
11
  }
12
- if (err instanceof QuotingError) {
12
+ if (isQuotingError(err)) {
13
13
  const body = { error: err.message };
14
14
  if (err.cpqStatus) body.cpqStatus = err.cpqStatus;
15
15
  if (err.details) Object.assign(body, err.details);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/api/_helpers/handleArcError.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport { QuotingError } from '../../services/cpqQuotingService'\n\n/**\n * Shared error \u2192 JSON-response converter for ARC routes (XD-250).\n *\n * Handles three families:\n * 1. Zod validation errors \u2192 400 with `details` array.\n * 2. Service-layer `QuotingError` (matched both via `instanceof` AND via\n * duck-typing on `name === 'QuotingError'`) \u2192 propagates `err.status`.\n * The duck-typing fallback exists because Turbopack hot-reload can\n * produce two distinct class instances for the same source file\n * across module boundaries, breaking `instanceof` checks at runtime.\n * 3. cpqInventoryService throws plain `{ status, error }` objects \u2192\n * propagate the embedded status.\n * 4. Anything else \u2192 500 + console.error with the route context.\n *\n * Returning the response keeps route handlers terse:\n * `} catch (err) { return handleArcError(err, 'cpq/quotes/from-subscription.POST') }`\n */\nexport function handleArcError(err: unknown, ctx: string): NextResponse {\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: 'Validation error', details: err.issues },\n { status: 400 },\n )\n }\n\n if (err instanceof QuotingError) {\n const body: Record<string, unknown> = { error: err.message }\n if (err.cpqStatus) body.cpqStatus = err.cpqStatus\n if (err.details) Object.assign(body, err.details)\n return NextResponse.json(body, { status: err.status })\n }\n\n // MikroORM optimistic-lock collision on subscription.version (XD-250 spec\n // \u00A7 Risks \"Order activation racing with another mutation\"). Both\n // `instanceof OptimisticLockError` AND constructor-name fallback to\n // survive Turbopack class drift.\n if (\n err instanceof OptimisticLockError ||\n (err &&\n typeof err === 'object' &&\n typeof (err as { constructor?: { name?: string } }).constructor?.name === 'string' &&\n (err as { constructor: { name: string } }).constructor.name === 'OptimisticLockError')\n ) {\n return NextResponse.json(\n { error: 'Concurrent modification detected', details: (err as Error).message },\n { status: 409 },\n )\n }\n\n // Duck-typed QuotingError (Turbopack instanceof drift) and inventory-service\n // plain throw shape `{ status, error }`. Both surface a numeric `status`.\n // Also propagates `details` (object) into response body \u2014 preserves the\n // structured payload (e.g. `existingQuoteId`) that the typed branch above\n // would have surfaced if instanceof had matched.\n if (\n err &&\n typeof err === 'object' &&\n 'status' in err &&\n typeof (err as { status: unknown }).status === 'number'\n ) {\n const e = err as {\n status: number\n message?: string\n error?: string\n name?: string\n details?: Record<string, unknown>\n }\n const message = e.message ?? e.error ?? 'Conflict'\n const body: Record<string, unknown> = { error: message }\n if (e.details && typeof e.details === 'object') Object.assign(body, e.details)\n return NextResponse.json(body, { status: e.status })\n }\n\n console.error(`[${ctx}]`, err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,2BAA2B;AACpC,SAAS,oBAAoB;AAmBtB,SAAS,eAAe,KAAc,KAA2B;AACtE,MAAI,eAAe,EAAE,UAAU;AAC7B,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO;AAAA,MACjD,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,eAAe,cAAc;AAC/B,UAAM,OAAgC,EAAE,OAAO,IAAI,QAAQ;AAC3D,QAAI,IAAI,UAAW,MAAK,YAAY,IAAI;AACxC,QAAI,IAAI,QAAS,QAAO,OAAO,MAAM,IAAI,OAAO;AAChD,WAAO,aAAa,KAAK,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,EACvD;AAMA,MACE,eAAe,uBACd,OACC,OAAO,QAAQ,YACf,OAAQ,IAA4C,aAAa,SAAS,YACzE,IAA0C,YAAY,SAAS,uBAClE;AACA,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,oCAAoC,SAAU,IAAc,QAAQ;AAAA,MAC7E,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAOA,MACE,OACA,OAAO,QAAQ,YACf,YAAY,OACZ,OAAQ,IAA4B,WAAW,UAC/C;AACA,UAAM,IAAI;AAOV,UAAM,UAAU,EAAE,WAAW,EAAE,SAAS;AACxC,UAAM,OAAgC,EAAE,OAAO,QAAQ;AACvD,QAAI,EAAE,WAAW,OAAO,EAAE,YAAY,SAAU,QAAO,OAAO,MAAM,EAAE,OAAO;AAC7E,WAAO,aAAa,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;AAAA,EACrD;AAEA,UAAQ,MAAM,IAAI,GAAG,KAAK,GAAG;AAC7B,SAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AACvE;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport { isQuotingError } from '../../services/cpqQuotingService'\n\n/**\n * Shared error \u2192 JSON-response converter for ARC routes (XD-250).\n *\n * Handles three families:\n * 1. Zod validation errors \u2192 400 with `details` array.\n * 2. Service-layer `QuotingError` (matched via its cross-realm brand,\n * `isQuotingError`, with a duck-typed fallback below) \u2192 propagates\n * `err.status`. `instanceof` is unreliable here: Next bundles this\n * package once per server layer, so the route and the DI-resolved\n * service can hold two distinct copies of the class \u2014 in production\n * too, not only under hot-reload.\n * 3. cpqInventoryService throws plain `{ status, error }` objects \u2192\n * propagate the embedded status.\n * 4. Anything else \u2192 500 + console.error with the route context.\n *\n * Returning the response keeps route handlers terse:\n * `} catch (err) { return handleArcError(err, 'cpq/quotes/from-subscription.POST') }`\n */\nexport function handleArcError(err: unknown, ctx: string): NextResponse {\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: 'Validation error', details: err.issues },\n { status: 400 },\n )\n }\n\n if (isQuotingError(err)) {\n const body: Record<string, unknown> = { error: err.message }\n if (err.cpqStatus) body.cpqStatus = err.cpqStatus\n if (err.details) Object.assign(body, err.details)\n return NextResponse.json(body, { status: err.status })\n }\n\n // MikroORM optimistic-lock collision on subscription.version (XD-250 spec\n // \u00A7 Risks \"Order activation racing with another mutation\"). Both\n // `instanceof OptimisticLockError` AND constructor-name fallback to\n // survive Turbopack class drift.\n if (\n err instanceof OptimisticLockError ||\n (err &&\n typeof err === 'object' &&\n typeof (err as { constructor?: { name?: string } }).constructor?.name === 'string' &&\n (err as { constructor: { name: string } }).constructor.name === 'OptimisticLockError')\n ) {\n return NextResponse.json(\n { error: 'Concurrent modification detected', details: (err as Error).message },\n { status: 409 },\n )\n }\n\n // cpqInventoryService's plain throw shape `{ status, error }` (QuotingError\n // from any bundle copy is already handled by `isQuotingError` above).\n // Propagates `details` (object) into the response body so structured\n // payloads survive here too.\n if (\n err &&\n typeof err === 'object' &&\n 'status' in err &&\n typeof (err as { status: unknown }).status === 'number'\n ) {\n const e = err as {\n status: number\n message?: string\n error?: string\n name?: string\n details?: Record<string, unknown>\n }\n const message = e.message ?? e.error ?? 'Conflict'\n const body: Record<string, unknown> = { error: message }\n if (e.details && typeof e.details === 'object') Object.assign(body, e.details)\n return NextResponse.json(body, { status: e.status })\n }\n\n console.error(`[${ctx}]`, err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAoBxB,SAAS,eAAe,KAAc,KAA2B;AACtE,MAAI,eAAe,EAAE,UAAU;AAC7B,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO;AAAA,MACjD,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,eAAe,GAAG,GAAG;AACvB,UAAM,OAAgC,EAAE,OAAO,IAAI,QAAQ;AAC3D,QAAI,IAAI,UAAW,MAAK,YAAY,IAAI;AACxC,QAAI,IAAI,QAAS,QAAO,OAAO,MAAM,IAAI,OAAO;AAChD,WAAO,aAAa,KAAK,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,EACvD;AAMA,MACE,eAAe,uBACd,OACC,OAAO,QAAQ,YACf,OAAQ,IAA4C,aAAa,SAAS,YACzE,IAA0C,YAAY,SAAS,uBAClE;AACA,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,oCAAoC,SAAU,IAAc,QAAQ;AAAA,MAC7E,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAMA,MACE,OACA,OAAO,QAAQ,YACf,YAAY,OACZ,OAAQ,IAA4B,WAAW,UAC/C;AACA,UAAM,IAAI;AAOV,UAAM,UAAU,EAAE,WAAW,EAAE,SAAS;AACxC,UAAM,OAAgC,EAAE,OAAO,QAAQ;AACvD,QAAI,EAAE,WAAW,OAAO,EAAE,YAAY,SAAU,QAAO,OAAO,MAAM,EAAE,OAAO;AAC7E,WAAO,aAAa,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;AAAA,EACrD;AAEA,UAAQ,MAAM,IAAI,GAAG,KAAK,GAAG;AAC7B,SAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AACvE;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,23 @@
1
+ import {
2
+ runRouteMutationGuards
3
+ } from "@open-mercato/shared/lib/crud/route-mutation-guard";
4
+ const CPQ_QUOTE_RESOURCE_KIND = "cpq.quote";
5
+ const CPQ_QUOTE_LINE_RESOURCE_KIND = "cpq.quote_line";
6
+ function runCpqMutationGuards(ctx, req, input) {
7
+ return runRouteMutationGuards({
8
+ container: ctx.container,
9
+ req,
10
+ auth: {
11
+ userId: ctx.auth?.sub ?? "",
12
+ tenantId: ctx.tenantId,
13
+ organizationId: ctx.organizationId || null
14
+ },
15
+ input
16
+ });
17
+ }
18
+ export {
19
+ CPQ_QUOTE_LINE_RESOURCE_KIND,
20
+ CPQ_QUOTE_RESOURCE_KIND,
21
+ runCpqMutationGuards
22
+ };
23
+ //# sourceMappingURL=quoteMutationGuard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/cpq/api/_helpers/quoteMutationGuard.ts"],
4
+ "sourcesContent": ["import {\n runRouteMutationGuards,\n type RouteMutationGuardInput,\n type RouteMutationGuardResult,\n} from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport type { CpqRouteContext } from '../context'\n\n/** Mutation-guard resource kinds for CPQ quote writes (`module.entity`). */\nexport const CPQ_QUOTE_RESOURCE_KIND = 'cpq.quote'\nexport const CPQ_QUOTE_LINE_RESOURCE_KIND = 'cpq.quote_line'\n\n/**\n * Run every registered mutation guard for a CPQ custom write route.\n *\n * Quote routes are hand-written (not `makeCrudRoute`), so they wire guards\n * explicitly through the framework's registry-based wrapper. Projects use\n * this hook to enforce rules CPQ does not own \u2014 e.g. blocking\n * `ready \u2192 with_customer` or order conversion until an approval workflow\n * signs a discounted quote off. With no guard registered the call passes.\n *\n * Call `runAfterSuccess()` on the passed result only after the write commits.\n */\nexport function runCpqMutationGuards(\n ctx: Pick<CpqRouteContext, 'container' | 'auth' | 'tenantId' | 'organizationId'>,\n req: Request,\n input: RouteMutationGuardInput,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: ctx.container,\n req,\n auth: {\n userId: ctx.auth?.sub ?? '',\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId || null,\n },\n input,\n })\n}\n"],
5
+ "mappings": "AAAA;AAAA,EACE;AAAA,OAGK;AAIA,MAAM,0BAA0B;AAChC,MAAM,+BAA+B;AAarC,SAAS,qBACd,KACA,KACA,OACmC;AACnC,SAAO,uBAAuB;AAAA,IAC5B,WAAW,IAAI;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,QAAQ,IAAI,MAAM,OAAO;AAAA,MACzB,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI,kBAAkB;AAAA,IACxC;AAAA,IACA;AAAA,EACF,CAAC;AACH;",
6
+ "names": []
7
+ }
@@ -0,0 +1,19 @@
1
+ import { z } from "zod";
2
+ const cpqErrorResponseSchema = z.object({ error: z.string() }).passthrough();
3
+ function cpqErrorDoc(status, description) {
4
+ return { status, description, schema: cpqErrorResponseSchema };
5
+ }
6
+ const cpqQuoteIdPathParams = z.object({ quoteId: z.string().uuid() });
7
+ const CPQ_GUARDED_WRITE_ERRORS = [
8
+ cpqErrorDoc(400, "Validation error"),
9
+ cpqErrorDoc(404, "Quote or line not found"),
10
+ cpqErrorDoc(409, "Quote is in a terminal status"),
11
+ cpqErrorDoc(422, "Blocked by a mutation guard")
12
+ ];
13
+ export {
14
+ CPQ_GUARDED_WRITE_ERRORS,
15
+ cpqErrorDoc,
16
+ cpqErrorResponseSchema,
17
+ cpqQuoteIdPathParams
18
+ };
19
+ //# sourceMappingURL=quoteRouteOpenApi.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts"],
4
+ "sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiResponseDoc } from '@open-mercato/shared/lib/openapi'\n\n/** Error body every CPQ quote route returns (`{ error, ... }`). */\nexport const cpqErrorResponseSchema = z.object({ error: z.string() }).passthrough()\n\nexport function cpqErrorDoc(status: number, description: string): OpenApiResponseDoc {\n return { status, description, schema: cpqErrorResponseSchema }\n}\n\nexport const cpqQuoteIdPathParams = z.object({ quoteId: z.string().uuid() })\n\n/** Shared error docs for quote writes guarded by mutation guards. */\nexport const CPQ_GUARDED_WRITE_ERRORS: OpenApiResponseDoc[] = [\n cpqErrorDoc(400, 'Validation error'),\n cpqErrorDoc(404, 'Quote or line not found'),\n cpqErrorDoc(409, 'Quote is in a terminal status'),\n cpqErrorDoc(422, 'Blocked by a mutation guard'),\n]\n"],
5
+ "mappings": "AAAA,SAAS,SAAS;AAIX,MAAM,yBAAyB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY;AAE3E,SAAS,YAAY,QAAgB,aAAyC;AACnF,SAAO,EAAE,QAAQ,aAAa,QAAQ,uBAAuB;AAC/D;AAEO,MAAM,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAGpE,MAAM,2BAAiD;AAAA,EAC5D,YAAY,KAAK,kBAAkB;AAAA,EACnC,YAAY,KAAK,yBAAyB;AAAA,EAC1C,YAAY,KAAK,+BAA+B;AAAA,EAChD,YAAY,KAAK,6BAA6B;AAChD;",
6
+ "names": []
7
+ }
@@ -1,7 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { OptimisticLockError } from "@mikro-orm/core";
3
3
  import { resolveCpqRouteContext } from "../../../context.js";
4
- import { OrderError } from "../../../../services/cpqOrderService.js";
4
+ import { isOrderError } from "../../../../services/cpqOrderService.js";
5
5
  const metadata = {
6
6
  POST: { requireAuth: true, requireFeatures: ["cpq.orders.manage"] }
7
7
  };
@@ -15,7 +15,7 @@ async function POST(req, { params }) {
15
15
  const result = await service.activateOrder(orderId, scope);
16
16
  return NextResponse.json(result);
17
17
  } catch (err) {
18
- if (err instanceof OrderError) {
18
+ if (isOrderError(err)) {
19
19
  return NextResponse.json({ error: err.message }, { status: err.status });
20
20
  }
21
21
  if (err instanceof OptimisticLockError || err && typeof err === "object" && err.constructor?.name === "OptimisticLockError") {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/cpq/api/orders/%5BorderId%5D/activate/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { OrderError } from '../../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.activateOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (err instanceof OrderError) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n // XD-250 ARC: optimistic-lock collision on subscription.version when two\n // ARC orders touching the same sub race to activate. Spec \u00A7 Risks:\n // \"second activation gets 409, order stays in pending_activation,\n // retryable\". Constructor-name fallback survives Turbopack class drift.\n if (\n err instanceof OptimisticLockError ||\n (err &&\n typeof err === 'object' &&\n (err as { constructor?: { name?: string } }).constructor?.name === 'OptimisticLockError')\n ) {\n return NextResponse.json(\n { error: 'Concurrent modification detected', details: (err as Error).message },\n { status: 409 },\n )\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error('[cpq/orders/[orderId]/activate.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,kBAAkB;AAGpB,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,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,cAAc,SAAS,KAAK;AACzD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,YAAY;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AAKA,QACE,eAAe,uBACd,OACC,OAAO,QAAQ,YACd,IAA4C,aAAa,SAAS,uBACrE;AACA,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,oCAAoC,SAAU,IAAc,QAAQ;AAAA,QAC7E,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AACA,UAAM,SAAS;AACf,QAAI,OAAO,QAAQ,WAAW,YAAY,OAAO,QAAQ,UAAU,UAAU;AAC3E,aAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,OAAO,OAAiB,CAAC;AAAA,IACvF;AACA,YAAQ,MAAM,wCAAwC,GAAG;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { isOrderError } from '../../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.activateOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n // XD-250 ARC: optimistic-lock collision on subscription.version when two\n // ARC orders touching the same sub race to activate. Spec \u00A7 Risks:\n // \"second activation gets 409, order stays in pending_activation,\n // retryable\". Constructor-name fallback survives Turbopack class drift.\n if (\n err instanceof OptimisticLockError ||\n (err &&\n typeof err === 'object' &&\n (err as { constructor?: { name?: string } }).constructor?.name === 'OptimisticLockError')\n ) {\n return NextResponse.json(\n { error: 'Concurrent modification detected', details: (err as Error).message },\n { status: 409 },\n )\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error('[cpq/orders/[orderId]/activate.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAGtB,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,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,cAAc,SAAS,KAAK;AACzD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AAKA,QACE,eAAe,uBACd,OACC,OAAO,QAAQ,YACd,IAA4C,aAAa,SAAS,uBACrE;AACA,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,oCAAoC,SAAU,IAAc,QAAQ;AAAA,QAC7E,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AACA,UAAM,SAAS;AACf,QAAI,OAAO,QAAQ,WAAW,YAAY,OAAO,QAAQ,UAAU,UAAU;AAC3E,aAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,OAAO,OAAiB,CAAC;AAAA,IACvF;AACA,YAAQ,MAAM,wCAAwC,GAAG;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,8 @@
1
1
  import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { cpqErrorDoc } from "../../_helpers/quoteRouteOpenApi.js";
2
4
  import { resolveCpqRouteContext } from "../../context.js";
3
- import { OrderError } from "../../../services/cpqOrderService.js";
5
+ import { isOrderError } from "../../../services/cpqOrderService.js";
4
6
  const metadata = {
5
7
  GET: { requireAuth: true, requireFeatures: ["cpq.orders.view"] }
6
8
  };
@@ -14,15 +16,29 @@ async function GET(req, { params }) {
14
16
  const result = await service.getOrder(orderId, scope);
15
17
  return NextResponse.json(result);
16
18
  } catch (err) {
17
- if (err instanceof OrderError) {
19
+ if (isOrderError(err)) {
18
20
  return NextResponse.json({ error: err.message }, { status: err.status });
19
21
  }
20
22
  console.error("[cpq/orders/[orderId].GET]", err);
21
23
  return NextResponse.json({ error: "Internal error" }, { status: 500 });
22
24
  }
23
25
  }
26
+ const openApi = {
27
+ tag: "CPQ Orders",
28
+ summary: "CPQ order",
29
+ pathParams: z.object({ orderId: z.string().uuid() }),
30
+ methods: {
31
+ GET: {
32
+ summary: "Get a CPQ order with its lines and pricing summary",
33
+ description: "`pricingSummary` carries NRC / MRC totals plus the source quote's waterfall figures (`listNrcTotal`, `listMrcTotal`, `standard*Total`, `ruleDiscountTotal`, `manualDiscountTotal`, `dealValue`, `discountDepth`), computed from the line charges copied at conversion. ARC lines carry `arcSource` (the subscription item before the change).",
34
+ responses: [{ status: 200, description: "The order" }],
35
+ errors: [cpqErrorDoc(401, "Unauthorized"), cpqErrorDoc(404, "Order not found")]
36
+ }
37
+ }
38
+ };
24
39
  export {
25
40
  GET,
26
- metadata
41
+ metadata,
42
+ openApi
27
43
  };
28
44
  //# sourceMappingURL=route.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/api/orders/%5BorderId%5D/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../context'\nimport { OrderError } from '../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../services/cpqOrderService'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.getOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (err instanceof OrderError) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/orders/[orderId].GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,kBAAkB;AAGpB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA6C;AAC5F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,SAAS,SAAS,KAAK;AACpD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,YAAY;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,8BAA8B,GAAG;AAC/C,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { cpqErrorDoc } from '../../_helpers/quoteRouteOpenApi'\nimport { resolveCpqRouteContext } from '../../context'\nimport { isOrderError } from '../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../services/cpqOrderService'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.getOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/orders/[orderId].GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Orders',\n summary: 'CPQ order',\n pathParams: z.object({ orderId: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'Get a CPQ order with its lines and pricing summary',\n description:\n '`pricingSummary` carries NRC / MRC totals plus the source quote\\'s waterfall figures (`listNrcTotal`, `listMrcTotal`, `standard*Total`, `ruleDiscountTotal`, `manualDiscountTotal`, `dealValue`, `discountDepth`), computed from the line charges copied at conversion. ARC lines carry `arcSource` (the subscription item before the change).',\n responses: [{ status: 200, description: 'The order' }],\n errors: [cpqErrorDoc(401, 'Unauthorized'), cpqErrorDoc(404, 'Order not found')],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAGtB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA6C;AAC5F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,SAAS,SAAS,KAAK;AACpD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,8BAA8B,GAAG;AAC/C,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,EACnD,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,YAAY,CAAC;AAAA,MACrD,QAAQ,CAAC,YAAY,KAAK,cAAc,GAAG,YAAY,KAAK,iBAAiB,CAAC;AAAA,IAChF;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
2
2
  import { z } from "zod";
3
3
  import { resolveCpqRouteContext } from "../../../context.js";
4
4
  import { cpqOrderStatusTransitionSchema } from "../../../../data/validators.js";
5
- import { OrderError } from "../../../../services/cpqOrderService.js";
5
+ import { isOrderError } from "../../../../services/cpqOrderService.js";
6
6
  const metadata = {
7
7
  POST: { requireAuth: true, requireFeatures: ["cpq.orders.manage"] }
8
8
  };
@@ -20,7 +20,7 @@ async function POST(req, { params }) {
20
20
  if (err instanceof z.ZodError) {
21
21
  return NextResponse.json({ error: "Validation error", details: err.issues }, { status: 400 });
22
22
  }
23
- if (err instanceof OrderError) {
23
+ if (isOrderError(err)) {
24
24
  return NextResponse.json({ error: err.message }, { status: err.status });
25
25
  }
26
26
  const errObj = err;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../src/modules/cpq/api/orders/%5BorderId%5D/status/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqOrderStatusTransitionSchema } from '../../../../data/validators'\nimport { OrderError } from '../../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const body = cpqOrderStatusTransitionSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.transitionOrderStatus(orderId, body.targetStatus, 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 (err instanceof OrderError) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error('[cpq/orders/[orderId]/status.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,sCAAsC;AAC/C,SAAS,kBAAkB;AAGpB,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,+BAA+B,MAAM,MAAM,IAAI,KAAK,CAAC;AAClE,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,sBAAsB,SAAS,KAAK,cAAc,KAAK;AACpF,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,YAAY;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,UAAM,SAAS;AACf,QAAI,OAAO,QAAQ,WAAW,YAAY,OAAO,QAAQ,UAAU,UAAU;AAC3E,aAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,OAAO,OAAiB,CAAC;AAAA,IACvF;AACA,YAAQ,MAAM,sCAAsC,GAAG;AACvD,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 { cpqOrderStatusTransitionSchema } from '../../../../data/validators'\nimport { isOrderError } from '../../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const body = cpqOrderStatusTransitionSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.transitionOrderStatus(orderId, body.targetStatus, 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 (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error('[cpq/orders/[orderId]/status.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,sCAAsC;AAC/C,SAAS,oBAAoB;AAGtB,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,+BAA+B,MAAM,MAAM,IAAI,KAAK,CAAC;AAClE,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,sBAAsB,SAAS,KAAK,cAAc,KAAK;AACpF,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,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,UAAM,SAAS;AACf,QAAI,OAAO,QAAQ,WAAW,YAAY,OAAO,QAAQ,UAAU,UAAU;AAC3E,aAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,OAAO,OAAiB,CAAC;AAAA,IACvF;AACA,YAAQ,MAAM,sCAAsC,GAAG;AACvD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
6
6
  "names": []
7
7
  }