@dainamite/cpq 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/MIGRATION.md +74 -0
  3. package/dist/modules/cpq/acl.js +1 -0
  4. package/dist/modules/cpq/acl.js.map +2 -2
  5. package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js +45 -0
  6. package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js.map +7 -0
  7. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js +23 -0
  8. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js.map +7 -0
  9. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js +19 -0
  10. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js.map +7 -0
  11. package/dist/modules/cpq/api/orders/[orderId]/route.js +17 -1
  12. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
  13. package/dist/modules/cpq/api/orders/route.js +47 -2
  14. package/dist/modules/cpq/api/orders/route.js.map +2 -2
  15. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js +54 -0
  16. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js.map +7 -0
  17. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +42 -2
  18. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
  19. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -2
  20. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +2 -2
  21. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js +47 -0
  22. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js.map +7 -0
  23. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js +30 -0
  24. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js.map +7 -0
  25. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js +47 -0
  26. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js.map +7 -0
  27. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +32 -2
  28. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
  29. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +33 -2
  30. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +2 -2
  31. package/dist/modules/cpq/api/quotes/price/route.js +43 -3
  32. package/dist/modules/cpq/api/quotes/price/route.js.map +2 -2
  33. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +1 -1
  34. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +1 -1
  35. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +22 -8
  36. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  37. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +89 -232
  38. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  39. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
  40. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
  41. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +205 -0
  42. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
  43. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
  44. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
  45. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
  46. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
  47. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
  48. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
  49. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
  50. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
  51. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +144 -221
  52. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
  53. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
  54. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
  55. package/dist/modules/cpq/ce.js +8 -0
  56. package/dist/modules/cpq/ce.js.map +2 -2
  57. package/dist/modules/cpq/cli.js +34 -1
  58. package/dist/modules/cpq/cli.js.map +2 -2
  59. package/dist/modules/cpq/commands/price-adjustments.js +107 -0
  60. package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
  61. package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
  62. package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
  63. package/dist/modules/cpq/components/ChargeBreakdownTable.js +154 -0
  64. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
  65. package/dist/modules/cpq/components/PriceWaterfall.js +66 -0
  66. package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
  67. package/dist/modules/cpq/components/PricingSummaryCard.js +117 -0
  68. package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
  69. package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
  70. package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
  71. package/dist/modules/cpq/data/entities.js +97 -0
  72. package/dist/modules/cpq/data/entities.js.map +2 -2
  73. package/dist/modules/cpq/data/price-adjustments.js +70 -0
  74. package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
  75. package/dist/modules/cpq/data/validators.js +4 -0
  76. package/dist/modules/cpq/data/validators.js.map +2 -2
  77. package/dist/modules/cpq/di.js +6 -0
  78. package/dist/modules/cpq/di.js.map +2 -2
  79. package/dist/modules/cpq/events.js +47 -0
  80. package/dist/modules/cpq/events.js.map +2 -2
  81. package/dist/modules/cpq/index.js +1 -1
  82. package/dist/modules/cpq/index.js.map +2 -2
  83. package/dist/modules/cpq/lib/auditActor.js +10 -0
  84. package/dist/modules/cpq/lib/auditActor.js.map +7 -0
  85. package/dist/modules/cpq/lib/formatMoney.js +7 -0
  86. package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
  87. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
  88. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
  89. package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
  90. package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
  91. package/dist/modules/cpq/lib/quotePermissions.js +13 -0
  92. package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
  93. package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
  94. package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
  95. package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
  96. package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
  97. package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
  98. package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
  99. package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
  100. package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
  101. package/dist/modules/cpq/services/cpqOrderService.js +25 -28
  102. package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
  103. package/dist/modules/cpq/services/cpqPricingService.js +7 -0
  104. package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
  105. package/dist/modules/cpq/services/cpqQuotingService.js +330 -109
  106. package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
  107. package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
  108. package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
  109. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
  110. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
  111. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
  112. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
  113. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
  114. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
  115. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
  116. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
  117. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
  118. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
  119. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
  120. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
  121. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
  122. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
  123. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
  124. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
  125. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
  126. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
  127. package/dist/modules/cpq/services/types.js.map +2 -2
  128. package/dist/modules/cpq/setup.js +6 -0
  129. package/dist/modules/cpq/setup.js.map +2 -2
  130. package/package.json +1 -1
  131. package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
  132. package/src/modules/cpq/acl.ts +1 -0
  133. package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
  134. package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
  135. package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
  136. package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
  137. package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
  138. package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
  139. package/src/modules/cpq/api/orders/[orderId]/route.ts +18 -0
  140. package/src/modules/cpq/api/orders/route.ts +51 -1
  141. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
  142. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +48 -1
  143. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
  144. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
  145. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
  146. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
  147. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
  148. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +40 -1
  149. package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
  150. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
  151. package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
  152. package/src/modules/cpq/api/quotes/price/route.ts +28 -2
  153. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +1 -1
  154. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
  155. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +60 -269
  156. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
  157. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
  158. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
  159. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
  160. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
  161. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
  162. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
  163. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +174 -263
  164. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
  165. package/src/modules/cpq/ce.ts +8 -0
  166. package/src/modules/cpq/cli.ts +48 -1
  167. package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
  168. package/src/modules/cpq/commands/price-adjustments.ts +90 -0
  169. package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
  170. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +201 -0
  171. package/src/modules/cpq/components/PriceWaterfall.tsx +94 -0
  172. package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
  173. package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
  174. package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
  175. package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
  176. package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
  177. package/src/modules/cpq/data/entities.ts +83 -0
  178. package/src/modules/cpq/data/price-adjustments.ts +67 -0
  179. package/src/modules/cpq/data/validators.ts +3 -0
  180. package/src/modules/cpq/di.ts +6 -0
  181. package/src/modules/cpq/events.ts +53 -2
  182. package/src/modules/cpq/index.ts +1 -1
  183. package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
  184. package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
  185. package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
  186. package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
  187. package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
  188. package/src/modules/cpq/lib/auditActor.ts +13 -0
  189. package/src/modules/cpq/lib/formatMoney.ts +4 -0
  190. package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
  191. package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
  192. package/src/modules/cpq/lib/quotePermissions.ts +22 -0
  193. package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
  194. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
  195. package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
  196. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
  197. package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
  198. package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
  199. package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
  200. package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
  201. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
  202. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
  203. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
  204. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
  205. package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
  206. package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
  207. package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
  208. package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
  209. package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
  210. package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
  211. package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
  212. package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
  213. package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
  214. package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
  215. package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
  216. package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
  217. package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
  218. package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
  219. package/src/modules/cpq/services/cpqOrderService.ts +22 -28
  220. package/src/modules/cpq/services/cpqPricingService.ts +8 -0
  221. package/src/modules/cpq/services/cpqQuotingService.ts +374 -125
  222. package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
  223. package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
  224. package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
  225. package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
  226. package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
  227. package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
  228. package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
  229. package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
  230. package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
  231. package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
  232. package/src/modules/cpq/services/types.ts +34 -1
  233. package/src/modules/cpq/setup.ts +7 -1
