@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
@@ -0,0 +1,116 @@
1
+ import type { AdjustmentError, PriceAdjustment } from '../../data/price-adjustments'
2
+ import type { ResolvedCharge } from '../types'
3
+ import { withChargeWaterfall } from './chargeWaterfall'
4
+
5
+ export interface AdjustmentPricingLine {
6
+ lineId: string
7
+ parentLineId: string | null
8
+ enginePriced: boolean
9
+ mirrored: boolean
10
+ charges: ResolvedCharge[]
11
+ }
12
+
13
+ /** Most-specific valid manual adjustment wins; each is evaluated against standard. */
14
+ export function resolveManualAdjustments(
15
+ lines: AdjustmentPricingLine[],
16
+ adjustments: PriceAdjustment[],
17
+ currencyCode: string,
18
+ ): { lines: AdjustmentPricingLine[]; errors: AdjustmentError[] } {
19
+ const errors: AdjustmentError[] = []
20
+ const byId = new Map(lines.map((line) => [line.lineId, line]))
21
+ const eligible = (line: AdjustmentPricingLine) => line.enginePriced && !line.mirrored
22
+ const output = lines.map((line) => ({
23
+ ...line,
24
+ charges: line.charges.map((charge) => {
25
+ const normalized = withChargeWaterfall(charge)
26
+ return { ...normalized, unitPrice: eligible(line) ? normalized.standardUnitPrice! : normalized.unitPrice,
27
+ totalPrice: eligible(line) ? normalized.standardTotalPrice! : normalized.totalPrice,
28
+ adjustments: (eligible(line) ? normalized.adjustments?.filter((entry) => entry.origin !== 'manual') : normalized.adjustments) ?? [], adjustmentErrors: [] as AdjustmentError[] }
29
+ }),
30
+ }))
31
+ const outById = new Map(output.map((line) => [line.lineId, line]))
32
+ const addError = (row: PriceAdjustment, code: AdjustmentError['code'], chargeCode?: string, lineId = row.quoteLineId) => {
33
+ if (errors.some((entry) => entry.adjustmentId === row.id && entry.quoteLineId === lineId && entry.chargeCode === chargeCode && entry.code === code)) return
34
+ const error: AdjustmentError = { adjustmentId: row.id, quoteLineId: lineId, ...(chargeCode ? { chargeCode } : {}), code }
35
+ errors.push(error)
36
+ for (const charge of outById.get(lineId)?.charges ?? []) {
37
+ if (!chargeCode || charge.chargeCode === chargeCode) charge.adjustmentErrors.push(error)
38
+ }
39
+ }
40
+ // Missing/ambiguous targets are invalid even if another adjustment could win.
41
+ const invalid = new Set<string>()
42
+ for (const row of adjustments) {
43
+ const target = byId.get(row.quoteLineId)
44
+ let code: AdjustmentError['code'] | undefined
45
+ if (!target) code = 'ADJUSTMENT_TARGET_MISSING'
46
+ else if (!eligible(target)) code = 'LINE_NOT_ELIGIBLE'
47
+ else if (row.level === 'charge') {
48
+ const matches = target.charges.filter((charge) => charge.chargeCode === row.chargeCode)
49
+ if (!matches.length) code = 'ADJUSTMENT_TARGET_MISSING'
50
+ else if (matches.length > 1) code = 'TARGET_AMBIGUOUS'
51
+ }
52
+ if (code) { addError(row, code, row.chargeCode ?? undefined); invalid.add(row.id) }
53
+ }
54
+ for (const line of output) {
55
+ if (!eligible(line)) continue
56
+ const lineage: string[] = []
57
+ const visited = new Set<string>([line.lineId])
58
+ let parent = line.parentLineId
59
+ while (parent && !visited.has(parent)) {
60
+ visited.add(parent); lineage.push(parent); parent = byId.get(parent)?.parentLineId ?? null
61
+ }
62
+ // Declared before use: `.sort()` below calls it eagerly (a `const` after it would hit the TDZ).
63
+ const rank = (row: PriceAdjustment) =>
64
+ row.level === 'charge' ? 0 : row.quoteLineId === line.lineId ? 1 : 2 + lineage.indexOf(row.quoteLineId)
65
+ for (const charge of line.charges) {
66
+ const candidates = adjustments.filter((row) => !invalid.has(row.id) && (
67
+ row.level === 'charge'
68
+ ? row.quoteLineId === line.lineId && row.chargeCode === charge.chargeCode
69
+ : (row.quoteLineId === line.lineId || lineage.includes(row.quoteLineId)) &&
70
+ (!row.chargeTypeFilter || row.chargeTypeFilter === charge.chargeType)
71
+ )).sort((a, b) => rank(a) - rank(b) || a.id.localeCompare(b.id))
72
+ const standard = charge.standardUnitPrice!
73
+ const total = charge.standardTotalPrice
74
+ const valid = candidates.filter((row) => {
75
+ // Line discounts cover billable NRC/MRC, never metered usage.
76
+ if (charge.chargeType === 'usage' && row.level === 'line') return false
77
+ let code: AdjustmentError['code'] | undefined
78
+ if (charge.chargeType === 'usage' || total === null || !Number.isFinite(row.value) || row.value < 0 ||
79
+ (row.adjustmentType === 'price_override' && charge.pricingMethod !== 'flat' && charge.pricingMethod !== 'per_unit') ||
80
+ (row.level === 'line' && row.adjustmentType !== 'discount_percent') ||
81
+ (row.adjustmentType === 'discount_percent' && (row.value <= 0 || row.value > 100)) ||
82
+ (row.adjustmentType === 'discount_absolute' && row.value <= 0)) code = 'TYPE_NOT_ALLOWED'
83
+ else if ((row.currencyCode && row.currencyCode !== currencyCode) || charge.currencyCode !== currencyCode ||
84
+ (row.adjustmentType !== 'discount_percent' && !row.currencyCode)) code = 'CURRENCY_MISMATCH'
85
+ else if (row.adjustmentType === 'price_override' && row.value > standard) code = 'ABOVE_STANDARD'
86
+ if (code) addError(row, code, charge.chargeCode, line.lineId)
87
+ return !code
88
+ })
89
+ let applied = false
90
+ for (const row of valid) {
91
+ if (valid.filter((other) => rank(other) === rank(row)).length > 1) {
92
+ addError(row, 'ADJUSTMENT_OVERLAP', charge.chargeCode, line.lineId)
93
+ continue
94
+ }
95
+ if (applied) continue
96
+ const quantity = charge.quantity ?? 1
97
+ const afterTotal = row.adjustmentType === 'discount_absolute' ? Math.max(0, total! - row.value)
98
+ : row.adjustmentType === 'discount_percent' ? Math.max(0, total! * (1 - row.value / 100))
99
+ : row.value * quantity
100
+ const afterUnit = row.adjustmentType === 'discount_percent' ? standard * (1 - row.value / 100)
101
+ : row.adjustmentType === 'price_override' ? row.value : quantity > 0 ? afterTotal / quantity : 0
102
+ charge.unitPrice = afterUnit
103
+ charge.totalPrice = afterTotal
104
+ charge.adjustments.push({
105
+ origin: 'manual', adjustmentId: row.id, class: 'rep', level: row.level,
106
+ windowKind: 'relative', fromMonth: 1, toMonth: null,
107
+ ruleCode: row.id, ruleName: row.reasonCode, ruleType: row.adjustmentType,
108
+ reasonCode: row.reasonCode, createdByUserId: row.createdByUserId,
109
+ value: row.value, unitPriceBefore: standard, unitPriceAfter: afterUnit, delta: afterUnit - standard,
110
+ })
111
+ applied = true
112
+ }
113
+ }
114
+ }
115
+ return { lines: output, errors }
116
+ }
@@ -0,0 +1,31 @@
1
+ import type { CpqQuoteConfiguration, CpqQuoteLineConfiguration } from '../../data/entities'
2
+ import { adjustmentErrorSchema } from '../../data/price-adjustments'
3
+
4
+ type Failure = { lineId: string | null; code: string }
5
+ function recordedError(value: unknown, fallback: string | null): Failure | null {
6
+ if (!value || typeof value !== 'object') return null
7
+ const record = value as Record<string, unknown>
8
+ const parsed = adjustmentErrorSchema.shape.code.safeParse(record.code ?? record.message)
9
+ return parsed.success ? { lineId: typeof record.lineId === 'string' ? record.lineId : fallback, code: parsed.data } : null
10
+ }
11
+
12
+ /** Conversion validates the reviewed stored snapshot, without calling the price engine. */
13
+ export function storedAdjustmentFailure(quote: CpqQuoteConfiguration, lines: CpqQuoteLineConfiguration[]): Failure | null {
14
+ for (const line of lines) {
15
+ for (const charge of line.charges ?? []) {
16
+ if (Array.isArray(charge.adjustmentErrors) && charge.adjustmentErrors.length) {
17
+ return recordedError(charge.adjustmentErrors[0], line.quoteLineId) ?? { lineId: line.quoteLineId, code: 'ADJUSTMENT_INVALID' }
18
+ }
19
+ }
20
+ for (const error of line.validationErrors ?? []) {
21
+ const failure = recordedError(error, line.quoteLineId)
22
+ if (failure) return failure
23
+ }
24
+ }
25
+ const errors = quote.validationResult?.errors
26
+ if (Array.isArray(errors)) for (const error of errors) {
27
+ const failure = recordedError(error, null)
28
+ if (failure) return failure
29
+ }
30
+ return null
31
+ }
@@ -92,6 +92,8 @@ export interface QuoteItemInput {
92
92
  }
