@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
|
@@ -3,6 +3,7 @@ import * as React from 'react'
|
|
|
3
3
|
import type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
5
|
import { Tag } from '@open-mercato/ui/primitives/tag'
|
|
6
|
+
import { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
6
7
|
import {
|
|
7
8
|
formatStatusLabel,
|
|
8
9
|
quoteCpqStatusMap,
|
|
@@ -58,10 +59,24 @@ function fmt(amount: number, currency: string): string {
|
|
|
58
59
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
function chargeTypeLabel(t: TranslateFn, chargeType: ResolvedCharge['chargeType']): string {
|
|
63
|
+
switch (chargeType) {
|
|
64
|
+
case 'nrc':
|
|
65
|
+
return t('cpq.lines.chargeType.nrc', 'nrc')
|
|
66
|
+
case 'mrc':
|
|
67
|
+
return t('cpq.lines.chargeType.mrc', 'mrc')
|
|
68
|
+
case 'usage':
|
|
69
|
+
return t('cpq.lines.chargeType.usage', 'usage')
|
|
70
|
+
default:
|
|
71
|
+
return chargeType
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
61
75
|
// ─── Main Widget (read-only) ─────────────────────────────────────
|
|
62
76
|
|
|
63
77
|
export default function QuoteConfiguratorWidget({ data }: InjectionWidgetComponentProps) {
|
|
64
78
|
const router = useRouter()
|
|
79
|
+
const t = useT()
|
|
65
80
|
const record = data as Record<string, unknown> | null
|
|
66
81
|
const quoteId = (record?.id as string) ?? null
|
|
67
82
|
|
|
@@ -83,12 +98,12 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
83
98
|
return () => { cancelled = true }
|
|
84
99
|
}, [quoteId])
|
|
85
100
|
|
|
86
|
-
if (!quoteId) return <div className="p-4 text-sm text-muted-foreground">No quote context available
|
|
101
|
+
if (!quoteId) return <div className="p-4 text-sm text-muted-foreground">{t('cpq.widgets.quoteConfigurator.noContext', 'No quote context available.')}</div>
|
|
87
102
|
|
|
88
103
|
if (loading) {
|
|
89
104
|
return (
|
|
90
105
|
<div className="flex items-center gap-2 p-6 text-sm text-muted-foreground">
|
|
91
|
-
<Spinner /> Loading CPQ configuration…
|
|
106
|
+
<Spinner /> {t('cpq.widgets.quoteConfigurator.loading', 'Loading CPQ configuration…')}
|
|
92
107
|
</div>
|
|
93
108
|
)
|
|
94
109
|
}
|
|
@@ -105,7 +120,7 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
105
120
|
<path strokeLinecap="round" strokeLinejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
|
106
121
|
</svg>
|
|
107
122
|
</div>
|
|
108
|
-
<p className="text-sm text-muted-foreground mb-4">This quote has no CPQ configuration yet
|
|
123
|
+
<p className="text-sm text-muted-foreground mb-4">{t('cpq.widgets.quoteConfigurator.noConfig', 'This quote has no CPQ configuration yet.')}</p>
|
|
109
124
|
<button
|
|
110
125
|
onClick={() => router.push(`/backend/cpq/quotes/new?quoteId=${quoteId}&customerId=${custId}`)}
|
|
111
126
|
className="inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
|
|
@@ -113,9 +128,9 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
113
128
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor">
|
|
114
129
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
|
115
130
|
</svg>
|
|
116
|
-
Create CPQ Quote
|
|
131
|
+
{t('cpq.widgets.quoteConfigurator.create', 'Create CPQ Quote')}
|
|
117
132
|
</button>
|
|
118
|
-
{!custId && <p className="mt-2 text-xs text-status-warning-text">Assign a customer to this quote first
|
|
133
|
+
{!custId && <p className="mt-2 text-xs text-status-warning-text">{t('cpq.widgets.quoteConfigurator.assignCustomer', 'Assign a customer to this quote first.')}</p>}
|
|
119
134
|
</div>
|
|
120
135
|
)
|
|
121
136
|
}
|
|
@@ -146,7 +161,7 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
146
161
|
onClick={() => router.push(`/backend/cpq/quotes/${cpqQuote.id}`)}
|
|
147
162
|
className="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors"
|
|
148
163
|
>
|
|
149
|
-
Open CPQ Configurator
|
|
164
|
+
{t('cpq.widgets.quoteConfigurator.open', 'Open CPQ Configurator')}
|
|
150
165
|
<svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" strokeWidth="2" stroke="currentColor">
|
|
151
166
|
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
|
|
152
167
|
</svg>
|
|
@@ -156,7 +171,7 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
156
171
|
{/* Lines */}
|
|
157
172
|
{cpqQuote.lines.length === 0 ? (
|
|
158
173
|
<div className="rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground">
|
|
159
|
-
No items configured yet.
|
|
174
|
+
{t('cpq.widgets.quoteConfigurator.noLines', 'No items configured yet.')}
|
|
160
175
|
</div>
|
|
161
176
|
) : (
|
|
162
177
|
<div className="rounded-lg border bg-card divide-y">
|
|
@@ -178,9 +193,9 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
178
193
|
<span className="font-medium text-sm">{line.offeringName}</span>
|
|
179
194
|
{line.quantity > 1 && <span className="rounded bg-muted px-1.5 py-0.5 text-xs font-medium">×{line.quantity}</span>}
|
|
180
195
|
{line.isConfigured ? (
|
|
181
|
-
<Tag variant="success" className="px-1.5 text-xs">configured</Tag>
|
|
196
|
+
<Tag variant="success" className="px-1.5 text-xs">{t('cpq.quotes.detail.lineConfigured', 'configured')}</Tag>
|
|
182
197
|
) : (
|
|
183
|
-
<Tag variant="warning" className="px-1.5 text-xs">incomplete</Tag>
|
|
198
|
+
<Tag variant="warning" className="px-1.5 text-xs">{t('cpq.quotes.detail.lineIncomplete', 'incomplete')}</Tag>
|
|
184
199
|
)}
|
|
185
200
|
</div>
|
|
186
201
|
{line.validationErrors && line.validationErrors.length > 0 && (
|
|
@@ -188,8 +203,8 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
188
203
|
)}
|
|
189
204
|
</div>
|
|
190
205
|
<div className="shrink-0 text-right text-xs space-y-0.5">
|
|
191
|
-
{line.nrcTotal > 0 && <div><span className="text-muted-foreground">NRC </span><span className="font-mono font-medium">{fmt(line.nrcTotal, currency)}</span></div>}
|
|
192
|
-
{line.mrcTotal > 0 && <div><span className="text-muted-foreground">MRC </span><span className="font-mono font-medium">{fmt(line.mrcTotal, currency)}</span></div>}
|
|
206
|
+
{line.nrcTotal > 0 && <div><span className="text-muted-foreground">{t('cpq.lines.nrc', 'NRC')} </span><span className="font-mono font-medium">{fmt(line.nrcTotal, currency)}</span></div>}
|
|
207
|
+
{line.mrcTotal > 0 && <div><span className="text-muted-foreground">{t('cpq.lines.mrc', 'MRC')} </span><span className="font-mono font-medium">{fmt(line.mrcTotal, currency)}</span></div>}
|
|
193
208
|
</div>
|
|
194
209
|
</div>
|
|
195
210
|
|
|
@@ -199,18 +214,18 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
199
214
|
<table className="w-full text-xs">
|
|
200
215
|
<thead>
|
|
201
216
|
<tr className="text-muted-foreground">
|
|
202
|
-
<th className="text-left py-1 font-medium">Charge</th>
|
|
203
|
-
<th className="text-left py-1 font-medium">Type</th>
|
|
204
|
-
<th className="text-right py-1 font-medium">Unit Price</th>
|
|
205
|
-
<th className="text-right py-1 font-medium">Qty</th>
|
|
206
|
-
<th className="text-right py-1 font-medium">Total</th>
|
|
217
|
+
<th className="text-left py-1 font-medium">{t('cpq.widgets.quoteConfigurator.charges.charge', 'Charge')}</th>
|
|
218
|
+
<th className="text-left py-1 font-medium">{t('cpq.widgets.quoteConfigurator.charges.type', 'Type')}</th>
|
|
219
|
+
<th className="text-right py-1 font-medium">{t('cpq.widgets.quoteConfigurator.charges.unitPrice', 'Unit Price')}</th>
|
|
220
|
+
<th className="text-right py-1 font-medium">{t('cpq.widgets.quoteConfigurator.charges.qty', 'Qty')}</th>
|
|
221
|
+
<th className="text-right py-1 font-medium">{t('cpq.widgets.quoteConfigurator.charges.total', 'Total')}</th>
|
|
207
222
|
</tr>
|
|
208
223
|
</thead>
|
|
209
224
|
<tbody>
|
|
210
225
|
{line.charges.map((c, i) => (
|
|
211
226
|
<tr key={i} className="border-t border-dashed border-border/50">
|
|
212
227
|
<td className="py-1">{c.chargeName}</td>
|
|
213
|
-
<td className="py-1 uppercase">{c.chargeType}</td>
|
|
228
|
+
<td className="py-1 uppercase">{chargeTypeLabel(t, c.chargeType)}</td>
|
|
214
229
|
<td className="py-1 text-right font-mono">{fmt(c.unitPrice, currency)}</td>
|
|
215
230
|
<td className="py-1 text-right font-mono">{c.quantity ?? '—'}</td>
|
|
216
231
|
<td className="py-1 text-right font-mono">{c.totalPrice != null ? fmt(c.totalPrice, currency) : c.note ?? '—'}</td>
|
|
@@ -231,19 +246,19 @@ export default function QuoteConfiguratorWidget({ data }: InjectionWidgetCompone
|
|
|
231
246
|
<div className="rounded-lg border bg-card p-4">
|
|
232
247
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-4">
|
|
233
248
|
<div>
|
|
234
|
-
<p className="text-xs text-muted-foreground">Non-Recurring (NRC)</p>
|
|
249
|
+
<p className="text-xs text-muted-foreground">{t('cpq.quotes.summary.nrc', 'Non-Recurring (NRC)')}</p>
|
|
235
250
|
<p className="text-lg font-semibold font-mono">{fmt(summary.nrcTotal, currency)}</p>
|
|
236
251
|
</div>
|
|
237
252
|
<div>
|
|
238
|
-
<p className="text-xs text-muted-foreground">Monthly Recurring (MRC)</p>
|
|
253
|
+
<p className="text-xs text-muted-foreground">{t('cpq.quotes.summary.mrc', 'Monthly Recurring (MRC)')}</p>
|
|
239
254
|
<p className="text-lg font-semibold font-mono">{fmt(summary.mrcTotal, currency)}</p>
|
|
240
255
|
</div>
|
|
241
256
|
{summary.usageCharges.length > 0 && (
|
|
242
257
|
<div>
|
|
243
|
-
<p className="text-xs text-muted-foreground">Usage Charges</p>
|
|
258
|
+
<p className="text-xs text-muted-foreground">{t('cpq.quotes.summary.usageCharges', 'Usage Charges')}</p>
|
|
244
259
|
<div className="mt-1 space-y-0.5">
|
|
245
260
|
{summary.usageCharges.map((uc, i) => (
|
|
246
|
-
<p key={i} className="text-xs">{uc.chargeName}: {fmt(uc.unitPrice, currency)}
|
|
261
|
+
<p key={i} className="text-xs">{uc.chargeName}: {t('cpq.lines.perUnit', '{price}/unit', { price: fmt(uc.unitPrice, currency) })}</p>
|
|
247
262
|
))}
|
|
248
263
|
</div>
|
|
249
264
|
</div>
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'
|
|
2
2
|
|
|
3
|
+
// Tab labels: the sales document page translates `groupLabel` as an i18n key
|
|
4
|
+
// (`t(groupLabel, metadata.title)`), so the sales tabs use CPQ keys. The
|
|
5
|
+
// customers detail pages (legacy v1) render `groupLabel` verbatim, so the
|
|
6
|
+
// customer-inventory tab keeps plain English until the host translates it
|
|
7
|
+
// (spec 2026-11-15-cpq-multi-language, Risks).
|
|
8
|
+
|
|
3
9
|
export const injectionTable: ModuleInjectionTable = {
|
|
4
10
|
'sales.document.detail.quote:tabs': [
|
|
5
11
|
{
|
|
6
12
|
widgetId: 'cpq.injection.quote-configurator',
|
|
7
13
|
kind: 'tab',
|
|
8
|
-
groupLabel: '
|
|
14
|
+
groupLabel: 'cpq.widgets.quoteConfigurator.tab',
|
|
9
15
|
groupDescription: 'Configure, price, and manage quote offerings',
|
|
10
16
|
priority: 70,
|
|
11
17
|
},
|
|
@@ -14,7 +20,7 @@ export const injectionTable: ModuleInjectionTable = {
|
|
|
14
20
|
{
|
|
15
21
|
widgetId: 'cpq.injection.order-cpq-tab',
|
|
16
22
|
kind: 'tab',
|
|
17
|
-
groupLabel: '
|
|
23
|
+
groupLabel: 'cpq.widgets.orderCpqTab.tab',
|
|
18
24
|
groupDescription: 'View the CPQ order associated with this sales order',
|
|
19
25
|
priority: 60,
|
|
20
26
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import { WizardEngineProvider, useWizardEngine } from './engine'
|
|
5
6
|
import { getStepType } from './registry'
|
|
6
7
|
import type { WizardDefinitionResult, WizardStepProps } from './types'
|
|
@@ -9,6 +10,7 @@ import type { TenantScope } from '../services/types'
|
|
|
9
10
|
// ─── Step renderer ──────────────────────────────────────────────
|
|
10
11
|
|
|
11
12
|
function StepRenderer({ scope }: { scope: TenantScope }) {
|
|
13
|
+
const t = useT()
|
|
12
14
|
const { state, completeStep, goBack, skipStep } = useWizardEngine()
|
|
13
15
|
const { currentStep, stepData, completedSteps, currentStepId, params, quoteId, customerId } = state
|
|
14
16
|
|
|
@@ -33,18 +35,18 @@ function StepRenderer({ scope }: { scope: TenantScope }) {
|
|
|
33
35
|
return (
|
|
34
36
|
<div className="rounded-lg border bg-card p-6 text-center space-y-3">
|
|
35
37
|
<p className="text-sm text-destructive">
|
|
36
|
-
Unknown step type: <strong>{currentStep.type}</strong>
|
|
38
|
+
{t('cpq.wizard.runner.unknownStepType', 'Unknown step type:')} <strong>{currentStep.type}</strong>
|
|
37
39
|
</p>
|
|
38
|
-
<p className="text-sm text-muted-foreground">This step type is not registered. Register it via <code className="font-mono text-xs bg-muted px-1 py-0.5 rounded">registerStepType()</code>.</p>
|
|
40
|
+
<p className="text-sm text-muted-foreground">{t('cpq.wizard.runner.notRegistered', 'This step type is not registered. Register it via')} <code className="font-mono text-xs bg-muted px-1 py-0.5 rounded">{'registerStepType()'}</code>.</p>
|
|
39
41
|
<div className="flex gap-2 justify-center pt-2">
|
|
40
42
|
{state.canGoBack && (
|
|
41
43
|
<button type="button" onClick={goBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
42
|
-
Back
|
|
44
|
+
{t('cpq.common.back', 'Back')}
|
|
43
45
|
</button>
|
|
44
46
|
)}
|
|
45
47
|
{state.canSkip && (
|
|
46
48
|
<button type="button" onClick={skipStep} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
47
|
-
Skip
|
|
49
|
+
{t('cpq.wizard.skip', 'Skip')}
|
|
48
50
|
</button>
|
|
49
51
|
)}
|
|
50
52
|
</div>
|
|
@@ -98,6 +100,7 @@ function WizardProgress() {
|
|
|
98
100
|
// ─── Header ─────────────────────────────────────────────────────
|
|
99
101
|
|
|
100
102
|
function WizardHeader() {
|
|
103
|
+
const t = useT()
|
|
101
104
|
const { state } = useWizardEngine()
|
|
102
105
|
|
|
103
106
|
return (
|
|
@@ -105,7 +108,7 @@ function WizardHeader() {
|
|
|
105
108
|
<div>
|
|
106
109
|
<h2 className="text-lg font-semibold">{state.definition.name}</h2>
|
|
107
110
|
<p className="text-sm text-muted-foreground">
|
|
108
|
-
Step: {state.currentStep.title}
|
|
111
|
+
{t('cpq.wizard.runner.currentStep', 'Step: {title}', { title: state.currentStep.title })}
|
|
109
112
|
</p>
|
|
110
113
|
</div>
|
|
111
114
|
</div>
|
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useCallback, useState } from 'react'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
|
+
import { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
5
6
|
import type { WizardDefinitionResult } from './types'
|
|
6
7
|
|
|
7
8
|
// ─── API helper ─────────────────────────────────────────────────
|
|
8
9
|
|
|
9
|
-
async function apiJson<T>(url: string): Promise<T> {
|
|
10
|
+
async function apiJson<T>(t: TranslateFn, url: string): Promise<T> {
|
|
10
11
|
const res = await fetch(url)
|
|
11
12
|
if (!res.ok) {
|
|
12
13
|
const body = await res.json().catch(() => ({}))
|
|
13
|
-
throw new Error(body.error ??
|
|
14
|
+
throw new Error(body.error ?? t('cpq.wizard.trigger.apiError', 'API error {status}', { status: res.status }))
|
|
14
15
|
}
|
|
15
16
|
return res.json() as Promise<T>
|
|
16
17
|
}
|
|
@@ -43,13 +44,16 @@ export function WizardTrigger({
|
|
|
43
44
|
offeringCode,
|
|
44
45
|
params: wizardParams = {},
|
|
45
46
|
surface = 'backend',
|
|
46
|
-
label
|
|
47
|
+
label,
|
|
47
48
|
variant = 'button',
|
|
48
49
|
className,
|
|
49
50
|
basePath,
|
|
50
51
|
onStart,
|
|
51
52
|
}: WizardTriggerProps) {
|
|
52
53
|
const router = useRouter()
|
|
54
|
+
const t = useT()
|
|
55
|
+
const buttonLabel = label ?? t('cpq.wizard.trigger.start', 'Start Wizard')
|
|
56
|
+
const startingLabel = t('cpq.wizard.trigger.starting', 'Starting...')
|
|
53
57
|
const [loading, setLoading] = useState(false)
|
|
54
58
|
const [error, setError] = useState<string | null>(null)
|
|
55
59
|
|
|
@@ -63,18 +67,20 @@ export function WizardTrigger({
|
|
|
63
67
|
|
|
64
68
|
if (wizardCode) {
|
|
65
69
|
definition = await apiJson<WizardDefinitionResult>(
|
|
70
|
+
t,
|
|
66
71
|
`/api/cpq/wizards?code=${encodeURIComponent(wizardCode)}`,
|
|
67
72
|
)
|
|
68
73
|
} else {
|
|
69
74
|
const resolveParams = new URLSearchParams({ surface })
|
|
70
75
|
if (offeringCode) resolveParams.set('offeringCode', offeringCode)
|
|
71
76
|
definition = await apiJson<WizardDefinitionResult>(
|
|
77
|
+
t,
|
|
72
78
|
`/api/cpq/wizards/resolve?${resolveParams}`,
|
|
73
79
|
)
|
|
74
80
|
}
|
|
75
81
|
|
|
76
82
|
if (!definition) {
|
|
77
|
-
setError('No matching wizard found')
|
|
83
|
+
setError(t('cpq.wizard.trigger.notFound', 'No matching wizard found'))
|
|
78
84
|
return
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -90,11 +96,11 @@ export function WizardTrigger({
|
|
|
90
96
|
router.push(`${path}/${definition.code}${qs ? `?${qs}` : ''}`)
|
|
91
97
|
}
|
|
92
98
|
} catch (err) {
|
|
93
|
-
setError(err instanceof Error ? err.message : 'Failed to start wizard')
|
|
99
|
+
setError(err instanceof Error ? err.message : t('cpq.wizard.trigger.failed', 'Failed to start wizard'))
|
|
94
100
|
} finally {
|
|
95
101
|
setLoading(false)
|
|
96
102
|
}
|
|
97
|
-
}, [wizardCode, offeringCode, wizardParams, surface, basePath, onStart, router])
|
|
103
|
+
}, [wizardCode, offeringCode, wizardParams, surface, basePath, onStart, router, t])
|
|
98
104
|
|
|
99
105
|
if (variant === 'link') {
|
|
100
106
|
return (
|
|
@@ -106,7 +112,7 @@ export function WizardTrigger({
|
|
|
106
112
|
handleClick()
|
|
107
113
|
}}
|
|
108
114
|
>
|
|
109
|
-
{loading ?
|
|
115
|
+
{loading ? startingLabel : buttonLabel}
|
|
110
116
|
{error && <span className="ml-2 text-xs text-destructive">{error}</span>}
|
|
111
117
|
</a>
|
|
112
118
|
)
|
|
@@ -121,7 +127,7 @@ export function WizardTrigger({
|
|
|
121
127
|
tabIndex={0}
|
|
122
128
|
onKeyDown={(e) => { if (e.key === 'Enter') handleClick() }}
|
|
123
129
|
>
|
|
124
|
-
<p className="font-semibold text-sm">{
|
|
130
|
+
<p className="font-semibold text-sm">{buttonLabel}</p>
|
|
125
131
|
{error && <p className="mt-1 text-xs text-destructive">{error}</p>}
|
|
126
132
|
</div>
|
|
127
133
|
)
|
|
@@ -135,7 +141,7 @@ export function WizardTrigger({
|
|
|
135
141
|
onClick={handleClick}
|
|
136
142
|
disabled={loading}
|
|
137
143
|
>
|
|
138
|
-
{loading ?
|
|
144
|
+
{loading ? startingLabel : buttonLabel}
|
|
139
145
|
{error && <span className="ml-2 text-xs text-destructive/70">{error}</span>}
|
|
140
146
|
</button>
|
|
141
147
|
)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
|
|
6
7
|
type Customer = {
|
|
@@ -11,6 +12,7 @@ type Customer = {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps }: WizardStepProps) {
|
|
15
|
+
const t = useT()
|
|
14
16
|
const [customers, setCustomers] = useState<Customer[]>([])
|
|
15
17
|
const [search, setSearch] = useState('')
|
|
16
18
|
const [selected, setSelected] = useState<Customer | null>(null)
|
|
@@ -59,7 +61,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
59
61
|
<div className="flex gap-2">
|
|
60
62
|
<input
|
|
61
63
|
type="text"
|
|
62
|
-
placeholder=
|
|
64
|
+
placeholder={t('cpq.wizard.steps.customerSelect.searchPlaceholder', 'Search customers...')}
|
|
63
65
|
value={search}
|
|
64
66
|
onChange={(e) => setSearch(e.target.value)}
|
|
65
67
|
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
|
@@ -71,21 +73,23 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
71
73
|
disabled={loading}
|
|
72
74
|
className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50"
|
|
73
75
|
>
|
|
74
|
-
{loading
|
|
76
|
+
{loading
|
|
77
|
+
? t('cpq.wizard.steps.customerSelect.searching', 'Searching...')
|
|
78
|
+
: t('cpq.wizard.steps.customerSelect.search', 'Search')}
|
|
75
79
|
</button>
|
|
76
80
|
</div>
|
|
77
81
|
|
|
78
82
|
{selected && (
|
|
79
83
|
<div className="rounded-md border-2 border-primary bg-primary/5 p-3 flex items-center justify-between">
|
|
80
84
|
<span className="text-sm">
|
|
81
|
-
<strong>Selected
|
|
85
|
+
<strong>{t('cpq.wizard.steps.customerSelect.selected', 'Selected:')}</strong> {selected.name}
|
|
82
86
|
</span>
|
|
83
87
|
<button
|
|
84
88
|
type="button"
|
|
85
89
|
onClick={() => setSelected(null)}
|
|
86
90
|
className="text-xs text-muted-foreground hover:text-foreground underline"
|
|
87
91
|
>
|
|
88
|
-
Clear
|
|
92
|
+
{t('cpq.wizard.steps.customerSelect.clear', 'Clear')}
|
|
89
93
|
</button>
|
|
90
94
|
</div>
|
|
91
95
|
)}
|
|
@@ -107,7 +111,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
107
111
|
))}
|
|
108
112
|
{customers.length === 0 && !loading && (
|
|
109
113
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
110
|
-
No customers found.
|
|
114
|
+
{t('cpq.wizard.steps.customerSelect.empty', 'No customers found.')}
|
|
111
115
|
</div>
|
|
112
116
|
)}
|
|
113
117
|
</div>
|
|
@@ -115,7 +119,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
115
119
|
<div className="flex gap-2 justify-end pt-2">
|
|
116
120
|
{completedSteps.length > 0 && (
|
|
117
121
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
118
|
-
Back
|
|
122
|
+
{t('cpq.common.back', 'Back')}
|
|
119
123
|
</button>
|
|
120
124
|
)}
|
|
121
125
|
<button
|
|
@@ -128,7 +132,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
128
132
|
disabled={!selected}
|
|
129
133
|
className="inline-flex items-center justify-center rounded-md bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
130
134
|
>
|
|
131
|
-
Next
|
|
135
|
+
{t('cpq.wizard.next', 'Next')}
|
|
132
136
|
</button>
|
|
133
137
|
</div>
|
|
134
138
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -8,17 +9,20 @@ import type { WizardStepProps } from '../types'
|
|
|
8
9
|
* Implementation deferred until the inventory/subscription domain exists.
|
|
9
10
|
*/
|
|
10
11
|
export function InventorySelectStep({ onBack, onSkip, completedSteps }: WizardStepProps) {
|
|
12
|
+
const t = useT()
|
|
11
13
|
return (
|
|
12
14
|
<div className="rounded-lg border bg-card p-6 text-center space-y-3">
|
|
13
|
-
<h3 className="text-lg font-semibold">Inventory Selection</h3>
|
|
15
|
+
<h3 className="text-lg font-semibold">{t('cpq.wizard.steps.inventorySelect.title', 'Inventory Selection')}</h3>
|
|
14
16
|
<p className="text-sm text-muted-foreground">
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
{t(
|
|
18
|
+
'cpq.wizard.steps.inventorySelect.notImplemented',
|
|
19
|
+
'This step type is not yet implemented. It will allow selecting existing customer inventory items for modification or renewal once the inventory domain is available.',
|
|
20
|
+
)}
|
|
17
21
|
</p>
|
|
18
22
|
<div className="flex gap-2 justify-center pt-2">
|
|
19
23
|
{completedSteps.length > 0 && (
|
|
20
24
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
21
|
-
Back
|
|
25
|
+
{t('cpq.common.back', 'Back')}
|
|
22
26
|
</button>
|
|
23
27
|
)}
|
|
24
28
|
<button
|
|
@@ -26,7 +30,7 @@ export function InventorySelectStep({ onBack, onSkip, completedSteps }: WizardSt
|
|
|
26
30
|
onClick={onSkip}
|
|
27
31
|
className="inline-flex items-center justify-center rounded-md border border-primary px-4 py-2 text-sm font-medium text-primary hover:bg-primary/10"
|
|
28
32
|
>
|
|
29
|
-
Skip
|
|
33
|
+
{t('cpq.wizard.skip', 'Skip')}
|
|
30
34
|
</button>
|
|
31
35
|
</div>
|
|
32
36
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
7
|
import { Tag } from '@open-mercato/ui/primitives/tag'
|
|
@@ -21,6 +22,7 @@ function fmt(n: number, currency = 'USD') {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, quoteId }: WizardStepProps) {
|
|
25
|
+
const t = useT()
|
|
24
26
|
const [lines, setLines] = useState<QuoteLine[]>([])
|
|
25
27
|
const [loading, setLoading] = useState(true)
|
|
26
28
|
|
|
@@ -67,19 +69,23 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
67
69
|
<div className="space-y-4">
|
|
68
70
|
<div className="flex items-center justify-between">
|
|
69
71
|
<span className="text-xs text-muted-foreground">
|
|
70
|
-
{lines.length
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
{lines.length !== 1
|
|
73
|
+
? t('cpq.wizard.steps.itemList.count.other', '{count} items', { count: lines.length })
|
|
74
|
+
: t('cpq.wizard.steps.itemList.count.one', '{count} item', { count: lines.length })}
|
|
75
|
+
{minItems > 0 && ' '}
|
|
76
|
+
{minItems > 0 && t('cpq.wizard.steps.itemList.min', '(min: {min})', { min: minItems })}
|
|
77
|
+
{maxItems < Infinity && ' '}
|
|
78
|
+
{maxItems < Infinity && t('cpq.wizard.steps.itemList.max', '(max: {max})', { max: maxItems })}
|
|
73
79
|
</span>
|
|
74
80
|
{canAddMore && (
|
|
75
81
|
<span className="text-xs text-muted-foreground">
|
|
76
|
-
Use the offering browser to add items to the quote, then return here.
|
|
82
|
+
{t('cpq.wizard.steps.itemList.hint', 'Use the offering browser to add items to the quote, then return here.')}
|
|
77
83
|
</span>
|
|
78
84
|
)}
|
|
79
85
|
</div>
|
|
80
86
|
|
|
81
87
|
{loading ? (
|
|
82
|
-
<div className="text-sm text-muted-foreground">Loading items
|
|
88
|
+
<div className="text-sm text-muted-foreground">{t('cpq.wizard.steps.itemList.loading', 'Loading items...')}</div>
|
|
83
89
|
) : (
|
|
84
90
|
<div className="space-y-2">
|
|
85
91
|
{lines.map((line) => (
|
|
@@ -92,14 +98,14 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
92
98
|
{line.quantity > 1 && <span className="ml-2 text-xs text-muted-foreground">x{line.quantity}</span>}
|
|
93
99
|
{!line.isConfigured && (
|
|
94
100
|
<Tag variant="warning" className="ml-2">
|
|
95
|
-
Needs configuration
|
|
101
|
+
{t('cpq.wizard.steps.itemList.needsConfiguration', 'Needs configuration')}
|
|
96
102
|
</Tag>
|
|
97
103
|
)}
|
|
98
104
|
</div>
|
|
99
105
|
<div className="flex items-center gap-3">
|
|
100
106
|
{showPricing && (
|
|
101
107
|
<span className="text-xs">
|
|
102
|
-
{
|
|
108
|
+
{t('cpq.wizard.linePricing', '{nrc} NRC / {mrc} MRC', { nrc: fmt(line.nrcTotal), mrc: fmt(line.mrcTotal) })}
|
|
103
109
|
</span>
|
|
104
110
|
)}
|
|
105
111
|
<Button
|
|
@@ -108,14 +114,14 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
108
114
|
size="sm"
|
|
109
115
|
onClick={() => handleRemove(line.lineId)}
|
|
110
116
|
>
|
|
111
|
-
Remove
|
|
117
|
+
{t('cpq.wizard.remove', 'Remove')}
|
|
112
118
|
</Button>
|
|
113
119
|
</div>
|
|
114
120
|
</div>
|
|
115
121
|
))}
|
|
116
122
|
{lines.length === 0 && (
|
|
117
123
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
118
|
-
No items added yet.
|
|
124
|
+
{t('cpq.wizard.steps.itemList.empty', 'No items added yet.')}
|
|
119
125
|
</div>
|
|
120
126
|
)}
|
|
121
127
|
</div>
|
|
@@ -124,11 +130,11 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
124
130
|
<div className="flex gap-2 justify-end pt-2">
|
|
125
131
|
{completedSteps.length > 0 && (
|
|
126
132
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
127
|
-
Back
|
|
133
|
+
{t('cpq.common.back', 'Back')}
|
|
128
134
|
</button>
|
|
129
135
|
)}
|
|
130
136
|
<button type="button" onClick={loadLines} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
131
|
-
Refresh
|
|
137
|
+
{t('cpq.wizard.steps.itemList.refresh', 'Refresh')}
|
|
132
138
|
</button>
|
|
133
139
|
<button
|
|
134
140
|
type="button"
|
|
@@ -138,7 +144,7 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
138
144
|
disabled={!canProceed}
|
|
139
145
|
className="inline-flex items-center justify-center rounded-md bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
140
146
|
>
|
|
141
|
-
Next
|
|
147
|
+
{t('cpq.wizard.next', 'Next')}
|
|
142
148
|
</button>
|
|
143
149
|
</div>
|
|
144
150
|
</div>
|