@@ -1,15 +1,27 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
5
  import { useRouter, useParams, useSearchParams } from "next/navigation";
5
6
  import ArcQuoteConfigurator from "./_components/ArcQuoteConfigurator.js";
6
7
  import { StatusPath } from "../../../../components/StatusPath.js";
7
- import { ArrowRight, ExternalLink, Plus } from "lucide-react";
8
+ import { ArrowRight, ExternalLink, Plus, RotateCcw } from "lucide-react";
8
9
  import { Alert, AlertTitle, AlertDescription } from "@open-mercato/ui/primitives/alert";
9
10
  import { Button } from "@open-mercato/ui/primitives/button";
10
11
  import { Tag } from "@open-mercato/ui/primitives/tag";
11
12
  import { FormHeader } from "@open-mercato/ui/backend/forms";
12
13
  import { NumberInput } from "../../../../components/NumberInput.js";
14
+ import { ChargeBreakdownTable } from "../../../../components/ChargeBreakdownTable.js";
15
+ import { PricingSummaryCard } from "../../../../components/PricingSummaryCard.js";
16
+ import { ArcLineDiff, LineActionBadge } from "../../../../components/ArcLineDiff.js";
17
+ import { ADJUST_PRICE_FEATURE } from "../../../../lib/priceAdjustmentView.js";
18
+ import { arcRecurringDelta } from "../../../../lib/quoteSummaryView.js";
19
+ import { usePriceAdjustments } from "./_components/usePriceAdjustments.js";
20
+ import { PriceAdjustmentsPanel } from "./_components/PriceAdjustmentsPanel.js";
21
+ import { ArcLineRemovalProvider, useArcLineRemoval, useArcLineRemovalUi } from "./_components/arcLineRemoval.js";
22
+ import { AdjustChargeAction, DiscountLineAction, ManualAdjustmentActions, PriceAdjustmentProvider, usePriceAdjustmentUi } from "./_components/priceAdjustmentContext.js";
23
+ import { useGrantedFeatures } from "../../../../components/useGrantedFeatures.js";
24
+ import { canRecalculateQuote, QUOTE_MANAGE_FEATURE } from "../../../../lib/quotePermissions.js";
13
25
  import {
14
26
  formatStatusLabel,
15
27
  quoteCpqStatusMap
@@ -17,18 +29,6 @@ import {
17
29
  function fmt(amount, currency) {
18
30
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
19
31
  }
20
- const RULE_TYPE_LABELS = {
21
- discount_percent: "% discount",
22
- discount_absolute: "fixed discount",
23
- surcharge_percent: "% surcharge",
24
- surcharge_absolute: "fixed surcharge",
25
- price_override: "override"
26
- };
27
- function fmtRuleValue(adj, currency) {
28
- if (adj.ruleType === "discount_percent" || adj.ruleType === "surcharge_percent") return `${adj.value}%`;
29
- if (currency) return fmt(adj.value, currency);
30
- return String(adj.value);
31
- }
32
32
  async function apiJson(url, init) {
33
33
  const res = await fetch(url, init);
34
34
  const json = await res.json();
@@ -82,20 +82,22 @@ function MoreActionsDropdown({ items }) {
82
82
  }
83
83
  function buildMoreActions({
84
84
  recalculate,
85
+ canRecalculate,
85
86
  submitting,
86
87
  deletable,
87
88
  onDelete,
88
89
  deleting
89
90
  }) {
90
- const items = [
91
- {
91
+ const items = [];
92
+ if (canRecalculate) {
93
+ items.push({
92
94
  id: "recalculate",
93
95
  label: "Recalculate",
94
96
  onSelect: recalculate,
95
97
  loading: submitting,
96
98
  disabled: submitting
97
- }
98
- ];
99
+ });
100
+ }
99
101
  if (deletable) {
100
102
  items.push({
101
103
  id: "delete",
@@ -113,7 +115,9 @@ function CpqQuoteDetailPage(props) {
113
115
  const searchParams = useSearchParams();
114
116
  const cpqConfigId = props.params?.id ?? urlParams?.id;
115
117
  const isNew = cpqConfigId === "new";
118
+ const t = useT();
116
119
  const [cpqQuote, setCpqQuote] = React.useState(null);
120
+ const grantedFeatures = useGrantedFeatures([QUOTE_MANAGE_FEATURE, ADJUST_PRICE_FEATURE]);
117
121
  const [loading, setLoading] = React.useState(true);
118
122
  const [error, setError] = React.useState(null);
119
123
  const [submitting, setSubmitting] = React.useState(false);
@@ -156,6 +160,20 @@ function CpqQuoteDetailPage(props) {
156
160
  if (!cpqConfigId || isNew) return;
157
161
  void loadArcTargets();
158
162
  }, [cpqConfigId, isNew, loadArcTargets]);
163
+ const reloadQuote = React.useCallback(async () => {
164
+ try {
165
+ setCpqQuote(await apiJson(`/api/cpq/quotes/${cpqConfigId}`));
166
+ } catch (err) {
167
+ setError(err.message);
168
+ }
169
+ }, [cpqConfigId]);
170
+ const priceAdjustments = usePriceAdjustments({
171
+ quote: isNew ? null : cpqQuote,
172
+ quoteType: arcQuoteType,
173
+ canAdjust: grantedFeatures.includes(QUOTE_MANAGE_FEATURE) && grantedFeatures.includes(ADJUST_PRICE_FEATURE),
174
+ onQuote: setCpqQuote,
175
+ reloadQuote
176
+ });
159
177
  React.useEffect(() => {
160
178
  if (isNew) {
161
179
  const quoteId = searchParams.get("quoteId");
@@ -265,16 +283,13 @@ function CpqQuoteDetailPage(props) {
265
283
  setSubmitting(false);
266
284
  }
267
285
  };
268
- const removeItem = async (lineId) => {
269
- if (!cpqQuote) return;
270
- try {
271
- setError(null);
272
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, { method: "DELETE" });
273
- setCpqQuote(result);
274
- } catch (err) {
275
- setError(err.message);
276
- }
277
- };
286
+ const lineRemoval = useArcLineRemoval({
287
+ quote: isNew ? null : cpqQuote,
288
+ quoteType: arcQuoteType,
289
+ onQuote: setCpqQuote,
290
+ onError: setError
291
+ });
292
+ const removeItem = lineRemoval.remove;
278
293
  const recalculate = async () => {
279
294
  if (!cpqQuote) return;
280
295
  try {
@@ -350,7 +365,7 @@ function CpqQuoteDetailPage(props) {
350
365
  };
351
366
  const startAddOffering = () => {
352
367
  resetConfigState();
353
- setAddingTargetSubIds(arcTargets.map((t) => t.subscriptionId));
368
+ setAddingTargetSubIds(arcTargets.map((t2) => t2.subscriptionId));
354
369
  loadOfferings();
355
370
  setView("add-offering");
356
371
  };
@@ -413,13 +428,16 @@ function CpqQuoteDetailPage(props) {
413
428
  ] });
414
429
  }
415
430
  const { pricingSummary: summary, currencyCode: currency } = cpqQuote;
431
+ const arcDelta = arcQuoteType === "amend" || arcQuoteType === "renew" ? arcRecurringDelta(cpqQuote.lines) : null;
416
432
  const arcEditable = ["new", "incomplete", "ready"].includes(cpqQuote.cpqStatus);
417
- const isMergeRenew = arcQuoteType === "renew" && arcTargets.length >= 2 && arcTargets.every((t) => t.mergeAction === "absorb");
433
+ const isMergeRenew = arcQuoteType === "renew" && arcTargets.length >= 2 && arcTargets.every((t2) => t2.mergeAction === "absorb");
418
434
  const arcTargetCodeMap = /* @__PURE__ */ new Map();
419
- for (const t of arcTargets) {
420
- if (t.subscription?.code) arcTargetCodeMap.set(t.subscriptionId, t.subscription.code);
435
+ for (const t2 of arcTargets) {
436
+ if (t2.subscription?.code) arcTargetCodeMap.set(t2.subscriptionId, t2.subscription.code);
421
437
  }
422
- return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
438
+ return /* @__PURE__ */ jsx(PriceAdjustmentProvider, { value: priceAdjustments.ui, children: /* @__PURE__ */ jsx(ArcLineRemovalProvider, { value: lineRemoval.ui, children: /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
439
+ priceAdjustments.elements,
440
+ lineRemoval.ConfirmDialogElement,
423
441
  arcDrawerOpen && /* @__PURE__ */ jsx(
424
442
  ArcQuoteConfigurator,
425
443
  {
@@ -495,6 +513,7 @@ function CpqQuoteDetailPage(props) {
495
513
  {
496
514
  items: buildMoreActions({
497
515
  recalculate,
516
+ canRecalculate: canRecalculateQuote(grantedFeatures, cpqQuote.cpqStatus),
498
517
  submitting,
499
518
  deletable: !["accepted", "rejected", "cancelled"].includes(cpqQuote.cpqStatus),
500
519
  onDelete: () => setShowDeleteConfirm(true),
@@ -530,17 +549,17 @@ function CpqQuoteDetailPage(props) {
530
549
  arcTargets.length === 1 ? "" : "s",
531
550
  ":",
532
551
  " ",
533
- arcTargets.map((t, i) => /* @__PURE__ */ jsxs("span", { children: [
552
+ arcTargets.map((t2, i) => /* @__PURE__ */ jsxs("span", { children: [
534
553
  i > 0 && ", ",
535
554
  /* @__PURE__ */ jsx(
536
555
  "button",
537
556
  {
538
- onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${t.subscriptionId}`),
557
+ onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${t2.subscriptionId}`),
539
558
  className: "text-brand-violet hover:underline",
540
- children: t.subscription?.code ?? t.subscriptionId.slice(0, 8)
559
+ children: t2.subscription?.code ?? t2.subscriptionId.slice(0, 8)
541
560
  }
542
561
  )
543
- ] }, t.id)),
562
+ ] }, t2.id)),
544
563
  isMergeRenew && /* @__PURE__ */ jsx("span", { className: "ml-2 text-status-warning-text font-medium", children: "\u2022 Merging into a new contract" })
545
564
  ] }) }) }),
546
565
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3", children: [
@@ -566,32 +585,7 @@ function CpqQuoteDetailPage(props) {
566
585
  ] })
567
586
  ] }) }),
568
587
  view === "summary" && /* @__PURE__ */ jsxs(Fragment, { children: [
569
- /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
570
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-3", children: "Pricing Summary" }),
571
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-4", children: [
572
- /* @__PURE__ */ jsxs("div", { children: [
573
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Non-Recurring (NRC)" }),
574
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.nrcTotal, currency) })
575
- ] }),
576
- /* @__PURE__ */ jsxs("div", { children: [
577
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Monthly Recurring (MRC)" }),
578
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.mrcTotal, currency) })
579
- ] }),
580
- summary.usageTotalEstimated != null && /* @__PURE__ */ jsxs("div", { children: [
581
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Usage (est.)" }),
582
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.usageTotalEstimated, currency) })
583
- ] }),
584
- summary.usageCharges.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
585
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Usage Charges" }),
586
- summary.usageCharges.map((uc, i) => /* @__PURE__ */ jsxs("p", { className: "text-xs mt-0.5", children: [
587
- uc.chargeName,
588
- ": ",
589
- fmt(uc.unitPrice, currency),
590
- "/unit"
591
- ] }, i))
592
- ] })
593
- ] })
594
- ] }),
588
+ /* @__PURE__ */ jsx(PricingSummaryCard, { summary, currency, arcDelta }),
595
589
  !cpqQuote.validationResult.valid && /* @__PURE__ */ jsxs(Alert, { variant: "warning", children: [
596
590
  /* @__PURE__ */ jsx(AlertTitle, { children: "Validation Issues" }),
597
591
  /* @__PURE__ */ jsx(AlertDescription, { children: /* @__PURE__ */ jsx("span", { className: "text-xs space-y-0.5 block", children: cpqQuote.validationResult.errors.map((e, i) => /* @__PURE__ */ jsxs("span", { className: "block", children: [
@@ -599,6 +593,13 @@ function CpqQuoteDetailPage(props) {
599
593
  e.message
600
594
  ] }, i)) }) })
601
595
  ] }),
596
+ arcQuoteType === "amend" && cpqQuote.lines.some((l) => l.action !== "cancel" && l.configuration?._arcRepriced === true) && /* @__PURE__ */ jsxs(Alert, { status: "information", style: "lighter", children: [
597
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("cpq.quotes.arc.repricedTitle", "Some existing items use the current catalog price") }),
598
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
599
+ "cpq.quotes.arc.repricedDescription",
600
+ "Reconfigured lines were re-priced at today's catalog price. Negotiated prices from the subscription are not carried over to reconfigured lines yet."
601
+ ) })
602
+ ] }),
602
603
  /* @__PURE__ */ jsx(
603
604
  QuoteLineTree,
604
605
  {
@@ -615,6 +616,17 @@ function CpqQuoteDetailPage(props) {
615
616
  pricingDetail?.lineIdx === lineIdx && pricingDetail?.chargeIdx === chargeIdx ? null : { lineIdx, chargeIdx }
616
617
  )
617
618
  }
619
+ ),
620
+ /* @__PURE__ */ jsx(
621
+ PriceAdjustmentsPanel,
622
+ {
623
+ quoteId: cpqQuote.id,
624
+ items: priceAdjustments.items,
625
+ total: priceAdjustments.total,
626
+ lines: cpqQuote.lines,
627
+ currency,
628
+ errorsById: priceAdjustments.errorsById
629
+ }
618
630
  )