93
93
 
94
94
  export interface ValidationError {
95
+ lineId?: string
96
+ adjustmentId?: string
95
97
  type?: 'attribute' | 'relationship'
96
98
  attribute?: string
97
99
  relationshipType?: string
@@ -137,7 +139,20 @@ export interface QuoteLineResult {
137
139
  targetSubscriptionId: string | null
138
140
  }
139
141
 
140
- export interface PricingSummary {
142
+ export interface PricingWaterfallSummary {
143
+ listNrcTotal: number
144
+ listMrcTotal: number
145
+ standardNrcTotal: number
146
+ standardMrcTotal: number
147
+ ruleDiscountTotal: number
148
+ manualDiscountTotal: number
149
+ dealValue: { list: number; standard: number; offer: number }
150
+ discountDepth: { rule: number | null; manual: number | null; total: number | null; maxLineManual: number | null }
151
+ hasTimeBasedAdjustments: boolean
152
+ snapshotHash: string
153
+ }
154
+
155
+ export interface PricingSummary extends Partial<PricingWaterfallSummary> {
141
156
  nrcTotal: number
142
157
  mrcTotal: number
143
158
  usageCharges: Array<{
@@ -153,6 +168,8 @@ export interface PricingSummary {
153
168
  }
154
169
 
155
170
  export interface QuoteResult {
171
+ adjustmentErrors?: import('../data/price-adjustments').AdjustmentError[]
172
+ pricingRevision?: number
156
173
  id: string
157
174
  quoteId: string
158
175
  quoteNumber: string
@@ -180,6 +197,16 @@ export interface QuoteResult {
180
197
  // ─── Resolved charge (extended for v2) ───────────────────────────
181
198
 
182
199
  export interface RuleAdjustment {
200
+ adjustmentId?: string
201
+ class?: 'rep' | 'carried_over'
202
+ level?: 'charge' | 'line' | 'quote'
203
+ windowKind?: 'relative' | 'absolute'
204
+ fromMonth?: number
205
+ toMonth?: number | null
206
+ reasonCode?: string
207
+ createdByUserId?: string
208
+ /** Missing on legacy snapshots. */
209
+ origin?: 'rule' | 'manual'
183
210
  ruleCode: string
184
211
  ruleName: string
185
212
  ruleType: string
@@ -210,6 +237,12 @@ export interface ResolvedCharge {
210
237
  quantity: number | null
211
238
  totalPrice: number | null
212
239
  currencyCode: string
240
+ /** Additive waterfall fields; absent on snapshots saved before negotiated pricing. */
241
+ adjustmentErrors?: import('../data/price-adjustments').AdjustmentError[]
242
+ listUnitPrice?: number
243
+ listTotalPrice?: number | null
244
+ standardUnitPrice?: number
245
+ standardTotalPrice?: number | null
213
246
  breakdown?: TierBreakdown | null
214
247
  note?: string | null
215
248
  adjustments?: RuleAdjustment[] | null
@@ -1,3 +1,4 @@
1
+ import { seedPriceAdjustmentDefaults } from './lib/priceAdjustmentDefaults'
1
2
  import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
2
3
 
3
4
  /**
@@ -9,7 +10,7 @@ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
9
10
  *
10
11
  * What still lives here:
11
12
  * - `defaultRoleFeatures` — required for any tenant that has cpq enabled
12
- * - structural defaults (currently none beyond role features)
13
+ * - structural defaults (price adjustment reasons and rollout toggle)
13
14
  *
14
15
  * What used to live here and moved out:
15
16
  * - GIX product/pricing/charge/rule/bundle/wizard seed → `src/modules/demo_gix/`
@@ -17,10 +18,14 @@ import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
17
18
  * - GIX wizard definition → `src/modules/demo_gix/seeds/wizards.ts`
18
19
  */
19
20
  export const setup: ModuleSetupConfig = {
21
+ async seedDefaults({ em, tenantId, organizationId }) {
22
+ if (tenantId && organizationId) await seedPriceAdjustmentDefaults(em, { tenantId, organizationId })
23
+ },
20
24
  defaultRoleFeatures: {
21
25
  admin: [
22
26
  'cpq.quotes.view',
23
27
  'cpq.quotes.manage',
28
+ 'cpq.quotes.adjust_price',
24
29
  'cpq.pricing.view',
25
30
  'cpq.pricing.manage',
26
31
  'cpq.products.view',
@@ -44,6 +49,7 @@ export const setup: ModuleSetupConfig = {
44
49
  employee: [
45
50
  'cpq.quotes.view',
46
51
  'cpq.quotes.manage',
52
+ 'cpq.quotes.adjust_price',
47
53
  'cpq.pricing.view',
48
54
  'cpq.products.view',
49
55
  'cpq.specifications.view',
@@ -2,14 +2,15 @@
2
2
  import * as React from 'react'
3
3
  import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
4
4
  import { useRouter } from 'next/navigation'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
5
6
  import { Tag } from '@open-mercato/ui/primitives/tag'
6
7
  import {
7
8
  assetStatusMap,
8
- formatStatusLabel,
9
9
  subscriptionStatusMap,
10
10
  type AssetStatus,
11
11
  type SubscriptionStatus,
12
12
  } from '../../../components/statusMaps'
13
+ import { inventoryStatusLabel } from '../../../backend/cpq/inventory/shared'
13
14
 
14
15
  // ─── Types ───────────────────────────────────────────────────────
15
16
 
@@ -79,13 +80,14 @@ function fmt(amount: number, currency: string): string {
79
80
  }
80
81
 
81
82
  function StatusBadge({ status }: { status: string }) {
83
+ const t = useT()
82
84
  const variant =
83
85
  subscriptionStatusMap[status as SubscriptionStatus] ??
84
86
  assetStatusMap[status as AssetStatus] ??
85
87
  'neutral'
86
88
  return (
87
89
  <Tag variant={variant} dot>
88
- {formatStatusLabel(status)}
90
+ {inventoryStatusLabel(t, status)}
89
91
  </Tag>
90
92
  )
91
93
  }
@@ -93,6 +95,7 @@ function StatusBadge({ status }: { status: string }) {
93
95
  // ─── Main Widget ─────────────────────────────────────────────────
94
96
 
95
97
  export default function CustomerInventoryWidget({ data, context }: InjectionWidgetComponentProps & { context?: Record<string, unknown> }) {
98
+ const t = useT()
96
99
  const router = useRouter()
97
100
  const record = data as Record<string, unknown> | null
98
101
  const companyRecord = record?.company as Record<string, unknown> | undefined
@@ -121,12 +124,12 @@ export default function CustomerInventoryWidget({ data, context }: InjectionWidg
121
124
  return () => { cancelled = true }
122
125
  }, [customerId])
123
126
 
124
- if (!customerId) return <div className="p-4 text-sm text-muted-foreground">No customer context available.</div>
127
+ if (!customerId) return <div className="p-4 text-sm text-muted-foreground">{t('cpq.widgets.customerInventory.noContext', 'No customer context available.')}</div>
125
128
 
126
129
  if (loading) {
127
130
  return (
128
131
  <div className="flex items-center gap-2 p-6 text-sm text-muted-foreground">
129
- <Spinner /> Loading inventory…
132
+ <Spinner /> {t('cpq.widgets.customerInventory.loading', 'Loading inventory…')}
130
133
  </div>
131
134
  )
132
135
  }
@@ -139,7 +142,7 @@ export default function CustomerInventoryWidget({ data, context }: InjectionWidg
139
142
  <path strokeLinecap="round" strokeLinejoin="round" d="M20.25 7.5l-.625 10.632a2.25 2.25 0 01-2.247 2.118H6.622a2.25 2.25 0 01-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125z" />
140
143
  </svg>
141
144
  </div>
142
- <p className="text-sm text-muted-foreground">No inventory records found.</p>
145
+ <p className="text-sm text-muted-foreground">{t('cpq.widgets.customerInventory.empty', 'No inventory records found.')}</p>
143
146
  </div>
144
147
  )
145
148
  }
@@ -148,19 +151,19 @@ export default function CustomerInventoryWidget({ data, context }: InjectionWidg
148
151
  const currency = summary.currencyCode || 'USD'
149
152
 
150
153
  const tabs: { key: TabKey; label: string; count: number }[] = [
151
- { key: 'subscriptions', label: 'Subscriptions', count: inventory.subscriptions.length },
152
- { key: 'items', label: 'Subscription Items', count: inventory.subscriptionItems.length },
153
- { key: 'assets', label: 'Assets', count: inventory.assets.length },
154
+ { key: 'subscriptions', label: t('cpq.widgets.customerInventory.tabs.subscriptions', 'Subscriptions'), count: inventory.subscriptions.length },
155
+ { key: 'items', label: t('cpq.widgets.customerInventory.tabs.items', 'Subscription Items'), count: inventory.subscriptionItems.length },
156
+ { key: 'assets', label: t('cpq.widgets.customerInventory.tabs.assets', 'Assets'), count: inventory.assets.length },
154
157
  ]
155
158
 
156
159
  return (
157
160
  <div className="space-y-4">
158
161
  {/* Summary Cards */}
159
162
  <div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
160
- <SummaryCard label="Active Subscriptions" value={String(summary.activeSubscriptions)} />
161
- <SummaryCard label="Total MRC" value={fmt(summary.totalMrc, currency)} mono />
162
- <SummaryCard label="Active Assets" value={String(summary.activeAssets)} />
163
- <SummaryCard label="Pending Items" value={String(summary.pendingItems)} />
163
+ <SummaryCard label={t('cpq.widgets.customerInventory.summary.activeSubscriptions', 'Active Subscriptions')} value={String(summary.activeSubscriptions)} />
164
+ <SummaryCard label={t('cpq.widgets.customerInventory.summary.totalMrc', 'Total MRC')} value={fmt(summary.totalMrc, currency)} mono />
165
+ <SummaryCard label={t('cpq.widgets.customerInventory.summary.activeAssets', 'Active Assets')} value={String(summary.activeAssets)} />
166
+ <SummaryCard label={t('cpq.widgets.customerInventory.summary.pendingItems', 'Pending Items')} value={String(summary.pendingItems)} />
164
167
  </div>
165
168
 
166
169
  {/* Tab Switcher */}
@@ -189,7 +192,7 @@ export default function CustomerInventoryWidget({ data, context }: InjectionWidg
189
192
  {/* Link to full page */}
190
193
  <div className="pt-2 text-right">
191
194
  <a href="/backend/cpq/inventory" className="text-sm text-primary hover:underline">
192
- View full inventory →
195
+ {t('cpq.widgets.customerInventory.viewFull', 'View full inventory →')}
193
196
  </a>
194
197
  </div>
195
198
  </div>
@@ -210,18 +213,19 @@ function SummaryCard({ label, value, mono }: { label: string; value: string; mon
210
213
  // ─── Subscriptions Table ─────────────────────────────────────────
211
214
 
212
215
  function SubscriptionsTable({ items, currency, onOpen }: { items: Subscription[]; currency: string; onOpen: (id: string) => void }) {
216
+ const t = useT()
213
217
  if (items.length === 0) return <EmptyTab />
214
218
  return (
215
219
  <div className="rounded-lg border bg-card overflow-hidden">
216
220
  <table className="w-full text-sm">
217
221
  <thead>
218
222
  <tr className="border-b border-border bg-muted/40 text-muted-foreground">
219
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Name</th>
220
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Status</th>
221
- <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">MRC</th>
222
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Billing</th>
223
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Start Date</th>
224
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Term End</th>
223
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.name', 'Name')}</th>
224
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.status', 'Status')}</th>
225
+ <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.mrc', 'MRC')}</th>
226
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.billing', 'Billing')}</th>
227
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.startDate', 'Start Date')}</th>
228
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.termEnd', 'Term End')}</th>
225
229
  </tr>
226
230
  </thead>
227
231
  <tbody>
@@ -248,17 +252,18 @@ function SubscriptionsTable({ items, currency, onOpen }: { items: Subscription[]
248
252
  // ─── Subscription Items Table ────────────────────────────────────
249
253
 
250
254
  function SubscriptionItemsTable({ items, currency, onOpen }: { items: SubscriptionItem[]; currency: string; onOpen: (subscriptionId: string) => void }) {
255
+ const t = useT()
251
256
  if (items.length === 0) return <EmptyTab />
252
257
  return (
253
258
  <div className="rounded-lg border bg-card overflow-hidden">
254
259
  <table className="w-full text-sm">
255
260
  <thead>
256
261
  <tr className="border-b border-border bg-muted/40 text-muted-foreground">
257
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Name</th>
258
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Status</th>
259
- <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">MRC</th>
260
- <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">NRC</th>
261
- <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">Qty</th>
262
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.name', 'Name')}</th>
263
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.status', 'Status')}</th>
264
+ <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.mrc', 'MRC')}</th>
265
+ <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.nrc', 'NRC')}</th>
266
+ <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.qty', 'Qty')}</th>
262
267
  </tr>
263
268
  </thead>
264
269
  <tbody>
@@ -284,17 +289,18 @@ function SubscriptionItemsTable({ items, currency, onOpen }: { items: Subscripti
284
289
  // ─── Assets Table ────────────────────────────────────────────────
285
290
 
286
291
  function AssetsTable({ items, currency, onOpen }: { items: Asset[]; currency: string; onOpen: (id: string) => void }) {
292
+ const t = useT()
287
293
  if (items.length === 0) return <EmptyTab />
288
294
  return (
289
295
  <div className="rounded-lg border bg-card overflow-hidden">
290
296
  <table className="w-full text-sm">
291
297
  <thead>
292
298
  <tr className="border-b border-border bg-muted/40 text-muted-foreground">
293
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Name</th>
294
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Type</th>
295
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Status</th>
296
- <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">Price</th>
297
- <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">Linked To</th>
299
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.name', 'Name')}</th>
300
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.type', 'Type')}</th>
301
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.status', 'Status')}</th>
302
+ <th className="px-4 py-2.5 text-right text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.price', 'Price')}</th>
303
+ <th className="px-4 py-2.5 text-left text-xs font-medium uppercase tracking-wide">{t('cpq.widgets.customerInventory.table.linkedTo', 'Linked To')}</th>
298
304
  </tr>
299
305
  </thead>
300
306
  <tbody>
@@ -309,7 +315,7 @@ function AssetsTable({ items, currency, onOpen }: { items: Asset[]; currency: st
309
315
  <td className="px-4 py-3"><StatusBadge status={a.status} /></td>
310
316
  <td className="px-4 py-3 text-right font-mono">{fmt(a.purchasePrice, a.currencyCode || currency)}</td>
311
317
  <td className="px-4 py-3 text-muted-foreground">
312
- {a.subscriptionId ? `Sub ${a.subscriptionId.slice(0, 8)}…` : '—'}
318
+ {a.subscriptionId ? t('cpq.widgets.customerInventory.table.linkedSubscription', 'Sub {id}…', { id: a.subscriptionId.slice(0, 8) }) : '—'}
313
319
  </td>
314
320
  </tr>
315
321
  ))}
@@ -322,9 +328,10 @@ function AssetsTable({ items, currency, onOpen }: { items: Asset[]; currency: st
322
328
  // ─── Shared ──────────────────────────────────────────────────────
323
329
 
324
330
  function EmptyTab() {
331
+ const t = useT()
325
332
  return (
326
333
  <div className="rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground">
327
- No records in this category.
334
+ {t('cpq.widgets.customerInventory.emptyTab', 'No records in this category.')}
328
335
  </div>
329
336
  )
330
337
  }
@@ -3,6 +3,7 @@ import * as React from 'react'
3
3
  import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
4
4
  import { useRouter } from 'next/navigation'
5
5
  import { Tag } from '@open-mercato/ui/primitives/tag'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
7
  import {
7
8
  formatStatusLabel,
8
9
  orderCpqStatusMap,
@@ -28,6 +29,7 @@ function fmt(amount: number | undefined | null, currency: string): string {
28
29
 
29
30
  export default function OrderCpqTabWidget({ data }: InjectionWidgetComponentProps<any, any>) {
30
31
  const router = useRouter()
32
+ const t = useT()
31
33
  const salesOrderId = data?.id as string | undefined
32
34
  const [cpqOrders, setCpqOrders] = React.useState<CpqOrderSummary[]>([])
33
35
  const [loading, setLoading] = React.useState(true)
@@ -52,7 +54,7 @@ export default function OrderCpqTabWidget({ data }: InjectionWidgetComponentProp
52
54
  <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
53
55
  <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" />
54
56
  </svg>
55
- Loading CPQ orders…
57
+ {t('cpq.widgets.orderCpqTab.loading', 'Loading CPQ orders…')}
56
58
  </div>
57
59
  )
58
60
  }
@@ -60,7 +62,7 @@ export default function OrderCpqTabWidget({ data }: InjectionWidgetComponentProp
60
62
  if (cpqOrders.length === 0) {
61
63
  return (
62
64
  <div className="py-8 text-center text-sm text-muted-foreground">
63
- No CPQ orders linked to this sales order.
65
+ {t('cpq.widgets.orderCpqTab.empty', 'No CPQ orders linked to this sales order.')}
64
66
  </div>
65
67
  )
66
68
  }
@@ -82,12 +84,12 @@ export default function OrderCpqTabWidget({ data }: InjectionWidgetComponentProp
82
84
  <div className="flex items-center gap-4 text-xs">
83
85
  {o.pricingSummary && (
84
86
  <>
85
- <span><span className="text-muted-foreground">NRC </span><span className="font-mono font-medium">{fmt(o.pricingSummary.nrcTotal, o.currencyCode)}</span></span>
86
- <span><span className="text-muted-foreground">MRC </span><span className="font-mono font-medium">{fmt(o.pricingSummary.mrcTotal, o.currencyCode)}</span></span>
87
+ <span><span className="text-muted-foreground">{t('cpq.lines.nrc', 'NRC')} </span><span className="font-mono font-medium">{fmt(o.pricingSummary.nrcTotal, o.currencyCode)}</span></span>
88
+ <span><span className="text-muted-foreground">{t('cpq.lines.mrc', 'MRC')} </span><span className="font-mono font-medium">{fmt(o.pricingSummary.mrcTotal, o.currencyCode)}</span></span>
87
89
  </>
88
90
  )}
89
91
  <span className="text-muted-foreground">{new Date(o.createdAt).toLocaleDateString()}</span>
90
- <span className="text-primary font-medium">Open →</span>
92
+ <span className="text-primary font-medium">{t('cpq.widgets.orderCpqTab.open', 'Open →')}</span>
91
93
  </div>
92
94
  </div>
93
95
  ))}