@dainamite/cpq 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +64 -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/AssetsTable.js +3 -9
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +3 -3
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
- package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +174 -184
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
- package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +140 -280
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
- package/dist/modules/cpq/backend/cpq/orders/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/price-rules/PriceRuleForm.js +41 -36
- package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
- package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
- package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +208 -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 +269 -311
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
- package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
- package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
- 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/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
- package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
- package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
- 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 +148 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
- package/dist/modules/cpq/components/PriceWaterfall.js +68 -0
- package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js +112 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
- package/dist/modules/cpq/components/StatusPath.js +5 -3
- package/dist/modules/cpq/components/StatusPath.js.map +2 -2
- package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
- package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
- 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/i18n/de.json +1220 -0
- package/dist/modules/cpq/i18n/en.json +1220 -0
- package/dist/modules/cpq/i18n/es.json +1220 -0
- package/dist/modules/cpq/i18n/pl.json +1220 -0
- 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/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection-table.js +2 -2
- package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
- package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
- package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
- package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
- package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
- package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
- package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/index.js +12 -0
- package/dist/modules/cpq/workflows/steps/index.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/__tests__/i18n/dictionaries.test.ts +62 -0
- package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
- package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -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/AssetsTable.tsx +3 -9
- package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +3 -3
- package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +164 -120
- package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
- package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +122 -308
- package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
- package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
- package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
- package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
- package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
- package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
- package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
- package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
- 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 +304 -348
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
- package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
- package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
- package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
- package/src/modules/cpq/backend/cpq/wizards/page.tsx +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 +203 -0
- package/src/modules/cpq/components/PriceWaterfall.tsx +96 -0
- package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
- package/src/modules/cpq/components/StatusPath.tsx +7 -4
- package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
- package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
- 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/i18n/de.json +1220 -0
- package/src/modules/cpq/i18n/en.json +1220 -0
- package/src/modules/cpq/i18n/es.json +1220 -0
- package/src/modules/cpq/i18n/pl.json +1220 -0
- 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
- package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
- package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
- package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
- package/src/modules/cpq/widgets/injection-table.ts +8 -2
- package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
- package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
- package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
- package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
- package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
- package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
- package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
- package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
- package/src/modules/cpq/workflows/steps/index.ts +12 -0
- package/src/modules/cpq/workflows/types.ts +4 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { Info } from "lucide-react";
|
|
6
|
+
import { SimpleTooltip } from "@open-mercato/ui/primitives/tooltip";
|
|
7
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
8
|
+
import { DepthBadge, PriceWaterfall } from "./PriceWaterfall.js";
|
|
9
|
+
import { ADJUSTMENT_ERROR_FALLBACKS } from "../lib/priceAdjustmentView.js";
|
|
10
|
+
import { formatMoney } from "../lib/formatMoney.js";
|
|
11
|
+
function PriceColumnHeader({ label, hint }) {
|
|
12
|
+
return /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: /* @__PURE__ */ jsx(SimpleTooltip, { content: hint, size: "sm", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 cursor-help", tabIndex: 0, "aria-label": `${label}: ${hint}`, children: [
|
|
13
|
+
label,
|
|
14
|
+
/* @__PURE__ */ jsx(Info, { className: "h-3 w-3", "aria-hidden": true })
|
|
15
|
+
] }) }) });
|
|
16
|
+
}
|
|
17
|
+
function ChargeBreakdownTable({
|
|
18
|
+
charges,
|
|
19
|
+
currency,
|
|
20
|
+
openDetailIndex,
|
|
21
|
+
onToggleDetail,
|
|
22
|
+
reasonLabels,
|
|
23
|
+
renderChargeActions,
|
|
24
|
+
renderManualActions
|
|
25
|
+
}) {
|
|
26
|
+
const t = useT();
|
|
27
|
+
return /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", "data-testid": "cpq-charge-breakdown", children: [
|
|
28
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
29
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: t("cpq.charges.charge", "Charge") }),
|
|
30
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: t("cpq.charges.type", "Type") }),
|
|
31
|
+
/* @__PURE__ */ jsx(
|
|
32
|
+
PriceColumnHeader,
|
|
33
|
+
{
|
|
34
|
+
label: t("cpq.priceAdjustments.listUnit", "List"),
|
|
35
|
+
hint: t("cpq.priceAdjustments.listUnitHint", "Catalog unit price from the pricing table, before any price rules or manual adjustments.")
|
|
36
|
+
}
|
|
37
|
+
),
|
|
38
|
+
/* @__PURE__ */ jsx(
|
|
39
|
+
PriceColumnHeader,
|
|
40
|
+
{
|
|
41
|
+
label: t("cpq.priceAdjustments.standardUnit", "Standard"),
|
|
42
|
+
hint: t("cpq.priceAdjustments.standardUnitHint", "Unit price after automatic price rules (e.g. volume or segment discounts), before manual adjustments.")
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
/* @__PURE__ */ jsx(
|
|
46
|
+
PriceColumnHeader,
|
|
47
|
+
{
|
|
48
|
+
label: t("cpq.priceAdjustments.offerUnit", "Offer"),
|
|
49
|
+
hint: t("cpq.priceAdjustments.offerUnitHint", "Final unit price quoted to the customer, after price rules and manual adjustments. The badge shows the total change vs list.")
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.charges.qty", "Qty") }),
|
|
53
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.charges.total", "Total") }),
|
|
54
|
+
/* @__PURE__ */ jsx("th", { className: "w-16" })
|
|
55
|
+
] }) }),
|
|
56
|
+
/* @__PURE__ */ jsx("tbody", { children: charges.map((c, i) => {
|
|
57
|
+
const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length);
|
|
58
|
+
const isDetailOpen = openDetailIndex === i;
|
|
59
|
+
const listUnit = c.listUnitPrice ?? c.unitPrice;
|
|
60
|
+
const standardUnit = c.standardUnitPrice ?? c.unitPrice;
|
|
61
|
+
const errors = c.adjustmentErrors ?? [];
|
|
62
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
63
|
+
/* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/50", children: [
|
|
64
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1", children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { children: c.chargeName }),
|
|
66
|
+
errors.length > 0 && /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "ml-1.5 px-1.5 text-xs", "data-testid": "cpq-adjustment-invalid", children: t("cpq.priceAdjustments.invalidReason", "Invalid: {reason}", {
|
|
67
|
+
reason: t(`cpq.priceAdjustments.errors.${errors[0].code}`, ADJUSTMENT_ERROR_FALLBACKS[errors[0].code] ?? errors[0].code)
|
|
68
|
+
}) })
|
|
69
|
+
] }),
|
|
70
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 uppercase", children: c.chargeType }),
|
|
71
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono text-muted-foreground", children: formatMoney(listUnit, currency) }),
|
|
72
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono text-muted-foreground", children: formatMoney(standardUnit, currency) }),
|
|
73
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
74
|
+
/* @__PURE__ */ jsx(DepthBadge, { before: listUnit, after: c.unitPrice }),
|
|
75
|
+
formatMoney(c.unitPrice, currency)
|
|
76
|
+
] }) }),
|
|
77
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.quantity ?? "\u2014" }),
|
|
78
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.totalPrice != null ? formatMoney(c.totalPrice, currency) : c.note ?? "\u2014" }),
|
|
79
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1 text-right whitespace-nowrap", children: [
|
|
80
|
+
renderChargeActions?.(c),
|
|
81
|
+
hasDetail && /* @__PURE__ */ jsx(
|
|
82
|
+
"button",
|
|
83
|
+
{
|
|
84
|
+
type: "button",
|
|
85
|
+
onClick: () => onToggleDetail(i),
|
|
86
|
+
className: "inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
|
|
87
|
+
title: t("cpq.priceAdjustments.showBreakdown", "Show price breakdown"),
|
|
88
|
+
children: /* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" })
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
] })
|
|
92
|
+
] }),
|
|
93
|
+
isDetailOpen && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 8, className: "pb-3 pt-1", children: /* @__PURE__ */ jsx(ChargePricingDetail, { charge: c, currency, reasonLabels, renderManualActions }) }) })
|
|
94
|
+
] }, i);
|
|
95
|
+
}) })
|
|
96
|
+
] });
|
|
97
|
+
}
|
|
98
|
+
function ChargePricingDetail({ charge: c, currency, reasonLabels, renderManualActions }) {
|
|
99
|
+
const t = useT();
|
|
100
|
+
const tiers = c.breakdown?.tiers ?? [];
|
|
101
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded border bg-background p-3 space-y-3 text-xs", children: [
|
|
102
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
103
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold text-sm", children: t("cpq.charges.pricingDetailsFor", "Pricing Details \u2014 {name}", { name: c.chargeName }) }),
|
|
104
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: c.pricingMethod ?? "flat" })
|
|
105
|
+
] }),
|
|
106
|
+
tiers.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
107
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: t("cpq.charges.tierBreakdown", "Tier Breakdown") }),
|
|
108
|
+
/* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
109
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
110
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: t("cpq.charges.tier", "Tier") }),
|
|
111
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.range", "Range") }),
|
|
112
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.pricePerUnit", "Price/Unit") }),
|
|
113
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.qty", "Qty") }),
|
|
114
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.subtotal", "Subtotal") })
|
|
115
|
+
] }) }),
|
|
116
|
+
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
117
|
+
tiers.map((tier) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
|
|
118
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5", children: tier.tier }),
|
|
119
|
+
/* @__PURE__ */ jsxs("td", { className: "py-0.5 text-right font-mono", children: [
|
|
120
|
+
tier.from,
|
|
121
|
+
"\u2013",
|
|
122
|
+
tier.to
|
|
123
|
+
] }),
|
|
124
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: formatMoney(tier.pricePerUnit, currency) }),
|
|
125
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: tier.quantity }),
|
|
126
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: formatMoney(tier.subtotal, currency) })
|
|
127
|
+
] }, tier.tier)),
|
|
128
|
+
/* @__PURE__ */ jsxs("tr", { className: "border-t font-medium", children: [
|
|
129
|
+
/* @__PURE__ */ jsx("td", { colSpan: 4, className: "py-0.5 text-right", children: t("cpq.charges.tieredTotal", "Tiered Total") }),
|
|
130
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: formatMoney(tiers.reduce((sum, tier) => sum + tier.subtotal, 0), currency) })
|
|
131
|
+
] })
|
|
132
|
+
] })
|
|
133
|
+
] })
|
|
134
|
+
] }),
|
|
135
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
136
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: t("cpq.priceAdjustments.waterfall.title", "Price waterfall") }),
|
|
137
|
+
/* @__PURE__ */ jsx(PriceWaterfall, { charge: c, currency, reasonLabels, renderManualActions })
|
|
138
|
+
] }),
|
|
139
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-1 border-t text-sm font-semibold", children: [
|
|
140
|
+
/* @__PURE__ */ jsx("span", { children: t("cpq.charges.finalTotal", "Final Total") }),
|
|
141
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: c.totalPrice != null ? formatMoney(c.totalPrice, currency) : "\u2014" })
|
|
142
|
+
] })
|
|
143
|
+
] });
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
ChargeBreakdownTable
|
|
147
|
+
};
|
|
148
|
+
//# sourceMappingURL=ChargeBreakdownTable.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/components/ChargeBreakdownTable.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Info } from 'lucide-react'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { DepthBadge, PriceWaterfall } from './PriceWaterfall'\nimport { ADJUSTMENT_ERROR_FALLBACKS, type WaterfallChargeInput } from '../lib/priceAdjustmentView'\nimport { formatMoney } from '../lib/formatMoney'\n\ntype TierBreakdownEntry = { tier: number; from: number; to: number; pricePerUnit: number; quantity: number; subtotal: number }\n\n/** One priced charge as stored on a quote or order line. */\nexport type ChargeRowView = WaterfallChargeInput & {\n chargeName: string\n quantity?: number | null\n note?: string | null\n breakdown?: { tiers: TierBreakdownEntry[] } | null\n adjustmentErrors?: Array<{ code: string }>\n}\n\n/** Right-aligned price column header with an explanatory hover tooltip. */\nfunction PriceColumnHeader({ label, hint }: { label: string; hint: string }) {\n return (\n <th className=\"text-right py-1 font-medium\">\n <SimpleTooltip content={hint} size=\"sm\">\n <span className=\"inline-flex items-center gap-1 cursor-help\" tabIndex={0} aria-label={`${label}: ${hint}`}>\n {label}\n <Info className=\"h-3 w-3\" aria-hidden />\n </span>\n </SimpleTooltip>\n </th>\n )\n}\n\n/**\n * Charge table shared by the quote and order detail pages: list / standard /\n * offer unit prices with depth badge, and a per-charge pricing detail (tiers +\n * price waterfall). Editing hooks are optional render props.\n */\nexport function ChargeBreakdownTable<C extends ChargeRowView>({\n charges,\n currency,\n openDetailIndex,\n onToggleDetail,\n reasonLabels,\n renderChargeActions,\n renderManualActions,\n}: {\n charges: C[]\n currency: string\n openDetailIndex: number | null\n onToggleDetail: (chargeIndex: number) => void\n reasonLabels?: Record<string, string>\n renderChargeActions?: (charge: C) => React.ReactNode\n renderManualActions?: (adjustmentId: string) => React.ReactNode\n}) {\n const t = useT()\n return (\n <table className=\"w-full text-xs\" data-testid=\"cpq-charge-breakdown\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left py-1 font-medium\">{t('cpq.charges.charge', 'Charge')}</th>\n <th className=\"text-left py-1 font-medium\">{t('cpq.charges.type', 'Type')}</th>\n <PriceColumnHeader\n label={t('cpq.priceAdjustments.listUnit', 'List')}\n hint={t('cpq.priceAdjustments.listUnitHint', 'Catalog unit price from the pricing table, before any price rules or manual adjustments.')}\n />\n <PriceColumnHeader\n label={t('cpq.priceAdjustments.standardUnit', 'Standard')}\n hint={t('cpq.priceAdjustments.standardUnitHint', 'Unit price after automatic price rules (e.g. volume or segment discounts), before manual adjustments.')}\n />\n <PriceColumnHeader\n label={t('cpq.priceAdjustments.offerUnit', 'Offer')}\n hint={t('cpq.priceAdjustments.offerUnitHint', 'Final unit price quoted to the customer, after price rules and manual adjustments. The badge shows the total change vs list.')}\n />\n <th className=\"text-right py-1 font-medium\">{t('cpq.charges.qty', 'Qty')}</th>\n <th className=\"text-right py-1 font-medium\">{t('cpq.charges.total', 'Total')}</th>\n <th className=\"w-16\" />\n </tr>\n </thead>\n <tbody>\n {charges.map((c, i) => {\n const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length)\n const isDetailOpen = openDetailIndex === i\n const listUnit = c.listUnitPrice ?? c.unitPrice\n const standardUnit = c.standardUnitPrice ?? c.unitPrice\n const errors = c.adjustmentErrors ?? []\n return (\n <React.Fragment key={i}>\n <tr className=\"border-t border-dashed border-border/50\">\n <td className=\"py-1\">\n <span>{c.chargeName}</span>\n {errors.length > 0 && (\n <Tag variant=\"warning\" className=\"ml-1.5 px-1.5 text-xs\" data-testid=\"cpq-adjustment-invalid\">\n {t('cpq.priceAdjustments.invalidReason', 'Invalid: {reason}', {\n reason: t(`cpq.priceAdjustments.errors.${errors[0].code}`, ADJUSTMENT_ERROR_FALLBACKS[errors[0].code] ?? errors[0].code),\n })}\n </Tag>\n )}\n </td>\n <td className=\"py-1 uppercase\">{c.chargeType}</td>\n <td className=\"py-1 text-right font-mono text-muted-foreground\">{formatMoney(listUnit, currency)}</td>\n <td className=\"py-1 text-right font-mono text-muted-foreground\">{formatMoney(standardUnit, currency)}</td>\n <td className=\"py-1 text-right font-mono\">\n <span className=\"inline-flex items-center gap-1\">\n <DepthBadge before={listUnit} after={c.unitPrice} />\n {formatMoney(c.unitPrice, currency)}\n </span>\n </td>\n <td className=\"py-1 text-right font-mono\">{c.quantity ?? '\u2014'}</td>\n <td className=\"py-1 text-right font-mono\">{c.totalPrice != null ? formatMoney(c.totalPrice, currency) : c.note ?? '\u2014'}</td>\n <td className=\"py-1 text-right whitespace-nowrap\">\n {renderChargeActions?.(c)}\n {hasDetail && (\n <button\n type=\"button\"\n onClick={() => onToggleDetail(i)}\n className=\"inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors\"\n title={t('cpq.priceAdjustments.showBreakdown', 'Show price breakdown')}\n >\n <Info className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </td>\n </tr>\n {isDetailOpen && (\n <tr>\n <td colSpan={8} className=\"pb-3 pt-1\">\n <ChargePricingDetail charge={c} currency={currency} reasonLabels={reasonLabels} renderManualActions={renderManualActions} />\n </td>\n </tr>\n )}\n </React.Fragment>\n )\n })}\n </tbody>\n </table>\n )\n}\n\nfunction ChargePricingDetail({ charge: c, currency, reasonLabels, renderManualActions }: {\n charge: ChargeRowView\n currency: string\n reasonLabels?: Record<string, string>\n renderManualActions?: (adjustmentId: string) => React.ReactNode\n}) {\n const t = useT()\n const tiers = c.breakdown?.tiers ?? []\n return (\n <div className=\"rounded border bg-background p-3 space-y-3 text-xs\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-sm\">{t('cpq.charges.pricingDetailsFor', 'Pricing Details \u2014 {name}', { name: c.chargeName })}</span>\n <span className=\"text-muted-foreground\">{c.pricingMethod ?? 'flat'}</span>\n </div>\n\n {tiers.length > 0 && (\n <div>\n <div className=\"font-medium text-muted-foreground mb-1\">{t('cpq.charges.tierBreakdown', 'Tier Breakdown')}</div>\n <table className=\"w-full\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left py-0.5 font-medium\">{t('cpq.charges.tier', 'Tier')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.range', 'Range')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.pricePerUnit', 'Price/Unit')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.qty', 'Qty')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.subtotal', 'Subtotal')}</th>\n </tr>\n </thead>\n <tbody>\n {tiers.map((tier) => (\n <tr key={tier.tier} className=\"border-t border-border/30\">\n <td className=\"py-0.5\">{tier.tier}</td>\n <td className=\"py-0.5 text-right font-mono\">{tier.from}\u2013{tier.to}</td>\n <td className=\"py-0.5 text-right font-mono\">{formatMoney(tier.pricePerUnit, currency)}</td>\n <td className=\"py-0.5 text-right font-mono\">{tier.quantity}</td>\n <td className=\"py-0.5 text-right font-mono\">{formatMoney(tier.subtotal, currency)}</td>\n </tr>\n ))}\n <tr className=\"border-t font-medium\">\n <td colSpan={4} className=\"py-0.5 text-right\">{t('cpq.charges.tieredTotal', 'Tiered Total')}</td>\n <td className=\"py-0.5 text-right font-mono\">\n {formatMoney(tiers.reduce((sum, tier) => sum + tier.subtotal, 0), currency)}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n )}\n\n <div>\n <div className=\"font-medium text-muted-foreground mb-1\">{t('cpq.priceAdjustments.waterfall.title', 'Price waterfall')}</div>\n <PriceWaterfall charge={c} currency={currency} reasonLabels={reasonLabels} renderManualActions={renderManualActions} />\n </div>\n\n <div className=\"flex items-center justify-between pt-1 border-t text-sm font-semibold\">\n <span>{t('cpq.charges.finalTotal', 'Final Total')}</span>\n <span className=\"font-mono\">{c.totalPrice != null ? formatMoney(c.totalPrice, currency) : '\u2014'}</span>\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
5
|
+
import {
|
|
6
|
+
discountDepth,
|
|
7
|
+
formatDepth,
|
|
8
|
+
waterfallRows
|
|
9
|
+
} from "../lib/priceAdjustmentView.js";
|
|
10
|
+
function money(amount, currency) {
|
|
11
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
12
|
+
}
|
|
13
|
+
const TYPE_FALLBACKS = {
|
|
14
|
+
discount_percent: "% discount",
|
|
15
|
+
discount_absolute: "amount off",
|
|
16
|
+
surcharge_percent: "% surcharge",
|
|
17
|
+
surcharge_absolute: "fixed surcharge",
|
|
18
|
+
price_override: "price override"
|
|
19
|
+
};
|
|
20
|
+
function DepthBadge({ before, after, title }) {
|
|
21
|
+
const label = formatDepth(discountDepth(before, after));
|
|
22
|
+
if (!label) return null;
|
|
23
|
+
return /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "px-1.5 text-xs font-mono", title, children: label });
|
|
24
|
+
}
|
|
25
|
+
function PriceWaterfall({ charge, currency, reasonLabels, renderManualActions }) {
|
|
26
|
+
const t = useT();
|
|
27
|
+
const rows = waterfallRows(charge);
|
|
28
|
+
const fmtValue = (ruleType, value) => ruleType.endsWith("_percent") ? `${value}%` : money(value, currency);
|
|
29
|
+
return /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", "data-testid": "cpq-price-waterfall", children: [
|
|
30
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
31
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.step", "Step") }),
|
|
32
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.source", "Source") }),
|
|
33
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.value", "Value") }),
|
|
34
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.delta", "Delta") }),
|
|
35
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.unitPrice", "Unit price") }),
|
|
36
|
+
renderManualActions && /* @__PURE__ */ jsx("th", { className: "w-16" })
|
|
37
|
+
] }) }),
|
|
38
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => {
|
|
39
|
+
if (row.kind === "list" || row.kind === "standard" || row.kind === "offer") {
|
|
40
|
+
const label = row.kind === "list" ? t("cpq.priceAdjustments.waterfall.list", "List price") : row.kind === "standard" ? t("cpq.priceAdjustments.waterfall.standard", "Standard price") : t("cpq.priceAdjustments.waterfall.offer", "Offer price");
|
|
41
|
+
return /* @__PURE__ */ jsxs("tr", { className: `border-t border-border/30 ${row.kind === "offer" ? "font-semibold" : "font-medium"}`, children: [
|
|
42
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5", colSpan: 4, children: label }),
|
|
43
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: money(row.unitPrice, currency) }),
|
|
44
|
+
renderManualActions && /* @__PURE__ */ jsx("td", {})
|
|
45
|
+
] }, `${row.kind}-${index}`);
|
|
46
|
+
}
|
|
47
|
+
const source = row.kind === "rule" ? t("cpq.priceAdjustments.waterfall.ruleSource", "Price rule: {name}", { name: row.label }) : t("cpq.priceAdjustments.waterfall.manualSource", "Negotiated: {reason}", {
|
|
48
|
+
reason: reasonLabels?.[row.reasonCode ?? ""] ?? row.reasonCode ?? row.label
|
|
49
|
+
});
|
|
50
|
+
return /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
|
|
51
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 pl-3 text-muted-foreground", children: t(`cpq.priceAdjustments.types.${row.ruleType}`, TYPE_FALLBACKS[row.ruleType] ?? row.ruleType) }),
|
|
52
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5", children: source }),
|
|
53
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmtValue(row.ruleType, row.value) }),
|
|
54
|
+
/* @__PURE__ */ jsxs("td", { className: "py-0.5 text-right font-mono", children: [
|
|
55
|
+
row.delta > 0 ? "+" : "",
|
|
56
|
+
money(row.delta, currency)
|
|
57
|
+
] }),
|
|
58
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: money(row.unitPriceAfter, currency) }),
|
|
59
|
+
renderManualActions && /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right", children: row.kind === "manual" && row.adjustmentId ? renderManualActions(row.adjustmentId) : null })
|
|
60
|
+
] }, `${row.kind}-${index}`);
|
|
61
|
+
}) })
|
|
62
|
+
] });
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
DepthBadge,
|
|
66
|
+
PriceWaterfall
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=PriceWaterfall.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/components/PriceWaterfall.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport {\n discountDepth,\n formatDepth,\n waterfallRows,\n type WaterfallChargeInput,\n} from '../lib/priceAdjustmentView'\n\nfunction money(amount: number, currency: string): string {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nconst TYPE_FALLBACKS: Record<string, string> = {\n discount_percent: '% discount',\n discount_absolute: 'amount off',\n surcharge_percent: '% surcharge',\n surcharge_absolute: 'fixed surcharge',\n price_override: 'price override',\n}\n\n/** Signed depth badge: \"\u221220%\" for a discount, \"+5%\" for a surcharge; text, never colour only. */\nexport function DepthBadge({ before, after, title }: { before: number | null | undefined; after: number | null | undefined; title?: string }) {\n const label = formatDepth(discountDepth(before, after))\n if (!label) return null\n return <Tag variant=\"neutral\" className=\"px-1.5 text-xs font-mono\" title={title}>{label}</Tag>\n}\n\nexport type PriceWaterfallProps = {\n charge: WaterfallChargeInput & { chargeName?: string }\n currency: string\n /** Reason code \u2192 label; manual rows fall back to the code. */\n reasonLabels?: Record<string, string>\n /** Optional per-adjustment actions (edit / delete) rendered on manual rows. */\n renderManualActions?: (adjustmentId: string) => React.ReactNode\n}\n\n/** Read-only list \u2192 rule \u2192 standard \u2192 manual \u2192 offer breakdown for one charge (unit prices). */\nexport function PriceWaterfall({ charge, currency, reasonLabels, renderManualActions }: PriceWaterfallProps) {\n const t = useT()\n const rows = waterfallRows(charge)\n const fmtValue = (ruleType: string, value: number) =>\n ruleType.endsWith('_percent') ? `${value}%` : money(value, currency)\n return (\n <table className=\"w-full text-xs\" data-testid=\"cpq-price-waterfall\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left py-0.5 font-medium\">{t('cpq.priceAdjustments.waterfall.step', 'Step')}</th>\n <th className=\"text-left py-0.5 font-medium\">{t('cpq.priceAdjustments.waterfall.source', 'Source')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.priceAdjustments.waterfall.value', 'Value')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.priceAdjustments.waterfall.delta', 'Delta')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.priceAdjustments.waterfall.unitPrice', 'Unit price')}</th>\n {renderManualActions && <th className=\"w-16\" />}\n </tr>\n </thead>\n <tbody>\n {rows.map((row, index) => {\n if (row.kind === 'list' || row.kind === 'standard' || row.kind === 'offer') {\n const label = row.kind === 'list'\n ? t('cpq.priceAdjustments.waterfall.list', 'List price')\n : row.kind === 'standard'\n ? t('cpq.priceAdjustments.waterfall.standard', 'Standard price')\n : t('cpq.priceAdjustments.waterfall.offer', 'Offer price')\n return (\n <tr key={`${row.kind}-${index}`} className={`border-t border-border/30 ${row.kind === 'offer' ? 'font-semibold' : 'font-medium'}`}>\n <td className=\"py-0.5\" colSpan={4}>{label}</td>\n <td className=\"py-0.5 text-right font-mono\">{money(row.unitPrice, currency)}</td>\n {renderManualActions && <td />}\n </tr>\n )\n }\n const source = row.kind === 'rule'\n ? t('cpq.priceAdjustments.waterfall.ruleSource', 'Price rule: {name}', { name: row.label })\n : t('cpq.priceAdjustments.waterfall.manualSource', 'Negotiated: {reason}', {\n reason: reasonLabels?.[row.reasonCode ?? ''] ?? row.reasonCode ?? row.label,\n })\n return (\n <tr key={`${row.kind}-${index}`} className=\"border-t border-border/30\">\n <td className=\"py-0.5 pl-3 text-muted-foreground\">{t(`cpq.priceAdjustments.types.${row.ruleType}`, TYPE_FALLBACKS[row.ruleType] ?? row.ruleType)}</td>\n <td className=\"py-0.5\">{source}</td>\n <td className=\"py-0.5 text-right font-mono\">{fmtValue(row.ruleType, row.value)}</td>\n <td className=\"py-0.5 text-right font-mono\">{row.delta > 0 ? '+' : ''}{money(row.delta, currency)}</td>\n <td className=\"py-0.5 text-right font-mono\">{money(row.unitPriceAfter, currency)}</td>\n {renderManualActions && (\n <td className=\"py-0.5 text-right\">{row.kind === 'manual' && row.adjustmentId ? renderManualActions(row.adjustmentId) : null}</td>\n )}\n </tr>\n )\n })}\n </tbody>\n </table>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AA4BS,cAqBD,YArBC;AAzBT,SAAS,YAAY;AACrB,SAAS,WAAW;AACpB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP,SAAS,MAAM,QAAgB,UAA0B;AACvD,SAAO,IAAI,KAAK,aAAa,SAAS,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,MAAM;AACtF;AAEA,MAAM,iBAAyC;AAAA,EAC7C,kBAAkB;AAAA,EAClB,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,oBAAoB;AAAA,EACpB,gBAAgB;AAClB;AAGO,SAAS,WAAW,EAAE,QAAQ,OAAO,MAAM,GAA4F;AAC5I,QAAM,QAAQ,YAAY,cAAc,QAAQ,KAAK,CAAC;AACtD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,oBAAC,OAAI,SAAQ,WAAU,WAAU,4BAA2B,OAAe,iBAAM;AAC1F;AAYO,SAAS,eAAe,EAAE,QAAQ,UAAU,cAAc,oBAAoB,GAAwB;AAC3G,QAAM,IAAI,KAAK;AACf,QAAM,OAAO,cAAc,MAAM;AACjC,QAAM,WAAW,CAAC,UAAkB,UAClC,SAAS,SAAS,UAAU,IAAI,GAAG,KAAK,MAAM,MAAM,OAAO,QAAQ;AACrE,SACE,qBAAC,WAAM,WAAU,kBAAiB,eAAY,uBAC5C;AAAA,wBAAC,WACC,+BAAC,QAAG,WAAU,yBACZ;AAAA,0BAAC,QAAG,WAAU,gCAAgC,YAAE,uCAAuC,MAAM,GAAE;AAAA,MAC/F,oBAAC,QAAG,WAAU,gCAAgC,YAAE,yCAAyC,QAAQ,GAAE;AAAA,MACnG,oBAAC,QAAG,WAAU,iCAAiC,YAAE,wCAAwC,OAAO,GAAE;AAAA,MAClG,oBAAC,QAAG,WAAU,iCAAiC,YAAE,wCAAwC,OAAO,GAAE;AAAA,MAClG,oBAAC,QAAG,WAAU,iCAAiC,YAAE,4CAA4C,YAAY,GAAE;AAAA,MAC1G,uBAAuB,oBAAC,QAAG,WAAU,QAAO;AAAA,OAC/C,GACF;AAAA,IACA,oBAAC,WACE,eAAK,IAAI,CAAC,KAAK,UAAU;AACxB,UAAI,IAAI,SAAS,UAAU,IAAI,SAAS,cAAc,IAAI,SAAS,SAAS;AAC1E,cAAM,QAAQ,IAAI,SAAS,SACvB,EAAE,uCAAuC,YAAY,IACrD,IAAI,SAAS,aACX,EAAE,2CAA2C,gBAAgB,IAC7D,EAAE,wCAAwC,aAAa;AAC7D,eACE,qBAAC,QAAgC,WAAW,6BAA6B,IAAI,SAAS,UAAU,kBAAkB,aAAa,IAC7H;AAAA,8BAAC,QAAG,WAAU,UAAS,SAAS,GAAI,iBAAM;AAAA,UAC1C,oBAAC,QAAG,WAAU,+BAA+B,gBAAM,IAAI,WAAW,QAAQ,GAAE;AAAA,UAC3E,uBAAuB,oBAAC,QAAG;AAAA,aAHrB,GAAG,IAAI,IAAI,IAAI,KAAK,EAI7B;AAAA,MAEJ;AACA,YAAM,SAAS,IAAI,SAAS,SACxB,EAAE,6CAA6C,sBAAsB,EAAE,MAAM,IAAI,MAAM,CAAC,IACxF,EAAE,+CAA+C,wBAAwB;AAAA,QACvE,QAAQ,eAAe,IAAI,cAAc,EAAE,KAAK,IAAI,cAAc,IAAI;AAAA,MACxE,CAAC;AACL,aACE,qBAAC,QAAgC,WAAU,6BACzC;AAAA,4BAAC,QAAG,WAAU,qCAAqC,YAAE,8BAA8B,IAAI,QAAQ,IAAI,eAAe,IAAI,QAAQ,KAAK,IAAI,QAAQ,GAAE;AAAA,QACjJ,oBAAC,QAAG,WAAU,UAAU,kBAAO;AAAA,QAC/B,oBAAC,QAAG,WAAU,+BAA+B,mBAAS,IAAI,UAAU,IAAI,KAAK,GAAE;AAAA,QAC/E,qBAAC,QAAG,WAAU,+BAA+B;AAAA,cAAI,QAAQ,IAAI,MAAM;AAAA,UAAI,MAAM,IAAI,OAAO,QAAQ;AAAA,WAAE;AAAA,QAClG,oBAAC,QAAG,WAAU,+BAA+B,gBAAM,IAAI,gBAAgB,QAAQ,GAAE;AAAA,QAChF,uBACC,oBAAC,QAAG,WAAU,qBAAqB,cAAI,SAAS,YAAY,IAAI,eAAe,oBAAoB,IAAI,YAAY,IAAI,MAAK;AAAA,WAPvH,GAAG,IAAI,IAAI,IAAI,KAAK,EAS7B;AAAA,IAEJ,CAAC,GACH;AAAA,KACF;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { formatDepth } from "../lib/priceAdjustmentView.js";
|
|
5
|
+
import { chargeTypeDepths, formatChangePct } from "../lib/quoteSummaryView.js";
|
|
6
|
+
import { formatMoney } from "../lib/formatMoney.js";
|
|
7
|
+
function SummaryFigure({
|
|
8
|
+
label,
|
|
9
|
+
value,
|
|
10
|
+
note,
|
|
11
|
+
title,
|
|
12
|
+
size = "lg",
|
|
13
|
+
valueClassName,
|
|
14
|
+
testId
|
|
15
|
+
}) {
|
|
16
|
+
return /* @__PURE__ */ jsxs("div", { "data-testid": testId, children: [
|
|
17
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", title, children: label }),
|
|
18
|
+
/* @__PURE__ */ jsxs("p", { className: `${size === "lg" ? "text-lg" : "text-sm"} font-semibold font-mono ${valueClassName ?? ""}`, children: [
|
|
19
|
+
value,
|
|
20
|
+
note && /* @__PURE__ */ jsxs("span", { className: "ml-1.5 text-xs font-normal text-muted-foreground", children: [
|
|
21
|
+
"(",
|
|
22
|
+
note,
|
|
23
|
+
")"
|
|
24
|
+
] })
|
|
25
|
+
] })
|
|
26
|
+
] });
|
|
27
|
+
}
|
|
28
|
+
function PricingSummaryCard({ summary, currency, arcDelta }) {
|
|
29
|
+
const t = useT();
|
|
30
|
+
const typeDepths = chargeTypeDepths(summary);
|
|
31
|
+
const usageCharges = summary.usageCharges ?? [];
|
|
32
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", "data-testid": "cpq-pricing-summary", children: [
|
|
33
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-3", children: t("cpq.quotes.summary.title", "Pricing Summary") }),
|
|
34
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-4", children: [
|
|
35
|
+
/* @__PURE__ */ jsx(
|
|
36
|
+
SummaryFigure,
|
|
37
|
+
{
|
|
38
|
+
label: t("cpq.quotes.summary.nrc", "Non-Recurring (NRC)"),
|
|
39
|
+
value: formatMoney(summary.nrcTotal, currency),
|
|
40
|
+
note: formatDepth(typeDepths.nrc),
|
|
41
|
+
testId: "cpq-summary-nrc"
|
|
42
|
+
}
|
|
43
|
+
),
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
SummaryFigure,
|
|
46
|
+
{
|
|
47
|
+
label: t("cpq.quotes.summary.mrc", "Monthly Recurring (MRC)"),
|
|
48
|
+
value: formatMoney(summary.mrcTotal, currency),
|
|
49
|
+
note: formatDepth(typeDepths.mrc),
|
|
50
|
+
testId: "cpq-summary-mrc"
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
summary.dealValue && /* @__PURE__ */ jsx(
|
|
54
|
+
SummaryFigure,
|
|
55
|
+
{
|
|
56
|
+
label: t("cpq.priceAdjustments.summary.dealValue", "Deal value"),
|
|
57
|
+
title: t("cpq.priceAdjustments.summary.hint", "Deal value counts recurring charges over the term plus one-time charges; existing subscription items on change quotes are excluded."),
|
|
58
|
+
value: formatMoney(summary.dealValue.offer, currency),
|
|
59
|
+
note: formatDepth(summary.discountDepth?.total ?? null),
|
|
60
|
+
testId: "cpq-deal-value"
|
|
61
|
+
}
|
|
62
|
+
),
|
|
63
|
+
summary.usageTotalEstimated != null && /* @__PURE__ */ jsx(
|
|
64
|
+
SummaryFigure,
|
|
65
|
+
{
|
|
66
|
+
label: t("cpq.quotes.summary.usageEstimated", "Usage (est.)"),
|
|
67
|
+
value: formatMoney(summary.usageTotalEstimated, currency)
|
|
68
|
+
}
|
|
69
|
+
),
|
|
70
|
+
usageCharges.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
71
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.quotes.summary.usageCharges", "Usage Charges") }),
|
|
72
|
+
usageCharges.map((uc, i) => /* @__PURE__ */ jsx("p", { className: "text-xs mt-0.5", children: t("cpq.quotes.summary.usageChargeLine", "{name}: {price}/unit", { name: uc.chargeName, price: formatMoney(uc.unitPrice, currency) }) }, i))
|
|
73
|
+
] })
|
|
74
|
+
] }),
|
|
75
|
+
arcDelta && /* @__PURE__ */ jsxs("div", { className: "mt-4 grid grid-cols-2 sm:grid-cols-4 gap-4 border-t pt-3", "data-testid": "cpq-arc-delta", children: [
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
SummaryFigure,
|
|
78
|
+
{
|
|
79
|
+
size: "sm",
|
|
80
|
+
testId: "cpq-arc-current-mrc",
|
|
81
|
+
label: t("cpq.quotes.summary.arc.currentMrc", "Current MRC"),
|
|
82
|
+
value: formatMoney(arcDelta.currentMrc, currency)
|
|
83
|
+
}
|
|
84
|
+
),
|
|
85
|
+
/* @__PURE__ */ jsx(
|
|
86
|
+
SummaryFigure,
|
|
87
|
+
{
|
|
88
|
+
size: "sm",
|
|
89
|
+
testId: "cpq-arc-new-mrc",
|
|
90
|
+
label: t("cpq.quotes.summary.arc.newMrc", "New MRC"),
|
|
91
|
+
value: formatMoney(arcDelta.newMrc, currency)
|
|
92
|
+
}
|
|
93
|
+
),
|
|
94
|
+
/* @__PURE__ */ jsx(
|
|
95
|
+
SummaryFigure,
|
|
96
|
+
{
|
|
97
|
+
size: "sm",
|
|
98
|
+
testId: "cpq-arc-mrc-change",
|
|
99
|
+
label: t("cpq.quotes.summary.arc.mrcChange", "MRC change"),
|
|
100
|
+
value: `${arcDelta.delta > 0 ? "+" : ""}${formatMoney(arcDelta.delta, currency)}`,
|
|
101
|
+
valueClassName: arcDelta.delta > 0 ? "text-status-success-text" : arcDelta.delta < 0 ? "text-destructive" : void 0,
|
|
102
|
+
note: formatChangePct(arcDelta.deltaPct)
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
] })
|
|
106
|
+
] });
|
|
107
|
+
}
|
|
108
|
+
export {
|
|
109
|
+
PricingSummaryCard,
|
|
110
|
+
SummaryFigure
|
|
111
|
+
};
|
|
112
|
+
//# sourceMappingURL=PricingSummaryCard.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/components/PricingSummaryCard.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { formatDepth } from '../lib/priceAdjustmentView'\nimport { chargeTypeDepths, formatChangePct, type ArcRecurringDelta } from '../lib/quoteSummaryView'\nimport { formatMoney } from '../lib/formatMoney'\n\n/** Pricing summary fields shared by quote and order results. Waterfall fields are absent on legacy snapshots. */\nexport type PricingSummaryView = {\n nrcTotal: number\n mrcTotal: number\n listNrcTotal?: number\n listMrcTotal?: number\n dealValue?: { list: number; standard: number; offer: number }\n discountDepth?: { total: number | null } | null\n usageTotalEstimated?: number | null\n usageCharges?: Array<{ chargeName: string; unitPrice: number }>\n}\n\nexport function SummaryFigure({\n label,\n value,\n note,\n title,\n size = 'lg',\n valueClassName,\n testId,\n}: {\n label: string\n value: string\n note?: string | null\n title?: string\n size?: 'lg' | 'sm'\n valueClassName?: string\n testId?: string\n}) {\n return (\n <div data-testid={testId}>\n <p className=\"text-xs text-muted-foreground\" title={title}>{label}</p>\n <p className={`${size === 'lg' ? 'text-lg' : 'text-sm'} font-semibold font-mono ${valueClassName ?? ''}`}>\n {value}\n {note && <span className=\"ml-1.5 text-xs font-normal text-muted-foreground\">({note})</span>}\n </p>\n </div>\n )\n}\n\n/**\n * NRC / MRC / deal value with discount depth in brackets, plus the ARC\n * current \u2192 new MRC row on Amend / Renew documents. Shared by the quote and\n * order detail pages so both read the same.\n */\nexport function PricingSummaryCard({ summary, currency, arcDelta }: {\n summary: PricingSummaryView\n currency: string\n arcDelta?: ArcRecurringDelta | null\n}) {\n const t = useT()\n const typeDepths = chargeTypeDepths(summary)\n const usageCharges = summary.usageCharges ?? []\n return (\n <div className=\"rounded-lg border bg-card p-4\" data-testid=\"cpq-pricing-summary\">\n <h3 className=\"text-sm font-medium mb-3\">{t('cpq.quotes.summary.title', 'Pricing Summary')}</h3>\n <div className=\"grid grid-cols-2 sm:grid-cols-4 gap-4\">\n <SummaryFigure\n label={t('cpq.quotes.summary.nrc', 'Non-Recurring (NRC)')}\n value={formatMoney(summary.nrcTotal, currency)}\n note={formatDepth(typeDepths.nrc)}\n testId=\"cpq-summary-nrc\"\n />\n <SummaryFigure\n label={t('cpq.quotes.summary.mrc', 'Monthly Recurring (MRC)')}\n value={formatMoney(summary.mrcTotal, currency)}\n note={formatDepth(typeDepths.mrc)}\n testId=\"cpq-summary-mrc\"\n />\n {summary.dealValue && (\n <SummaryFigure\n label={t('cpq.priceAdjustments.summary.dealValue', 'Deal value')}\n title={t('cpq.priceAdjustments.summary.hint', 'Deal value counts recurring charges over the term plus one-time charges; existing subscription items on change quotes are excluded.')}\n value={formatMoney(summary.dealValue.offer, currency)}\n note={formatDepth(summary.discountDepth?.total ?? null)}\n testId=\"cpq-deal-value\"\n />\n )}\n {summary.usageTotalEstimated != null && (\n <SummaryFigure\n label={t('cpq.quotes.summary.usageEstimated', 'Usage (est.)')}\n value={formatMoney(summary.usageTotalEstimated, currency)}\n />\n )}\n {usageCharges.length > 0 && (\n <div>\n <p className=\"text-xs text-muted-foreground\">{t('cpq.quotes.summary.usageCharges', 'Usage Charges')}</p>\n {usageCharges.map((uc, i) => (\n <p key={i} className=\"text-xs mt-0.5\">{t('cpq.quotes.summary.usageChargeLine', '{name}: {price}/unit', { name: uc.chargeName, price: formatMoney(uc.unitPrice, currency) })}</p>\n ))}\n </div>\n )}\n </div>\n {arcDelta && (\n <div className=\"mt-4 grid grid-cols-2 sm:grid-cols-4 gap-4 border-t pt-3\" data-testid=\"cpq-arc-delta\">\n <SummaryFigure\n size=\"sm\"\n testId=\"cpq-arc-current-mrc\"\n label={t('cpq.quotes.summary.arc.currentMrc', 'Current MRC')}\n value={formatMoney(arcDelta.currentMrc, currency)}\n />\n <SummaryFigure\n size=\"sm\"\n testId=\"cpq-arc-new-mrc\"\n label={t('cpq.quotes.summary.arc.newMrc', 'New MRC')}\n value={formatMoney(arcDelta.newMrc, currency)}\n />\n <SummaryFigure\n size=\"sm\"\n testId=\"cpq-arc-mrc-change\"\n label={t('cpq.quotes.summary.arc.mrcChange', 'MRC change')}\n value={`${arcDelta.delta > 0 ? '+' : ''}${formatMoney(arcDelta.delta, currency)}`}\n valueClassName={arcDelta.delta > 0 ? 'text-status-success-text' : arcDelta.delta < 0 ? 'text-destructive' : undefined}\n note={formatChangePct(arcDelta.deltaPct)}\n />\n </div>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -3,6 +3,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
import { ChevronRight } from "lucide-react";
|
|
5
5
|
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
7
|
import { formatStatusLabel } from "./statusMaps.js";
|
|
7
8
|
function StatusPath({
|
|
8
9
|
current,
|
|
@@ -13,15 +14,16 @@ function StatusPath({
|
|
|
13
14
|
onTransition,
|
|
14
15
|
allowedTransitions,
|
|
15
16
|
disabled,
|
|
16
|
-
ariaLabel
|
|
17
|
+
ariaLabel
|
|
17
18
|
}) {
|
|
19
|
+
const t = useT();
|
|
18
20
|
const sequence = React.useMemo(
|
|
19
21
|
() => [...happyPath, ...terminals],
|
|
20
22
|
[happyPath, terminals]
|
|
21
23
|
);
|
|
22
24
|
const currentIdx = sequence.findIndex((s) => s === current);
|
|
23
25
|
const interactiveMode = typeof onTransition === "function";
|
|
24
|
-
return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel, className: "flex items-center gap-1.5 flex-wrap text-xs", children: sequence.map((status, idx) => {
|
|
26
|
+
return /* @__PURE__ */ jsx("nav", { "aria-label": ariaLabel ?? t("cpq.statusPath.ariaLabel", "Status path"), className: "flex items-center gap-1.5 flex-wrap text-xs", children: sequence.map((status, idx) => {
|
|
25
27
|
const reached = currentIdx >= 0 && idx <= currentIdx;
|
|
26
28
|
const isCurrent = status === current;
|
|
27
29
|
const variant = statusMap[status] ?? "neutral";
|
|
@@ -37,7 +39,7 @@ function StatusPath({
|
|
|
37
39
|
type: "button",
|
|
38
40
|
onClick: () => onTransition(status),
|
|
39
41
|
"aria-current": isCurrent ? "step" : void 0,
|
|
40
|
-
title:
|
|
42
|
+
title: t("cpq.statusPath.transitionTo", "Transition to {status}", { status: label }),
|
|
41
43
|
className: wrapperClass,
|
|
42
44
|
children: /* @__PURE__ */ jsx(Tag, { variant, dot: isCurrent, className: tagClass, children: label })
|
|
43
45
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/cpq/components/StatusPath.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronRight } from 'lucide-react'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { TagMap } from '@open-mercato/ui/primitives/tag'\nimport { formatStatusLabel } from './statusMaps'\n\n// Generic horizontal status breadcrumb shared by quotes, orders,\n// subscriptions, and assets. Each consumer passes its own `path`\n// (happy-path order) + `terminals` (off-path failure / cancellation\n// states always rendered at the end) + `statusMap` (status \u2192 Tag\n// variant for colours).\n//\n// Two interaction modes:\n// - read-only: omit `onTransition`. Path renders as a progress\n// indicator only.\n// - interactive: pass `onTransition`. Every non-current step\n// becomes a button that calls `onTransition(target)`. If\n// `allowedTransitions` is provided, only those targets are\n// clickable; otherwise all non-current steps are clickable.\n\ntype TagVariant = TagMap<string>[string]\n\nexport function StatusPath({\n current,\n path: happyPath,\n terminals = [],\n statusMap,\n labels,\n onTransition,\n allowedTransitions,\n disabled,\n ariaLabel
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { ChevronRight } from 'lucide-react'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { TagMap } from '@open-mercato/ui/primitives/tag'\nimport { formatStatusLabel } from './statusMaps'\n\n// Generic horizontal status breadcrumb shared by quotes, orders,\n// subscriptions, and assets. Each consumer passes its own `path`\n// (happy-path order) + `terminals` (off-path failure / cancellation\n// states always rendered at the end) + `statusMap` (status \u2192 Tag\n// variant for colours).\n//\n// Two interaction modes:\n// - read-only: omit `onTransition`. Path renders as a progress\n// indicator only.\n// - interactive: pass `onTransition`. Every non-current step\n// becomes a button that calls `onTransition(target)`. If\n// `allowedTransitions` is provided, only those targets are\n// clickable; otherwise all non-current steps are clickable.\n\ntype TagVariant = TagMap<string>[string]\n\nexport function StatusPath({\n current,\n path: happyPath,\n terminals = [],\n statusMap,\n labels,\n onTransition,\n allowedTransitions,\n disabled,\n ariaLabel,\n}: {\n current: string\n /** Happy-path statuses in order. */\n path: string[]\n /** Off-path terminal statuses always rendered after the happy path. */\n terminals?: string[]\n /** Status \u2192 Tag variant. Missing entries fall back to \"neutral\". */\n statusMap: Record<string, TagVariant>\n /** Optional human labels, already translated by the caller (overrides `formatStatusLabel`). */\n labels?: Record<string, string>\n /** Click handler. Omit for read-only path. */\n onTransition?: (target: string) => void\n /** If set, only these targets are clickable. Else any non-current. */\n allowedTransitions?: string[]\n disabled?: boolean\n /** Translated accessible name; defaults to \"Status path\". */\n ariaLabel?: string\n}) {\n const t = useT()\n // Build the displayed sequence. Aliases can swap into the happy\n // path slot (e.g. quote's `pre_approved` shown as `approved`). We\n // don't handle aliasing here \u2014 consumers can swap entries in\n // their own happyPath before passing it in.\n const sequence = React.useMemo<string[]>(\n () => [...happyPath, ...terminals],\n [happyPath, terminals],\n )\n\n const currentIdx = sequence.findIndex((s) => s === current)\n const interactiveMode = typeof onTransition === 'function'\n\n return (\n <nav aria-label={ariaLabel ?? t('cpq.statusPath.ariaLabel', 'Status path')} className=\"flex items-center gap-1.5 flex-wrap text-xs\">\n {sequence.map((status, idx) => {\n const reached = currentIdx >= 0 && idx <= currentIdx\n const isCurrent = status === current\n const variant = statusMap[status] ?? 'neutral'\n const label = labels?.[status] ?? formatStatusLabel(status)\n const tagClass = isCurrent\n ? 'ring-2 ring-primary/40'\n : reached\n ? ''\n : 'opacity-60'\n // Clickability: interactive mode + not current + not disabled\n // + (no allowedTransitions list OR status is in the list).\n const clickable =\n interactiveMode &&\n !isCurrent &&\n !disabled &&\n (allowedTransitions ? allowedTransitions.includes(status) : true)\n const wrapperClass = clickable\n ? 'cursor-pointer hover:ring-2 hover:ring-primary/30 rounded-full'\n : ''\n\n return (\n <React.Fragment key={status}>\n {idx > 0 && <ChevronRight className=\"h-3 w-3 text-muted-foreground shrink-0\" />}\n {clickable ? (\n <button\n type=\"button\"\n onClick={() => onTransition!(status)}\n aria-current={isCurrent ? 'step' : undefined}\n title={t('cpq.statusPath.transitionTo', 'Transition to {status}', { status: label })}\n className={wrapperClass}\n >\n <Tag variant={variant} dot={isCurrent} className={tagClass}>\n {label}\n </Tag>\n </button>\n ) : (\n <Tag\n variant={variant}\n dot={isCurrent}\n className={tagClass}\n aria-current={isCurrent ? 'step' : undefined}\n >\n {label}\n </Tag>\n )}\n </React.Fragment>\n )\n })}\n </nav>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAyFU,SACc,KADd;AAxFV,YAAY,WAAW;AACvB,SAAS,oBAAoB;AAC7B,SAAS,WAAW;AACpB,SAAS,YAAY;AAErB,SAAS,yBAAyB;AAkB3B,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,MAAM;AAAA,EACN,YAAY,CAAC;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAiBG;AACD,QAAM,IAAI,KAAK;AAKf,QAAM,WAAW,MAAM;AAAA,IACrB,MAAM,CAAC,GAAG,WAAW,GAAG,SAAS;AAAA,IACjC,CAAC,WAAW,SAAS;AAAA,EACvB;AAEA,QAAM,aAAa,SAAS,UAAU,CAAC,MAAM,MAAM,OAAO;AAC1D,QAAM,kBAAkB,OAAO,iBAAiB;AAEhD,SACE,oBAAC,SAAI,cAAY,aAAa,EAAE,4BAA4B,aAAa,GAAG,WAAU,+CACnF,mBAAS,IAAI,CAAC,QAAQ,QAAQ;AAC7B,UAAM,UAAU,cAAc,KAAK,OAAO;AAC1C,UAAM,YAAY,WAAW;AAC7B,UAAM,UAAU,UAAU,MAAM,KAAK;AACrC,UAAM,QAAQ,SAAS,MAAM,KAAK,kBAAkB,MAAM;AAC1D,UAAM,WAAW,YACb,2BACA,UACE,KACA;AAGN,UAAM,YACJ,mBACA,CAAC,aACD,CAAC,aACA,qBAAqB,mBAAmB,SAAS,MAAM,IAAI;AAC9D,UAAM,eAAe,YACjB,mEACA;AAEJ,WACE,qBAAC,MAAM,UAAN,EACE;AAAA,YAAM,KAAK,oBAAC,gBAAa,WAAU,0CAAyC;AAAA,MAC5E,YACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,aAAc,MAAM;AAAA,UACnC,gBAAc,YAAY,SAAS;AAAA,UACnC,OAAO,EAAE,+BAA+B,0BAA0B,EAAE,QAAQ,MAAM,CAAC;AAAA,UACnF,WAAW;AAAA,UAEX,8BAAC,OAAI,SAAkB,KAAK,WAAW,WAAW,UAC/C,iBACH;AAAA;AAAA,MACF,IAEA;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA,KAAK;AAAA,UACL,WAAW;AAAA,UACX,gBAAc,YAAY,SAAS;AAAA,UAElC;AAAA;AAAA,MACH;AAAA,SAtBiB,MAwBrB;AAAA,EAEJ,CAAC,GACH;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -4,7 +4,7 @@ import { flash } from "@open-mercato/ui/backend/FlashMessages";
|
|
|
4
4
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
5
|
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
6
6
|
function useCpqRowActions(options) {
|
|
7
|
-
const { endpoint,
|
|
7
|
+
const { endpoint, editHref, onReload, canDelete } = options;
|
|
8
8
|
const t = useT();
|
|
9
9
|
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
10
10
|
const deleteOne = React.useCallback(
|
|
@@ -22,25 +22,22 @@ function useCpqRowActions(options) {
|
|
|
22
22
|
async (row) => {
|
|
23
23
|
if (canDelete && !canDelete(row)) return;
|
|
24
24
|
const confirmed = await confirm({
|
|
25
|
-
title: t(
|
|
26
|
-
"cpq.rowActions.deleteOne.confirm",
|
|
27
|
-
`Delete this ${entityName}?`
|
|
28
|
-
),
|
|
25
|
+
title: t("cpq.rowActions.deleteOne.confirm", "Delete this item?"),
|
|
29
26
|
variant: "destructive"
|
|
30
27
|
});
|
|
31
28
|
if (!confirmed) return;
|
|
32
29
|
const ok = await deleteOne(row.id);
|
|
33
30
|
if (!ok) {
|
|
34
31
|
flash(
|
|
35
|
-
t("cpq.rowActions.deleteOne.error",
|
|
32
|
+
t("cpq.rowActions.deleteOne.error", "Failed to delete the item"),
|
|
36
33
|
"error"
|
|
37
34
|
);
|
|
38
35
|
return;
|
|
39
36
|
}
|
|
40
|
-
flash(t("cpq.rowActions.deleteOne.success",
|
|
37
|
+
flash(t("cpq.rowActions.deleteOne.success", "Item deleted"), "success");
|
|
41
38
|
onReload();
|
|
42
39
|
},
|
|
43
|
-
[canDelete, confirm, deleteOne,
|
|
40
|
+
[canDelete, confirm, deleteOne, onReload, t]
|
|
44
41
|
);
|
|
45
42
|
const bulkDelete = React.useCallback(
|
|
46
43
|
async (rows) => {
|
|
@@ -48,19 +45,13 @@ function useCpqRowActions(options) {
|
|
|
48
45
|
const eligible = canDelete ? rows.filter(canDelete) : rows;
|
|
49
46
|
if (!eligible.length) {
|
|
50
47
|
flash(
|
|
51
|
-
t(
|
|
52
|
-
"cpq.rowActions.deleteBulk.noEligible",
|
|
53
|
-
`None of the selected ${entityName}s can be deleted in their current state.`
|
|
54
|
-
),
|
|
48
|
+
t("cpq.rowActions.deleteBulk.noEligible", "None of the selected items can be deleted in their current state."),
|
|
55
49
|
"error"
|
|
56
50
|
);
|
|
57
51
|
return { ok: false };
|
|
58
52
|
}
|
|
59
53
|
const confirmed = await confirm({
|
|
60
|
-
title: t(
|
|
61
|
-
"cpq.rowActions.deleteBulk.confirm",
|
|
62
|
-
`Delete ${eligible.length} ${entityName}${eligible.length > 1 ? "s" : ""}?`
|
|
63
|
-
),
|
|
54
|
+
title: t("cpq.rowActions.deleteBulk.confirm", "Delete selected items ({count})?", { count: eligible.length }),
|
|
64
55
|
variant: "destructive"
|
|
65
56
|
});
|
|
66
57
|
if (!confirmed) return { ok: false };
|
|
@@ -71,22 +62,19 @@ function useCpqRowActions(options) {
|
|
|
71
62
|
}
|
|
72
63
|
if (failed > 0) {
|
|
73
64
|
flash(
|
|
74
|
-
t(
|
|
75
|
-
"cpq.rowActions.deleteBulk.partialError",
|
|
76
|
-
`Failed to delete ${failed} ${entityName}${failed > 1 ? "s" : ""}`
|
|
77
|
-
),
|
|
65
|
+
t("cpq.rowActions.deleteBulk.partialError", "Failed to delete {count} of the selected items", { count: failed }),
|
|
78
66
|
"error"
|
|
79
67
|
);
|
|
80
68
|
} else {
|
|
81
69
|
flash(
|
|
82
|
-
t("cpq.rowActions.deleteBulk.success",
|
|
70
|
+
t("cpq.rowActions.deleteBulk.success", "Selected items deleted ({count})", { count: eligible.length }),
|
|
83
71
|
"success"
|
|
84
72
|
);
|
|
85
73
|
}
|
|
86
74
|
onReload();
|
|
87
75
|
return { ok: failed === 0, affectedCount: eligible.length - failed };
|
|
88
76
|
},
|
|
89
|
-
[canDelete, confirm, deleteOne,
|
|
77
|
+
[canDelete, confirm, deleteOne, onReload, t]
|
|
90
78
|
);
|
|
91
79
|
const buildItems = React.useCallback(
|
|
92
80
|
(row, extras) => {
|