619
631
  ] }),
620
632
  view === "add-offering" && /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -640,10 +652,10 @@ function CpqQuoteDetailPage(props) {
640
652
  attributes,
641
653
  config,
642
654
  quantity,
643
- arcTargetOptions: arcTargets.length >= 2 && !addingToParentLineId ? arcTargets.map((t) => ({
644
- subscriptionId: t.subscriptionId,
645
- code: t.subscription?.code ?? t.subscriptionId.slice(0, 8),
646
- name: t.subscription?.name ?? null
655
+ arcTargetOptions: arcTargets.length >= 2 && !addingToParentLineId ? arcTargets.map((t2) => ({
656
+ subscriptionId: t2.subscriptionId,
657
+ code: t2.subscription?.code ?? t2.subscriptionId.slice(0, 8),
658
+ name: t2.subscription?.name ?? null
647
659
  })) : null,
648
660
  arcSelectedTargetIds: addingTargetSubIds,
649
661
  onArcTargetToggle: (subId) => {
@@ -685,7 +697,7 @@ function CpqQuoteDetailPage(props) {
685
697
  submitting
686
698
  }
687
699
  )
688
- ] });
700
+ ] }) }) });
689
701
  }
690
702
  function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCodes, onToggleLine, onEditLine, onRemoveLine, onAddComponent, onTogglePricingDetail }) {
691
703
  const [bundleTrees, setBundleTrees] = React.useState(/* @__PURE__ */ new Map());
@@ -889,20 +901,48 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
889
901
  ] });
