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