@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.
- package/CHANGELOG.md +48 -0
- package/MIGRATION.md +74 -0
- package/dist/modules/cpq/acl.js +1 -0
- package/dist/modules/cpq/acl.js.map +2 -2
- package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js +45 -0
- package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js.map +7 -0
- package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js +23 -0
- package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js.map +7 -0
- package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js +19 -0
- package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js.map +7 -0
- package/dist/modules/cpq/api/orders/[orderId]/route.js +17 -1
- package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/route.js +47 -2
- package/dist/modules/cpq/api/orders/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js +54 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +42 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js +47 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js +30 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js +47 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +32 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +33 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/price/route.js +43 -3
- package/dist/modules/cpq/api/quotes/price/route.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +22 -8
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +89 -232
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +205 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +144 -221
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
- package/dist/modules/cpq/ce.js +8 -0
- package/dist/modules/cpq/ce.js.map +2 -2
- package/dist/modules/cpq/cli.js +34 -1
- package/dist/modules/cpq/cli.js.map +2 -2
- package/dist/modules/cpq/commands/price-adjustments.js +107 -0
- package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
- package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
- package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js +154 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
- package/dist/modules/cpq/components/PriceWaterfall.js +66 -0
- package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js +117 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
- package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
- package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
- package/dist/modules/cpq/data/entities.js +97 -0
- package/dist/modules/cpq/data/entities.js.map +2 -2
- package/dist/modules/cpq/data/price-adjustments.js +70 -0
- package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
- package/dist/modules/cpq/data/validators.js +4 -0
- package/dist/modules/cpq/data/validators.js.map +2 -2
- package/dist/modules/cpq/di.js +6 -0
- package/dist/modules/cpq/di.js.map +2 -2
- package/dist/modules/cpq/events.js +47 -0
- package/dist/modules/cpq/events.js.map +2 -2
- package/dist/modules/cpq/index.js +1 -1
- package/dist/modules/cpq/index.js.map +2 -2
- package/dist/modules/cpq/lib/auditActor.js +10 -0
- package/dist/modules/cpq/lib/auditActor.js.map +7 -0
- package/dist/modules/cpq/lib/formatMoney.js +7 -0
- package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
- package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
- package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
- package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
- package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
- package/dist/modules/cpq/lib/quotePermissions.js +13 -0
- package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
- package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
- package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
- package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
- package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
- package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
- package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
- package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
- package/dist/modules/cpq/services/cpqOrderService.js +25 -28
- package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
- package/dist/modules/cpq/services/cpqPricingService.js +7 -0
- package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
- package/dist/modules/cpq/services/cpqQuotingService.js +330 -109
- package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
- package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
- package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
- package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
- package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
- package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
- package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
- package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
- package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
- package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
- package/dist/modules/cpq/services/types.js.map +2 -2
- package/dist/modules/cpq/setup.js +6 -0
- package/dist/modules/cpq/setup.js.map +2 -2
- package/package.json +1 -1
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
- package/src/modules/cpq/acl.ts +1 -0
- package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
- package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
- package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
- package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
- package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
- package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
- package/src/modules/cpq/api/orders/[orderId]/route.ts +18 -0
- package/src/modules/cpq/api/orders/route.ts +51 -1
- package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
- package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +48 -1
- package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
- package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
- package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +40 -1
- package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
- package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
- package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
- package/src/modules/cpq/api/quotes/price/route.ts +28 -2
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +60 -269
- package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +174 -263
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
- package/src/modules/cpq/ce.ts +8 -0
- package/src/modules/cpq/cli.ts +48 -1
- package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
- package/src/modules/cpq/commands/price-adjustments.ts +90 -0
- package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
- package/src/modules/cpq/components/ChargeBreakdownTable.tsx +201 -0
- package/src/modules/cpq/components/PriceWaterfall.tsx +94 -0
- package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
- package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
- package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
- package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
- package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
- package/src/modules/cpq/data/entities.ts +83 -0
- package/src/modules/cpq/data/price-adjustments.ts +67 -0
- package/src/modules/cpq/data/validators.ts +3 -0
- package/src/modules/cpq/di.ts +6 -0
- package/src/modules/cpq/events.ts +53 -2
- package/src/modules/cpq/index.ts +1 -1
- package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
- package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
- package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
- package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
- package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
- package/src/modules/cpq/lib/auditActor.ts +13 -0
- package/src/modules/cpq/lib/formatMoney.ts +4 -0
- package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
- package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
- package/src/modules/cpq/lib/quotePermissions.ts +22 -0
- package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
- package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
- package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
- package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
- package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
- package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
- package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
- package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
- package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
- package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
- package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
- package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
- package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
- package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
- package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
- package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
- package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
- package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
- package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
- package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
- package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
- package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
- package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
- package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
- package/src/modules/cpq/services/cpqOrderService.ts +22 -28
- package/src/modules/cpq/services/cpqPricingService.ts +8 -0
- package/src/modules/cpq/services/cpqQuotingService.ts +374 -125
- package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
- package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
- package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
- package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
- package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
- package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
- package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
- package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
- package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
- package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
- package/src/modules/cpq/services/types.ts +34 -1
- package/src/modules/cpq/setup.ts +7 -1
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
type SubscriptionStatus,
|
|
12
12
|
} from '../../../../../components/statusMaps'
|
|
13
13
|
import { StatusPath } from '../../../../../components/StatusPath'
|
|
14
|
+
import { DepthBadge, PriceWaterfall } from '../../../../../components/PriceWaterfall'
|
|
15
|
+
import { parseStoredCharge } from '../../../../../lib/priceAdjustmentView'
|
|
14
16
|
|
|
15
17
|
// XD-250 — ARC integration on subscription detail.
|
|
16
18
|
// • header buttons (Amend / Renew / Cancel) call /from-subscription
|
|
@@ -682,21 +684,41 @@ export default function SubscriptionDetailPage(props: { params?: { id?: string }
|
|
|
682
684
|
<tr className="text-muted-foreground">
|
|
683
685
|
<th className="text-left py-1 font-medium">Charge</th>
|
|
684
686
|
<th className="text-left py-1 font-medium">Type</th>
|
|
685
|
-
<th className="text-right py-1 font-medium">
|
|
687
|
+
<th className="text-right py-1 font-medium">List</th>
|
|
688
|
+
<th className="text-right py-1 font-medium">Offer</th>
|
|
686
689
|
<th className="text-right py-1 font-medium">Qty</th>
|
|
687
690
|
<th className="text-right py-1 font-medium">Total</th>
|
|
688
691
|
</tr>
|
|
689
692
|
</thead>
|
|
690
693
|
<tbody>
|
|
691
|
-
{item.charges.map((
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
<
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
694
|
+
{item.charges.map((raw, i) => {
|
|
695
|
+
const c = parseStoredCharge(raw)
|
|
696
|
+
if (!c) return null
|
|
697
|
+
return (
|
|
698
|
+
<React.Fragment key={i}>
|
|
699
|
+
<tr className="border-t border-dashed border-border/50">
|
|
700
|
+
<td className="py-1">{c.chargeName}</td>
|
|
701
|
+
<td className="py-1 uppercase">{c.chargeType}</td>
|
|
702
|
+
<td className="py-1 text-right font-mono text-muted-foreground">{fmt(c.listUnitPrice ?? c.unitPrice, currency)}</td>
|
|
703
|
+
<td className="py-1 text-right font-mono">
|
|
704
|
+
<span className="inline-flex items-center gap-1">
|
|
705
|
+
<DepthBadge before={c.listUnitPrice ?? c.unitPrice} after={c.unitPrice} />
|
|
706
|
+
{fmt(c.unitPrice, currency)}
|
|
707
|
+
</span>
|
|
708
|
+
</td>
|
|
709
|
+
<td className="py-1 text-right font-mono">{c.quantity ?? '—'}</td>
|
|
710
|
+
<td className="py-1 text-right font-mono">{c.totalPrice != null ? fmt(c.totalPrice, currency) : '—'}</td>
|
|
711
|
+
</tr>
|
|
712
|
+
{(c.adjustments?.length ?? 0) > 0 && (
|
|
713
|
+
<tr>
|
|
714
|
+
<td colSpan={6} className="pb-2 pl-4">
|
|
715
|
+
<PriceWaterfall charge={c} currency={currency} />
|
|
716
|
+
</td>
|
|
717
|
+
</tr>
|
|
718
|
+
)}
|
|
719
|
+
</React.Fragment>
|
|
720
|
+
)
|
|
721
|
+
})}
|
|
700
722
|
</tbody>
|
|
701
723
|
</table>
|
|
702
724
|
</div>
|
|
@@ -12,6 +12,12 @@ import {
|
|
|
12
12
|
type OrderCpqStatus,
|
|
13
13
|
} from '../../../../components/statusMaps'
|
|
14
14
|
import { StatusPath } from '../../../../components/StatusPath'
|
|
15
|
+
import { ChargeBreakdownTable, type ChargeRowView } from '../../../../components/ChargeBreakdownTable'
|
|
16
|
+
import { PricingSummaryCard, type PricingSummaryView } from '../../../../components/PricingSummaryCard'
|
|
17
|
+
import { ArcLineDiff, LineActionBadge } from '../../../../components/ArcLineDiff'
|
|
18
|
+
import { arcRecurringDelta } from '../../../../lib/quoteSummaryView'
|
|
19
|
+
import { isArcCancelledLine } from '../../../../services/arcLineRemoval'
|
|
20
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
21
|
|
|
16
22
|
// ─── Types ───────────────────────────────────────────────────────
|
|
17
23
|
|
|
@@ -24,7 +30,7 @@ type ArcLineSource = {
|
|
|
24
30
|
}
|
|
25
31
|
|
|
26
32
|
type TierBreakdownEntry = { tier: number; from: number; to: number; pricePerUnit: number; quantity: number; subtotal: number }
|
|
27
|
-
type RuleAdjustment = { ruleCode: string; ruleName: string; ruleType: string; value: number; unitPriceBefore: number; unitPriceAfter: number; delta: number }
|
|
33
|
+
type RuleAdjustment = { ruleCode: string; ruleName: string; ruleType: string; value: number; unitPriceBefore: number; unitPriceAfter: number; delta: number; origin?: 'rule' | 'manual'; adjustmentId?: string; reasonCode?: string }
|
|
28
34
|
|
|
29
35
|
type ResolvedCharge = {
|
|
30
36
|
chargeName?: string
|
|
@@ -36,6 +42,15 @@ type ResolvedCharge = {
|
|
|
36
42
|
note?: string | null
|
|
37
43
|
breakdown?: { tiers: TierBreakdownEntry[] } | null
|
|
38
44
|
adjustments?: RuleAdjustment[] | null
|
|
45
|
+
listUnitPrice?: number
|
|
46
|
+
listTotalPrice?: number | null
|
|
47
|
+
standardUnitPrice?: number
|
|
48
|
+
standardTotalPrice?: number | null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Order lines carry the quote's stored waterfall verbatim (no recalculation at conversion). */
|
|
52
|
+
function toChargeRow(c: ResolvedCharge): ChargeRowView {
|
|
53
|
+
return { ...c, chargeName: c.chargeName ?? '—', chargeType: (c.chargeType ?? 'mrc') as 'nrc' | 'mrc' | 'usage', unitPrice: c.unitPrice ?? 0, totalPrice: c.totalPrice ?? null }
|
|
39
54
|
}
|
|
40
55
|
|
|
41
56
|
type OrderLineResult = {
|
|
@@ -68,11 +83,7 @@ type OrderResult = {
|
|
|
68
83
|
sourceQuoteId: string | null
|
|
69
84
|
quoteType: string
|
|
70
85
|
currencyCode: string
|
|
71
|
-
pricingSummary: {
|
|
72
|
-
nrcTotal: number
|
|
73
|
-
mrcTotal: number
|
|
74
|
-
currencyCode: string
|
|
75
|
-
}
|
|
86
|
+
pricingSummary: PricingSummaryView & { currencyCode: string }
|
|
76
87
|
activatedAt: string | null
|
|
77
88
|
createdAt: string
|
|
78
89
|
lines: OrderLineResult[]
|
|
@@ -84,19 +95,6 @@ const ARC_BADGE_VARIANTS: Record<string, 'brand' | 'success' | 'error'> = {
|
|
|
84
95
|
cancel: 'error',
|
|
85
96
|
}
|
|
86
97
|
|
|
87
|
-
const RULE_TYPE_LABELS: Record<string, string> = {
|
|
88
|
-
discount_percent: '% discount',
|
|
89
|
-
discount_absolute: 'fixed discount',
|
|
90
|
-
surcharge_percent: '% surcharge',
|
|
91
|
-
surcharge_absolute: 'fixed surcharge',
|
|
92
|
-
price_override: 'override',
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function fmtRuleValue(adj: RuleAdjustment, currency?: string): string {
|
|
96
|
-
if (adj.ruleType === 'discount_percent' || adj.ruleType === 'surcharge_percent') return `${adj.value}%`
|
|
97
|
-
if (currency) return fmt(adj.value, currency)
|
|
98
|
-
return String(adj.value)
|
|
99
|
-
}
|
|
100
98
|
|
|
101
99
|
// ─── Constants ───────────────────────────────────────────────────
|
|
102
100
|
|
|
@@ -422,24 +420,18 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
422
420
|
</MetaCard>
|
|
423
421
|
</div>
|
|
424
422
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
</div>
|
|
433
|
-
<div>
|
|
434
|
-
<p className="text-xs text-muted-foreground">Monthly Recurring (MRC)</p>
|
|
435
|
-
<p className="text-lg font-semibold font-mono">{fmt(order.pricingSummary.mrcTotal, currency)}</p>
|
|
436
|
-
</div>
|
|
437
|
-
</div>
|
|
438
|
-
</div>
|
|
423
|
+
<PricingSummaryCard
|
|
424
|
+
summary={order.pricingSummary}
|
|
425
|
+
currency={currency}
|
|
426
|
+
arcDelta={order.quoteType === 'amend' || order.quoteType === 'renew'
|
|
427
|
+
? arcRecurringDelta(order.lines.map((l) => ({ ...l, action: l.action as 'add' | 'modify' | 'cancel' })))
|
|
428
|
+
: null}
|
|
429
|
+
/>
|
|
439
430
|
|
|
440
431
|
{/* Order Lines */}
|
|
441
432
|
<OrderLineTree
|
|
442
433
|
lines={order.lines}
|
|
434
|
+
quoteType={order.quoteType}
|
|
443
435
|
currency={currency}
|
|
444
436
|
expandedLines={expandedLines}
|
|
445
437
|
pricingDetail={pricingDetail}
|
|
@@ -465,6 +457,7 @@ function MetaCard({ label, children }: { label: string; children: React.ReactNod
|
|
|
465
457
|
|
|
466
458
|
function OrderLineTree({
|
|
467
459
|
lines,
|
|
460
|
+
quoteType,
|
|
468
461
|
currency,
|
|
469
462
|
expandedLines,
|
|
470
463
|
pricingDetail,
|
|
@@ -472,6 +465,7 @@ function OrderLineTree({
|
|
|
472
465
|
onTogglePricingDetail,
|
|
473
466
|
}: {
|
|
474
467
|
lines: OrderLineResult[]
|
|
468
|
+
quoteType: string
|
|
475
469
|
currency: string
|
|
476
470
|
expandedLines: Set<string>
|
|
477
471
|
pricingDetail: { lineIdx: number; chargeIdx: number } | null
|
|
@@ -512,6 +506,7 @@ function OrderLineTree({
|
|
|
512
506
|
<OrderLineRow
|
|
513
507
|
line={line}
|
|
514
508
|
lineIdx={lineIdx}
|
|
509
|
+
quoteType={quoteType}
|
|
515
510
|
currency={currency}
|
|
516
511
|
isExpanded={isExpanded}
|
|
517
512
|
isBundle={isBundle}
|
|
@@ -533,6 +528,7 @@ function OrderLineTree({
|
|
|
533
528
|
key={child.lineId}
|
|
534
529
|
line={child}
|
|
535
530
|
lineIdx={childIdx}
|
|
531
|
+
quoteType={quoteType}
|
|
536
532
|
currency={currency}
|
|
537
533
|
isExpanded={expandedLines.has(child.lineId)}
|
|
538
534
|
isBundle={false}
|
|
@@ -555,20 +551,26 @@ function OrderLineTree({
|
|
|
555
551
|
)
|
|
556
552
|
}
|
|
557
553
|
|
|
558
|
-
function OrderLineRow({ line, lineIdx, currency, isExpanded, isBundle, childCount, indent, pricingDetail, onToggle, onTogglePricingDetail }: {
|
|
559
|
-
line: OrderLineResult; lineIdx: number; currency: string; isExpanded: boolean; isBundle: boolean; childCount: number; indent: number
|
|
554
|
+
function OrderLineRow({ line, lineIdx, quoteType, currency, isExpanded, isBundle, childCount, indent, pricingDetail, onToggle, onTogglePricingDetail }: {
|
|
555
|
+
line: OrderLineResult; lineIdx: number; quoteType: string; currency: string; isExpanded: boolean; isBundle: boolean; childCount: number; indent: number
|
|
560
556
|
pricingDetail: { lineIdx: number; chargeIdx: number } | null
|
|
561
557
|
onToggle: () => void
|
|
562
558
|
onTogglePricingDetail: (lineIdx: number, chargeIdx: number) => void
|
|
563
559
|
}) {
|
|
560
|
+
const t = useT()
|
|
564
561
|
const paddingLeft = indent > 0 ? `${indent * 2 + 1}rem` : undefined
|
|
562
|
+
// Same presentation as the quote page: an existing item removed on an
|
|
563
|
+
// Amend / Renew quote reads as "remove", struck through.
|
|
564
|
+
const isRemoved = isArcCancelledLine(line, quoteType)
|
|
565
565
|
|
|
566
566
|
return (
|
|
567
567
|
<div>
|
|
568
568
|
<div
|
|
569
|
-
className=
|
|
569
|
+
className={`flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors cursor-pointer ${isRemoved ? 'bg-muted/20' : ''}`}
|
|
570
570
|
style={paddingLeft ? { paddingLeft } : undefined}
|
|
571
571
|
onClick={onToggle}
|
|
572
|
+
data-testid="cpq-order-line"
|
|
573
|
+
data-removed={isRemoved ? 'true' : undefined}
|
|
572
574
|
>
|
|
573
575
|
<button className="flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground">
|
|
574
576
|
<svg className={`h-4 w-4 transition-transform ${isExpanded ? 'rotate-90' : ''}`} fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor">
|
|
@@ -582,21 +584,31 @@ function OrderLineRow({ line, lineIdx, currency, isExpanded, isBundle, childCoun
|
|
|
582
584
|
{isBundle && (
|
|
583
585
|
<Tag variant="brand" className="px-1.5 text-xs">bundle</Tag>
|
|
584
586
|
)}
|
|
585
|
-
<span className=
|
|
587
|
+
<span className={`font-medium text-sm ${isRemoved ? 'text-muted-foreground line-through' : ''}`}>{line.offeringName}</span>
|
|
586
588
|
{line.quantity > 1 && <span className="rounded bg-muted px-1.5 py-0.5 text-xs font-medium">×{line.quantity}</span>}
|
|
587
|
-
<
|
|
589
|
+
<LineActionBadge
|
|
590
|
+
action={line.action}
|
|
591
|
+
label={isRemoved ? t('cpq.quotes.arc.lineRemoved', 'remove') : undefined}
|
|
592
|
+
title={isRemoved ? t('cpq.quotes.arc.lineRemovedHint', 'This item is cancelled on the subscription when the order is activated') : undefined}
|
|
593
|
+
/>
|
|
588
594
|
{isBundle && <span className="text-xs text-muted-foreground">{childCount} component{childCount !== 1 ? 's' : ''}</span>}
|
|
589
595
|
</div>
|
|
590
596
|
</div>
|
|
591
597
|
|
|
592
598
|
<div className="shrink-0 text-right text-xs space-y-0.5">
|
|
599
|
+
{isRemoved && line.arcSource && line.arcSource.mrcAmount > 0 && (
|
|
600
|
+
<div>
|
|
601
|
+
<span className="text-muted-foreground">MRC </span>
|
|
602
|
+
<span className="font-mono text-muted-foreground line-through">{fmt(line.arcSource.mrcAmount, currency)}</span>
|
|
603
|
+
</div>
|
|
604
|
+
)}
|
|
593
605
|
{line.nrcTotal > 0 && <div><span className="text-muted-foreground">NRC </span><span className="font-mono font-medium">{fmt(line.nrcTotal, currency)}</span></div>}
|
|
594
606
|
{line.mrcTotal > 0 && <div><span className="text-muted-foreground">MRC </span><span className="font-mono font-medium">{fmt(line.mrcTotal, currency)}</span></div>}
|
|
595
607
|
</div>
|
|
596
608
|
</div>
|
|
597
609
|
|
|
598
610
|
{isExpanded && (() => {
|
|
599
|
-
const internalKeys = new Set(['_arcMirroredName', '_arcMirroredFromItemId', 'offeringName'])
|
|
611
|
+
const internalKeys = new Set(['_arcMirroredName', '_arcMirroredFromItemId', '_arcPriceFrozen', '_arcRepriced', 'offeringName'])
|
|
600
612
|
const visibleConfigEntries = Object.entries(line.configuration).filter(
|
|
601
613
|
([key]) => !internalKeys.has(key),
|
|
602
614
|
)
|
|
@@ -612,9 +624,6 @@ function OrderLineRow({ line, lineIdx, currency, isExpanded, isBundle, childCoun
|
|
|
612
624
|
className="border-t bg-muted/10 px-12 py-3 space-y-3"
|
|
613
625
|
style={paddingLeft ? { paddingLeft: `calc(${paddingLeft} + 2rem)` } : undefined}
|
|
614
626
|
>
|
|
615
|
-
{hasArcDiff && (
|
|
616
|
-
<ArcLineDiff line={line} currency={currency} />
|
|
617
|
-
)}
|
|
618
627
|
{isEmpty ? (
|
|
619
628
|
<div className="text-xs text-muted-foreground space-y-1">
|
|
620
629
|
<div>
|
|
@@ -649,16 +658,12 @@ function OrderLineRow({ line, lineIdx, currency, isExpanded, isBundle, childCoun
|
|
|
649
658
|
)}
|
|
650
659
|
|
|
651
660
|
{hasCharges && (
|
|
652
|
-
<
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
pricingDetail={pricingDetail}
|
|
659
|
-
onTogglePricingDetail={onTogglePricingDetail}
|
|
660
|
-
/>
|
|
661
|
-
</div>
|
|
661
|
+
<ChargeBreakdownTable
|
|
662
|
+
charges={line.charges.map(toChargeRow)}
|
|
663
|
+
currency={currency}
|
|
664
|
+
openDetailIndex={pricingDetail?.lineIdx === lineIdx ? pricingDetail.chargeIdx : null}
|
|
665
|
+
onToggleDetail={(chargeIdx) => onTogglePricingDetail(lineIdx, chargeIdx)}
|
|
666
|
+
/>
|
|
662
667
|
)}
|
|
663
668
|
|
|
664
669
|
{hasTerm && (
|
|
@@ -668,6 +673,9 @@ function OrderLineRow({ line, lineIdx, currency, isExpanded, isBundle, childCoun
|
|
|
668
673
|
{line.endDate && <span>End: {new Date(line.endDate).toLocaleDateString()}</span>}
|
|
669
674
|
</div>
|
|
670
675
|
)}
|
|
676
|
+
|
|
677
|
+
{/* XD-250 ARC: before/after preview below the charges, as on the quote. */}
|
|
678
|
+
{hasArcDiff && <ArcLineDiff line={line} currency={currency} />}
|
|
671
679
|
</>
|
|
672
680
|
)}
|
|
673
681
|
</div>
|
|
@@ -677,144 +685,6 @@ function OrderLineRow({ line, lineIdx, currency, isExpanded, isBundle, childCoun
|
|
|
677
685
|
)
|
|
678
686
|
}
|
|
679
687
|
|
|
680
|
-
// Charge breakdown table with the waterfall ("show pricing details")
|
|
681
|
-
// toggle. Same shape as the quote page so a charge looks identical on
|
|
682
|
-
// both surfaces, including tier-by-tier subtotals and any price-rule
|
|
683
|
-
// adjustments that fired during calculation.
|
|
684
|
-
function ChargeBreakdown({ line, lineIdx, currency, pricingDetail, onTogglePricingDetail }: {
|
|
685
|
-
line: OrderLineResult; lineIdx: number; currency: string
|
|
686
|
-
pricingDetail: { lineIdx: number; chargeIdx: number } | null
|
|
687
|
-
onTogglePricingDetail: (lineIdx: number, chargeIdx: number) => void
|
|
688
|
-
}) {
|
|
689
|
-
return (
|
|
690
|
-
<table className="w-full text-xs">
|
|
691
|
-
<thead>
|
|
692
|
-
<tr className="text-muted-foreground">
|
|
693
|
-
<th className="text-left py-1 font-medium">Charge</th>
|
|
694
|
-
<th className="text-left py-1 font-medium">Type</th>
|
|
695
|
-
<th className="text-right py-1 font-medium">Unit Price</th>
|
|
696
|
-
<th className="text-right py-1 font-medium">Qty</th>
|
|
697
|
-
<th className="text-right py-1 font-medium">Total</th>
|
|
698
|
-
<th className="w-8" />
|
|
699
|
-
</tr>
|
|
700
|
-
</thead>
|
|
701
|
-
<tbody>
|
|
702
|
-
{line.charges.map((c, i) => {
|
|
703
|
-
const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length)
|
|
704
|
-
const isDetailOpen = pricingDetail?.lineIdx === lineIdx && pricingDetail?.chargeIdx === i
|
|
705
|
-
return (
|
|
706
|
-
<React.Fragment key={i}>
|
|
707
|
-
<tr className="border-t border-dashed border-border/50">
|
|
708
|
-
<td className="py-1">{c.chargeName ?? '—'}</td>
|
|
709
|
-
<td className="py-1 uppercase">{c.chargeType ?? '—'}</td>
|
|
710
|
-
<td className="py-1 text-right font-mono">{c.unitPrice != null ? fmt(c.unitPrice, currency) : '—'}</td>
|
|
711
|
-
<td className="py-1 text-right font-mono">{c.quantity ?? '—'}</td>
|
|
712
|
-
<td className="py-1 text-right font-mono">{c.totalPrice != null ? fmt(c.totalPrice, currency) : c.note ?? '—'}</td>
|
|
713
|
-
<td className="py-1 text-center">
|
|
714
|
-
{hasDetail && (
|
|
715
|
-
<button
|
|
716
|
-
onClick={() => onTogglePricingDetail(lineIdx, i)}
|
|
717
|
-
className="inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors"
|
|
718
|
-
title="Show pricing breakdown"
|
|
719
|
-
>
|
|
720
|
-
<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">
|
|
721
|
-
<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" />
|
|
722
|
-
</svg>
|
|
723
|
-
</button>
|
|
724
|
-
)}
|
|
725
|
-
</td>
|
|
726
|
-
</tr>
|
|
727
|
-
{isDetailOpen && (
|
|
728
|
-
<tr>
|
|
729
|
-
<td colSpan={6} className="pb-3 pt-1">
|
|
730
|
-
<div className="rounded border bg-background p-3 space-y-3 text-xs">
|
|
731
|
-
<div className="flex items-center justify-between">
|
|
732
|
-
<span className="font-semibold text-sm">Pricing Details — {c.chargeName}</span>
|
|
733
|
-
<span className="text-muted-foreground">{c.pricingMethod ?? 'flat'}</span>
|
|
734
|
-
</div>
|
|
735
|
-
|
|
736
|
-
{c.breakdown?.tiers && c.breakdown.tiers.length > 0 && (
|
|
737
|
-
<div>
|
|
738
|
-
<div className="font-medium text-muted-foreground mb-1">Tier Breakdown</div>
|
|
739
|
-
<table className="w-full">
|
|
740
|
-
<thead>
|
|
741
|
-
<tr className="text-muted-foreground">
|
|
742
|
-
<th className="text-left py-0.5 font-medium">Tier</th>
|
|
743
|
-
<th className="text-right py-0.5 font-medium">Range</th>
|
|
744
|
-
<th className="text-right py-0.5 font-medium">Price/Unit</th>
|
|
745
|
-
<th className="text-right py-0.5 font-medium">Qty</th>
|
|
746
|
-
<th className="text-right py-0.5 font-medium">Subtotal</th>
|
|
747
|
-
</tr>
|
|
748
|
-
</thead>
|
|
749
|
-
<tbody>
|
|
750
|
-
{c.breakdown.tiers.map((t) => (
|
|
751
|
-
<tr key={t.tier} className="border-t border-border/30">
|
|
752
|
-
<td className="py-0.5">{t.tier}</td>
|
|
753
|
-
<td className="py-0.5 text-right font-mono">{t.from}–{t.to}</td>
|
|
754
|
-
<td className="py-0.5 text-right font-mono">{fmt(t.pricePerUnit, currency)}</td>
|
|
755
|
-
<td className="py-0.5 text-right font-mono">{t.quantity}</td>
|
|
756
|
-
<td className="py-0.5 text-right font-mono">{fmt(t.subtotal, currency)}</td>
|
|
757
|
-
</tr>
|
|
758
|
-
))}
|
|
759
|
-
<tr className="border-t font-medium">
|
|
760
|
-
<td colSpan={4} className="py-0.5 text-right">Tiered Total</td>
|
|
761
|
-
<td className="py-0.5 text-right font-mono">
|
|
762
|
-
{fmt(c.breakdown.tiers.reduce((s, t) => s + t.subtotal, 0), currency)}
|
|
763
|
-
</td>
|
|
764
|
-
</tr>
|
|
765
|
-
</tbody>
|
|
766
|
-
</table>
|
|
767
|
-
</div>
|
|
768
|
-
)}
|
|
769
|
-
|
|
770
|
-
{c.adjustments && c.adjustments.length > 0 && (
|
|
771
|
-
<div>
|
|
772
|
-
<div className="font-medium text-muted-foreground mb-1">Applied Price Rules</div>
|
|
773
|
-
<table className="w-full">
|
|
774
|
-
<thead>
|
|
775
|
-
<tr className="text-muted-foreground">
|
|
776
|
-
<th className="text-left py-0.5 font-medium">Rule</th>
|
|
777
|
-
<th className="text-left py-0.5 font-medium">Type</th>
|
|
778
|
-
<th className="text-right py-0.5 font-medium">Value</th>
|
|
779
|
-
<th className="text-right py-0.5 font-medium">Before</th>
|
|
780
|
-
<th className="text-right py-0.5 font-medium">After</th>
|
|
781
|
-
<th className="text-right py-0.5 font-medium">Delta</th>
|
|
782
|
-
</tr>
|
|
783
|
-
</thead>
|
|
784
|
-
<tbody>
|
|
785
|
-
{c.adjustments.map((adj, ai) => (
|
|
786
|
-
<tr key={ai} className="border-t border-border/30">
|
|
787
|
-
<td className="py-0.5">{adj.ruleName}</td>
|
|
788
|
-
<td className="py-0.5">{RULE_TYPE_LABELS[adj.ruleType] ?? adj.ruleType}</td>
|
|
789
|
-
<td className="py-0.5 text-right font-mono">{fmtRuleValue(adj, currency)}</td>
|
|
790
|
-
<td className="py-0.5 text-right font-mono">{fmt(adj.unitPriceBefore, currency)}</td>
|
|
791
|
-
<td className="py-0.5 text-right font-mono">{fmt(adj.unitPriceAfter, currency)}</td>
|
|
792
|
-
<td className={`py-0.5 text-right font-mono ${adj.delta < 0 ? 'text-destructive' : adj.delta > 0 ? 'text-status-success-text' : ''}`}>
|
|
793
|
-
{adj.delta > 0 ? '+' : ''}{fmt(adj.delta, currency)}
|
|
794
|
-
</td>
|
|
795
|
-
</tr>
|
|
796
|
-
))}
|
|
797
|
-
</tbody>
|
|
798
|
-
</table>
|
|
799
|
-
</div>
|
|
800
|
-
)}
|
|
801
|
-
|
|
802
|
-
<div className="flex items-center justify-between pt-1 border-t text-sm font-semibold">
|
|
803
|
-
<span>Final Total</span>
|
|
804
|
-
<span className="font-mono">{c.totalPrice != null ? fmt(c.totalPrice, currency) : '—'}</span>
|
|
805
|
-
</div>
|
|
806
|
-
</div>
|
|
807
|
-
</td>
|
|
808
|
-
</tr>
|
|
809
|
-
)}
|
|
810
|
-
</React.Fragment>
|
|
811
|
-
)
|
|
812
|
-
})}
|
|
813
|
-
</tbody>
|
|
814
|
-
</table>
|
|
815
|
-
)
|
|
816
|
-
}
|
|
817
|
-
|
|
818
688
|
function Spinner() {
|
|
819
689
|
return (
|
|
820
690
|
<svg className="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
|
@@ -840,82 +710,3 @@ function ErrorBanner({ message, onDismiss }: { message: string; onDismiss?: () =
|
|
|
840
710
|
</Alert>
|
|
841
711
|
)
|
|
842
712
|
}
|
|
843
|
-
|
|
844
|
-
// XD-250 ARC: side-by-side "Before / After" snapshot for a quote / order line
|
|
845
|
-
// that mirrors an existing subscription item. "Before" comes from the live
|
|
846
|
-
// CpqInventorySubscriptionItem at render time; "After" is what the line will
|
|
847
|
-
// apply on activation. For 'cancel' lines the after column is greyed out.
|
|
848
|
-
function ArcLineDiff({ line, currency }: { line: OrderLineResult; currency: string }) {
|
|
849
|
-
const src = line.arcSource
|
|
850
|
-
if (!src) return null
|
|
851
|
-
const isCancel = line.action === 'cancel'
|
|
852
|
-
|
|
853
|
-
const beforeMrc = src.mrcAmount
|
|
854
|
-
const beforeNrc = src.nrcAmount
|
|
855
|
-
const beforeQty = src.quantity
|
|
856
|
-
const afterMrc = isCancel ? 0 : line.mrcTotal
|
|
857
|
-
const afterNrc = isCancel ? 0 : line.nrcTotal
|
|
858
|
-
const afterQty = isCancel ? 0 : line.quantity
|
|
859
|
-
const mrcDelta = afterMrc - beforeMrc
|
|
860
|
-
const nrcDelta = afterNrc - beforeNrc
|
|
861
|
-
const qtyDelta = afterQty - beforeQty
|
|
862
|
-
|
|
863
|
-
return (
|
|
864
|
-
<div>
|
|
865
|
-
<p className="text-xs font-medium text-muted-foreground mb-1.5">Change Preview</p>
|
|
866
|
-
<div className="rounded border bg-background overflow-hidden">
|
|
867
|
-
<table className="w-full text-xs">
|
|
868
|
-
<thead className="bg-muted/40">
|
|
869
|
-
<tr className="text-muted-foreground">
|
|
870
|
-
<th className="text-left px-3 py-1.5 font-medium" />
|
|
871
|
-
<th className="text-right px-3 py-1.5 font-medium">Before</th>
|
|
872
|
-
<th className="text-right px-3 py-1.5 font-medium">After</th>
|
|
873
|
-
<th className="text-right px-3 py-1.5 font-medium">Δ</th>
|
|
874
|
-
</tr>
|
|
875
|
-
</thead>
|
|
876
|
-
<tbody>
|
|
877
|
-
<ArcDiffRow label="Quantity" before={beforeQty} after={afterQty} delta={qtyDelta} currency={null} dimAfter={isCancel} />
|
|
878
|
-
<ArcDiffRow label="MRC" before={beforeMrc} after={afterMrc} delta={mrcDelta} currency={currency} dimAfter={isCancel} />
|
|
879
|
-
<ArcDiffRow label="NRC" before={beforeNrc} after={afterNrc} delta={nrcDelta} currency={currency} dimAfter={isCancel} />
|
|
880
|
-
</tbody>
|
|
881
|
-
</table>
|
|
882
|
-
</div>
|
|
883
|
-
{isCancel && (
|
|
884
|
-
<p className="text-xs text-destructive mt-1 italic">
|
|
885
|
-
Cancellation — item will be terminated on activation.
|
|
886
|
-
</p>
|
|
887
|
-
)}
|
|
888
|
-
</div>
|
|
889
|
-
)
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
function ArcDiffRow({
|
|
893
|
-
label,
|
|
894
|
-
before,
|
|
895
|
-
after,
|
|
896
|
-
delta,
|
|
897
|
-
currency,
|
|
898
|
-
dimAfter,
|
|
899
|
-
}: {
|
|
900
|
-
label: string
|
|
901
|
-
before: number
|
|
902
|
-
after: number
|
|
903
|
-
delta: number
|
|
904
|
-
currency: string | null
|
|
905
|
-
dimAfter: boolean
|
|
906
|
-
}) {
|
|
907
|
-
const fmtVal = (v: number) => (currency ? fmt(v, currency) : String(v))
|
|
908
|
-
const showDelta = delta !== 0
|
|
909
|
-
return (
|
|
910
|
-
<tr className="border-t border-dashed border-border/40">
|
|
911
|
-
<td className="px-3 py-1 text-muted-foreground">{label}</td>
|
|
912
|
-
<td className="px-3 py-1 text-right font-mono">{fmtVal(before)}</td>
|
|
913
|
-
<td className={`px-3 py-1 text-right font-mono ${dimAfter ? 'text-muted-foreground line-through' : ''}`}>
|
|
914
|
-
{fmtVal(after)}
|
|
915
|
-
</td>
|
|
916
|
-
<td className={`px-3 py-1 text-right font-mono font-medium ${showDelta ? (delta > 0 ? 'text-status-success-text' : 'text-destructive') : 'text-muted-foreground'}`}>
|
|
917
|
-
{showDelta ? `${delta > 0 ? '+' : ''}${fmtVal(delta)}` : '—'}
|
|
918
|
-
</td>
|
|
919
|
-
</tr>
|
|
920
|
-
)
|
|
921
|
-
}
|
|
@@ -10,7 +10,7 @@ export const metadata = {
|
|
|
10
10
|
pageTitleKey: 'cpq.orders.title',
|
|
11
11
|
pageGroup: 'CPQ',
|
|
12
12
|
pageGroupKey: 'customers~sales~cpq.nav.group',
|
|
13
|
-
pageOrder:
|
|
13
|
+
pageOrder: 2,
|
|
14
14
|
icon: ordersIcon,
|
|
15
15
|
breadcrumb: [{ label: 'CPQ Orders', labelKey: 'cpq.orders.title' }],
|
|
16
16
|
} as const
|