890
902
  }
891
903
  function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBundle, childCount, indent, targetCodes, onToggle, onEdit, onRemove, onAddComponent, onTogglePricingDetail }) {
904
+ const t = useT();
905
+ const adjustmentUi = usePriceAdjustmentUi();
906
+ const removalUi = useArcLineRemovalUi();
907
+ const isRemoved = removalUi?.isRemoved(line) ?? false;
892
908
  const paddingLeft = indent > 0 ? `${indent * 2 + 1}rem` : void 0;
909
+ const chargeActions = {
910
+ reasonLabels: adjustmentUi?.reasonLabels,
911
+ renderChargeActions: (charge) => /* @__PURE__ */ jsx(AdjustChargeAction, { line, charge }),
912
+ renderManualActions: (adjustmentId) => /* @__PURE__ */ jsx(ManualAdjustmentActions, { adjustmentId })
913
+ };
893
914
  return /* @__PURE__ */ jsxs("div", { children: [
894
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors", style: paddingLeft ? { paddingLeft } : void 0, children: [
915
+ /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors ${isRemoved ? "bg-muted/20" : ""}`, style: paddingLeft ? { paddingLeft } : void 0, "data-testid": "cpq-quote-line", "data-removed": isRemoved ? "true" : void 0, children: [
895
916
  /* @__PURE__ */ jsx("button", { onClick: onToggle, className: "flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx("svg", { className: `h-4 w-4 transition-transform ${isExpanded ? "rotate-90" : ""}`, fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) }) }),
896
917
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
897
918
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
898
919
  indent > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: "\u2514" }),
899
920
  isBundle && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-1.5 text-xs", children: "bundle" }),
900
- /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: line.offeringName }),
921
+ /* @__PURE__ */ jsx("span", { className: `font-medium text-sm ${isRemoved ? "text-muted-foreground line-through" : ""}`, children: line.offeringName }),
901
922
  line.quantity > 1 && /* @__PURE__ */ jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs font-medium", children: [
902
923
  "\xD7",
903
924
  line.quantity
904
925
  ] }),
905
- /* @__PURE__ */ jsx(ActionBadge, { action: line.action }),
926
+ /* @__PURE__ */ jsx(
927
+ LineActionBadge,
928
+ {
929
+ action: line.action,
930
+ label: isRemoved ? t("cpq.quotes.arc.lineRemoved", "remove") : void 0,
931
+ title: isRemoved ? t("cpq.quotes.arc.lineRemovedHint", "This item is cancelled on the subscription when the order is activated") : void 0
932
+ }
933
+ ),
934
+ !isRemoved && line.configuration?._arcPriceFrozen === true && /* @__PURE__ */ jsx(
935
+ Tag,
936
+ {
937
+ variant: "neutral",
938
+ className: "px-1.5 text-xs",
939
+ title: t(
940
+ "cpq.quotes.arc.priceKeptHint",
941
+ "Price kept from the subscription \u2014 it changes only if you reconfigure this line"
942
+ ),
943
+ children: t("cpq.quotes.arc.priceKept", "price kept")
944
+ }
945
+ ),
906
946
  targetCodes.size >= 2 && line.targetSubscriptionId && targetCodes.get(line.targetSubscriptionId) && /* @__PURE__ */ jsxs(
907
947
  Tag,
908
948
  {
@@ -915,7 +955,7 @@ function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBu
915
955
  ]
916
956
  }
917
957
  ),
918
- line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: "configured" }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: "incomplete" }),
958
+ isRemoved ? null : line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: "configured" }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: "incomplete" }),
919
959
  isBundle && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
920
960
  childCount,
921
961
  " component",
@@ -925,6 +965,10 @@ function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBu
925
965
  line.validationErrors && line.validationErrors.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-0.5", children: line.validationErrors[0].message })
926
966
  ] }),
927
967
  /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right text-xs space-y-0.5", children: [
968
+ isRemoved && line.arcSource && line.arcSource.mrcAmount > 0 && /* @__PURE__ */ jsxs("div", { children: [
969
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "MRC " }),
970
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground line-through", children: fmt(line.arcSource.mrcAmount, currency) })
971
+ ] }),
928
972
  line.nrcTotal > 0 && /* @__PURE__ */ jsxs("div", { children: [
929
973
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "NRC " }),
930
974
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.nrcTotal, currency) })
@@ -934,65 +978,33 @@ function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBu
934
978
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.mrcTotal, currency) })
935
979
  ] })
936
980
  ] }),
937
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
981
+ isRemoved ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 shrink-0", children: /* @__PURE__ */ jsxs(
982
+ Button,
983
+ {
984
+ type: "button",
985
+ variant: "ghost",
986
+ size: "sm",
987
+ onClick: () => removalUi?.restore(line.lineId),
988
+ title: t("cpq.quotes.arc.restoreHint", "Keep this item on the subscription"),
989
+ "data-testid": "cpq-line-restore",
990
+ children: [
991
+ /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" }),
992
+ t("cpq.quotes.arc.restore", "Restore")
993
+ ]
994
+ }
995
+ ) }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
996
+ /* @__PURE__ */ jsx(DiscountLineAction, { line }),
938
997
  onAddComponent && /* @__PURE__ */ jsx("button", { onClick: onAddComponent, className: "rounded p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors", title: "Add Component", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }) }),
939
998
  /* @__PURE__ */ jsx("button", { onClick: onEdit, className: "rounded p-1.5 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors", title: "Edit", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" }) }) }),
940
999
  /* @__PURE__ */ jsx("button", { onClick: onRemove, className: "rounded p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors", title: "Remove", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" }) }) })
941
1000
  ] })
942
1001
  ] }),
943
- isExpanded && line.arcSource && /* @__PURE__ */ jsx(ArcLineDiff, { line, currency }),
944
- isExpanded && !isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail }),
945
- isExpanded && isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail }),
1002
+ isExpanded && !isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail, ...chargeActions }),
1003
+ isExpanded && isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail, ...chargeActions }),
1004
+ isExpanded && line.arcSource && /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsx(ArcLineDiff, { line, currency }) }),
946
1005
  isExpanded && line.charges.length === 0 && !line.arcSource && /* @__PURE__ */ jsx(LineSummaryFallback, { line, currency })
947
1006
  ] });
948
1007
  }
949
- function ArcLineDiff({ line, currency }) {
950
- const src = line.arcSource;
951
- if (!src) return null;
952
- const isCancel = line.action === "cancel";
953
- const beforeMrc = src.mrcAmount;
954
- const beforeNrc = src.nrcAmount;
955
- const beforeQty = src.quantity;
956
- const afterMrc = isCancel ? 0 : line.mrcTotal;
957
- const afterNrc = isCancel ? 0 : line.nrcTotal;
958
- const afterQty = isCancel ? 0 : line.quantity;
959
- const fmtVal = (v, asCurrency) => asCurrency ? fmt(v, currency) : String(v);
960
- const rows = [
961
- { label: "Quantity", before: beforeQty, after: afterQty, asCurrency: false },
962
- { label: "MRC", before: beforeMrc, after: afterMrc, asCurrency: true },
963
- { label: "NRC", before: beforeNrc, after: afterNrc, asCurrency: true }
964
- ];
965
- return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/10 px-12 py-2 space-y-1.5", children: [
966
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Change Preview" }),
967
- /* @__PURE__ */ jsx("div", { className: "rounded border bg-background overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
968
- /* @__PURE__ */ jsx("thead", { className: "bg-muted/40", children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
969
- /* @__PURE__ */ jsx("th", { className: "text-left px-3 py-1.5 font-medium" }),
970
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "Before" }),
971
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "After" }),
972
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "\u0394" })
973
- ] }) }),
974
- /* @__PURE__ */ jsx("tbody", { children: rows.map((r) => {
975
- const delta = r.after - r.before;
976
- const showDelta = delta !== 0;
977
- return /* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/40", children: [
978
- /* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-muted-foreground", children: r.label }),
979
- /* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-right font-mono", children: fmtVal(r.before, r.asCurrency) }),
980
- /* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono ${isCancel ? "text-muted-foreground line-through" : ""}`, children: fmtVal(r.after, r.asCurrency) }),
981
- /* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono font-medium ${showDelta ? delta > 0 ? "text-status-success-text" : "text-destructive" : "text-muted-foreground"}`, children: showDelta ? `${delta > 0 ? "+" : ""}${fmtVal(delta, r.asCurrency)}` : "\u2014" })
982
- ] }, r.label);
983
- }) })
984
- ] }) }),
985
- isCancel && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive italic", children: "Cancellation \u2014 item will be terminated on activation." })
986
- ] });
987
- }
988
- function ActionBadge({ action }) {
989
- const variantByAction = {
990
- add: "info",
991
- modify: "warning",
992
- cancel: "error"
993
- };
994
- return /* @__PURE__ */ jsx(Tag, { variant: variantByAction[action], className: "px-1.5 text-xs", children: action });
995
- }
996
1008
  function LineSummaryFallback({ line, currency }) {
997
1009
  const hasTotals = line.nrcTotal > 0 || line.mrcTotal > 0;
998
1010
  const mirroredFromItemId = line.configuration?._arcMirroredFromItemId ?? null;
@@ -1013,105 +1025,16 @@ function LineSummaryFallback({ line, currency }) {
1013
1025
  ] })
1014
1026
  ] });
1015
1027
  }
1016
- function ChargeBreakdown({ line, lineIdx, currency, pricingDetail, onTogglePricingDetail }) {
1017
- return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
1018
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
1019
- /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Charge" }),
1020
- /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Type" }),
1021
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Unit Price" }),
1022
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Qty" }),
1023
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Total" }),
1024
- /* @__PURE__ */ jsx("th", { className: "w-8" })
1025
- ] }) }),
1026
- /* @__PURE__ */ jsx("tbody", { children: line.charges.map((c, i) => {
1027
- const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length);
1028
- const isDetailOpen = pricingDetail?.lineIdx === lineIdx && pricingDetail?.chargeIdx === i;
1029
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1030
- /* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/50", children: [
1031
- /* @__PURE__ */ jsx("td", { className: "py-1", children: c.chargeName }),
1032
- /* @__PURE__ */ jsx("td", { className: "py-1 uppercase", children: c.chargeType }),
1033
- /* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: fmt(c.unitPrice, currency) }),
1034
- /* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.quantity ?? "\u2014" }),
1035
- /* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.totalPrice != null ? fmt(c.totalPrice, currency) : c.note ?? "\u2014" }),
1036
- /* @__PURE__ */ jsx("td", { className: "py-1 text-center", children: hasDetail && /* @__PURE__ */ jsx(
1037
- "button",
1038
- {
1039
- onClick: () => onTogglePricingDetail(lineIdx, i),
1040
- className: "inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
1041
- title: "Show pricing breakdown",
1042
- children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", className: "w-3.5 h-3.5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" }) })
1043
- }
1044
- ) })
1045
- ] }),
1046
- isDetailOpen && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 6, className: "pb-3 pt-1", children: /* @__PURE__ */ jsxs("div", { className: "rounded border bg-background p-3 space-y-3 text-xs", children: [
1047
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1048
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-sm", children: [
1049
- "Pricing Details \u2014 ",
1050
- c.chargeName
1051
- ] }),
1052
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: c.pricingMethod ?? "flat" })
1053
- ] }),
1054
- c.breakdown?.tiers && c.breakdown.tiers.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1055
- /* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: "Tier Breakdown" }),
1056
- /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
1057
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
1058
- /* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Tier" }),
1059
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Range" }),
1060
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Price/Unit" }),
1061
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Qty" }),
1062
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Subtotal" })
1063
- ] }) }),
1064
- /* @__PURE__ */ jsxs("tbody", { children: [
1065
- c.breakdown.tiers.map((t) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
1066
- /* @__PURE__ */ jsx("td", { className: "py-0.5", children: t.tier }),
1067
- /* @__PURE__ */ jsxs("td", { className: "py-0.5 text-right font-mono", children: [
1068
- t.from,
1069
- "\u2013",
1070
- t.to
1071
- ] }),
1072
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(t.pricePerUnit, currency) }),
1073
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: t.quantity }),
1074
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(t.subtotal, currency) })
1075
- ] }, t.tier)),
1076
- /* @__PURE__ */ jsxs("tr", { className: "border-t font-medium", children: [
1077
- /* @__PURE__ */ jsx("td", { colSpan: 4, className: "py-0.5 text-right", children: "Tiered Total" }),
1078
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(c.breakdown.tiers.reduce((s, t) => s + t.subtotal, 0), currency) })
1079
- ] })
1080
- ] })
1081
- ] })
1082
- ] }),
1083
- c.adjustments && c.adjustments.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1084
- /* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: "Applied Price Rules" }),
1085
- /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
1086
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
1087
- /* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Rule" }),
1088
- /* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Type" }),
1089
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Value" }),
1090
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Before" }),
1091
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "After" }),
1092
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Delta" })
1093
- ] }) }),
1094
- /* @__PURE__ */ jsx("tbody", { children: c.adjustments.map((adj, ai) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
1095
- /* @__PURE__ */ jsx("td", { className: "py-0.5", children: adj.ruleName }),
1096
- /* @__PURE__ */ jsx("td", { className: "py-0.5", children: RULE_TYPE_LABELS[adj.ruleType] ?? adj.ruleType }),
1097
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmtRuleValue(adj, currency) }),
1098
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(adj.unitPriceBefore, currency) }),
1099
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(adj.unitPriceAfter, currency) }),
1100
- /* @__PURE__ */ jsxs("td", { className: `py-0.5 text-right font-mono ${adj.delta < 0 ? "text-destructive" : adj.delta > 0 ? "text-status-success-text" : ""}`, children: [
1101
- adj.delta > 0 ? "+" : "",
1102
- fmt(adj.delta, currency)
1103
- ] })
1104
- ] }, ai)) })
1105
- ] })
1106
- ] }),
1107
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-1 border-t text-sm font-semibold", children: [
1108
- /* @__PURE__ */ jsx("span", { children: "Final Total" }),
1109
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: c.totalPrice != null ? fmt(c.totalPrice, currency) : "\u2014" })
1110
- ] })
1111
- ] }) }) })
1112
- ] }, i);
1113
- }) })
1114
- ] }) });
1028
+ function ChargeBreakdown({ line, lineIdx, pricingDetail, onTogglePricingDetail, ...tableProps }) {
1029
+ return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsx(
1030
+ ChargeBreakdownTable,
1031
+ {
1032
+ charges: line.charges,
1033
+ openDetailIndex: pricingDetail?.lineIdx === lineIdx ? pricingDetail.chargeIdx : null,
1034
+ onToggleDetail: (chargeIdx) => onTogglePricingDetail(lineIdx, chargeIdx),
1035
+ ...tableProps
1036
+ }
1037
+ ) });
1115
1038
  }
1116
1039
  function Spinner() {
1117
1040
  return /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", children: [