@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,38 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server'
|
|
3
|
+
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
4
|
+
import { DepthBadge, PriceWaterfall } from '../PriceWaterfall'
|
|
5
|
+
|
|
6
|
+
const withI18n = (node: React.ReactNode) => <I18nProvider locale="en" dict={{}}>{node}</I18nProvider>
|
|
7
|
+
|
|
8
|
+
const text = (html: string) => html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim()
|
|
9
|
+
|
|
10
|
+
it('renders list, rule, standard, negotiated and offer steps with the reason label', () => {
|
|
11
|
+
const html = renderToStaticMarkup(withI18n(
|
|
12
|
+
<PriceWaterfall
|
|
13
|
+
currency="USD"
|
|
14
|
+
reasonLabels={{ competitive_match: 'Competitive match' }}
|
|
15
|
+
charge={{
|
|
16
|
+
chargeType: 'mrc', unitPrice: 80, totalPrice: 80, listUnitPrice: 100, standardUnitPrice: 90,
|
|
17
|
+
adjustments: [
|
|
18
|
+
{ origin: 'rule', ruleCode: 'VOL', ruleName: 'Volume', ruleType: 'discount_percent', value: 10, unitPriceBefore: 100, unitPriceAfter: 90, delta: -10 },
|
|
19
|
+
{ origin: 'manual', adjustmentId: 'a1', ruleCode: 'a1', ruleName: 'competitive_match', ruleType: 'price_override', value: 80, unitPriceBefore: 90, unitPriceAfter: 80, delta: -10, reasonCode: 'competitive_match' },
|
|
20
|
+
],
|
|
21
|
+
}}
|
|
22
|
+
renderManualActions={(id) => <button>edit {id}</button>}
|
|
23
|
+
/>,
|
|
24
|
+
))
|
|
25
|
+
const content = text(html)
|
|
26
|
+
expect(content).toMatch(/List price \$100\.00/)
|
|
27
|
+
expect(content).toMatch(/Price rule: Volume 10%/)
|
|
28
|
+
expect(content).toMatch(/Standard price \$90\.00/)
|
|
29
|
+
expect(content).toMatch(/Negotiated: Competitive match \$80\.00/)
|
|
30
|
+
expect(content).toMatch(/Offer price \$80\.00/)
|
|
31
|
+
expect(content).toContain('edit a1')
|
|
32
|
+
expect(content.match(/edit/g)).toHaveLength(1)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders a signed depth badge only when prices differ', () => {
|
|
36
|
+
expect(text(renderToStaticMarkup(<DepthBadge before={100} after={80} />))).toBe('−20%')
|
|
37
|
+
expect(renderToStaticMarkup(<DepthBadge before={100} after={100} />)).toBe('')
|
|
38
|
+
})
|
|
@@ -11,10 +11,14 @@ export type UseCpqRowActionsOptions<T extends { id: string }> = {
|
|
|
11
11
|
/** DELETE endpoint that accepts `{ id }` body (e.g. `/api/cpq/product-offerings`). */
|
|
12
12
|
endpoint: string
|
|
13
13
|
/**
|
|
14
|
-
* Singular noun for the entity
|
|
15
|
-
*
|
|
14
|
+
* Singular noun for the entity (e.g. `'offering'`).
|
|
15
|
+
*
|
|
16
|
+
* @deprecated Not used in messages any more: splicing a noun into a
|
|
17
|
+
* sentence cannot produce correct grammar in ES / DE / PL (gender, case).
|
|
18
|
+
* Confirm and flash messages are noun-free and count-based
|
|
19
|
+
* (spec 2026-11-15-cpq-multi-language). Kept for API compatibility.
|
|
16
20
|
*/
|
|
17
|
-
entityName
|
|
21
|
+
entityName?: string
|
|
18
22
|
/**
|
|
19
23
|
* Build the href for the row's edit/detail page (e.g. `(row) =>
|
|
20
24
|
* `/backend/cpq/offerings/${row.id}`).
|
|
@@ -59,7 +63,7 @@ export type CpqRowActionsApi<T extends { id: string }> = {
|
|
|
59
63
|
export function useCpqRowActions<T extends { id: string }>(
|
|
60
64
|
options: UseCpqRowActionsOptions<T>,
|
|
61
65
|
): CpqRowActionsApi<T> {
|
|
62
|
-
const { endpoint,
|
|
66
|
+
const { endpoint, editHref, onReload, canDelete } = options
|
|
63
67
|
const t = useT()
|
|
64
68
|
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
65
69
|
|
|
@@ -79,25 +83,22 @@ export function useCpqRowActions<T extends { id: string }>(
|
|
|
79
83
|
async (row: T) => {
|
|
80
84
|
if (canDelete && !canDelete(row)) return
|
|
81
85
|
const confirmed = await confirm({
|
|
82
|
-
title: t(
|
|
83
|
-
'cpq.rowActions.deleteOne.confirm',
|
|
84
|
-
`Delete this ${entityName}?`,
|
|
85
|
-
),
|
|
86
|
+
title: t('cpq.rowActions.deleteOne.confirm', 'Delete this item?'),
|
|
86
87
|
variant: 'destructive',
|
|
87
88
|
})
|
|
88
89
|
if (!confirmed) return
|
|
89
90
|
const ok = await deleteOne(row.id)
|
|
90
91
|
if (!ok) {
|
|
91
92
|
flash(
|
|
92
|
-
t('cpq.rowActions.deleteOne.error',
|
|
93
|
+
t('cpq.rowActions.deleteOne.error', 'Failed to delete the item'),
|
|
93
94
|
'error',
|
|
94
95
|
)
|
|
95
96
|
return
|
|
96
97
|
}
|
|
97
|
-
flash(t('cpq.rowActions.deleteOne.success',
|
|
98
|
+
flash(t('cpq.rowActions.deleteOne.success', 'Item deleted'), 'success')
|
|
98
99
|
onReload()
|
|
99
100
|
},
|
|
100
|
-
[canDelete, confirm, deleteOne,
|
|
101
|
+
[canDelete, confirm, deleteOne, onReload, t],
|
|
101
102
|
)
|
|
102
103
|
|
|
103
104
|
const bulkDelete = React.useCallback(
|
|
@@ -106,19 +107,13 @@ export function useCpqRowActions<T extends { id: string }>(
|
|
|
106
107
|
const eligible = canDelete ? rows.filter(canDelete) : rows
|
|
107
108
|
if (!eligible.length) {
|
|
108
109
|
flash(
|
|
109
|
-
t(
|
|
110
|
-
'cpq.rowActions.deleteBulk.noEligible',
|
|
111
|
-
`None of the selected ${entityName}s can be deleted in their current state.`,
|
|
112
|
-
),
|
|
110
|
+
t('cpq.rowActions.deleteBulk.noEligible', 'None of the selected items can be deleted in their current state.'),
|
|
113
111
|
'error',
|
|
114
112
|
)
|
|
115
113
|
return { ok: false }
|
|
116
114
|
}
|
|
117
115
|
const confirmed = await confirm({
|
|
118
|
-
title: t(
|
|
119
|
-
'cpq.rowActions.deleteBulk.confirm',
|
|
120
|
-
`Delete ${eligible.length} ${entityName}${eligible.length > 1 ? 's' : ''}?`,
|
|
121
|
-
),
|
|
116
|
+
title: t('cpq.rowActions.deleteBulk.confirm', 'Delete selected items ({count})?', { count: eligible.length }),
|
|
122
117
|
variant: 'destructive',
|
|
123
118
|
})
|
|
124
119
|
if (!confirmed) return { ok: false }
|
|
@@ -131,22 +126,19 @@ export function useCpqRowActions<T extends { id: string }>(
|
|
|
131
126
|
|
|
132
127
|
if (failed > 0) {
|
|
133
128
|
flash(
|
|
134
|
-
t(
|
|
135
|
-
'cpq.rowActions.deleteBulk.partialError',
|
|
136
|
-
`Failed to delete ${failed} ${entityName}${failed > 1 ? 's' : ''}`,
|
|
137
|
-
),
|
|
129
|
+
t('cpq.rowActions.deleteBulk.partialError', 'Failed to delete {count} of the selected items', { count: failed }),
|
|
138
130
|
'error',
|
|
139
131
|
)
|
|
140
132
|
} else {
|
|
141
133
|
flash(
|
|
142
|
-
t('cpq.rowActions.deleteBulk.success',
|
|
134
|
+
t('cpq.rowActions.deleteBulk.success', 'Selected items deleted ({count})', { count: eligible.length }),
|
|
143
135
|
'success',
|
|
144
136
|
)
|
|
145
137
|
}
|
|
146
138
|
onReload()
|
|
147
139
|
return { ok: failed === 0, affectedCount: eligible.length - failed }
|
|
148
140
|
},
|
|
149
|
-
[canDelete, confirm, deleteOne,
|
|
141
|
+
[canDelete, confirm, deleteOne, onReload, t],
|
|
150
142
|
)
|
|
151
143
|
|
|
152
144
|
const buildItems = React.useCallback(
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
5
|
+
|
|
6
|
+
type FeatureCheckResponse = { granted: string[] }
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Resolve which of `features` the current user holds, through the framework's
|
|
10
|
+
* `POST /api/auth/feature-check` endpoint (the same one the component-override
|
|
11
|
+
* provider uses). Returns an empty list until the check completes, so callers
|
|
12
|
+
* hide privileged actions by default.
|
|
13
|
+
*/
|
|
14
|
+
export function useGrantedFeatures(features: readonly string[]): readonly string[] {
|
|
15
|
+
const [granted, setGranted] = React.useState<readonly string[]>([])
|
|
16
|
+
const key = features.join('|')
|
|
17
|
+
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
if (!key) return
|
|
20
|
+
let cancelled = false
|
|
21
|
+
void (async () => {
|
|
22
|
+
const call = await apiCall<FeatureCheckResponse>('/api/auth/feature-check', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: { 'content-type': 'application/json' },
|
|
25
|
+
body: JSON.stringify({ features: key.split('|') }),
|
|
26
|
+
})
|
|
27
|
+
if (!cancelled && call.ok && call.result?.granted) setGranted(call.result.granted)
|
|
28
|
+
})()
|
|
29
|
+
return () => {
|
|
30
|
+
cancelled = true
|
|
31
|
+
}
|
|
32
|
+
}, [key])
|
|
33
|
+
|
|
34
|
+
return granted
|
|
35
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Migration20261110213052 } from '../../migrations/Migration20261110213052'
|
|
2
|
+
|
|
3
|
+
it('generates a rollback that removes the new table before it can be recreated', () => {
|
|
4
|
+
const up: string[] = [], down: string[] = []
|
|
5
|
+
Migration20261110213052.prototype.up.call({ addSql: (sql: string) => up.push(sql) } as never)
|
|
6
|
+
Migration20261110213052.prototype.down.call({ addSql: (sql: string) => down.push(sql) } as never)
|
|
7
|
+
expect(up.join('\n')).toContain('create table "cpq_quote_price_adjustments"')
|
|
8
|
+
expect(down.join('\n')).toContain('drop table if exists "cpq_quote_price_adjustments"')
|
|
9
|
+
expect(up.join('\n')).toContain('add "pricing_revision"')
|
|
10
|
+
expect(down.join('\n')).toContain('drop column "pricing_revision"')
|
|
11
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { cpqPriceAdjustmentInputSchema, cpqPriceAdjustmentPatchSchema } from '../price-adjustments'
|
|
2
|
+
const valid = {
|
|
3
|
+
level: 'charge', quoteLineId: '11111111-1111-4111-8111-111111111111', chargeCode: 'setup',
|
|
4
|
+
adjustmentType: 'price_override', value: 80, currencyCode: 'USD', reasonCode: 'competitive_match',
|
|
5
|
+
}
|
|
6
|
+
it('allows a zero override and percentage discounts with no currency', () => {
|
|
7
|
+
expect(cpqPriceAdjustmentInputSchema.parse({ ...valid, value: 0 }).value).toBe(0)
|
|
8
|
+
expect(cpqPriceAdjustmentInputSchema.parse({ ...valid, adjustmentType: 'discount_percent', value: 100, currencyCode: undefined }).value).toBe(100)
|
|
9
|
+
})
|
|
10
|
+
it.each([
|
|
11
|
+
{ level: 'quote' }, { quoteLineId: 'bad' }, { chargeCode: null }, { value: -1 }, { value: Infinity }, { value: 1.23456 }, { value: 100_000_000_000_000 },
|
|
12
|
+
{ adjustmentType: 'discount_percent', value: 101 }, { adjustmentType: 'discount_percent', value: 0 },
|
|
13
|
+
{ adjustmentType: 'discount_absolute', value: 0 }, { currencyCode: null }, { currencyCode: 'usd' },
|
|
14
|
+
{ reasonCode: 'other', reasonNote: '' }, { reasonNote: 'x'.repeat(501) },
|
|
15
|
+
{ fromMonth: 2 }, { toMonth: 3 }, { windowKind: 'absolute' },
|
|
16
|
+
{ level: 'line', chargeCode: undefined, adjustmentType: 'price_override' }, { chargeTypeFilter: 'mrc' },
|
|
17
|
+
])('rejects malformed or unsupported adjustments: %j', (patch) => {
|
|
18
|
+
expect(cpqPriceAdjustmentInputSchema.safeParse({ ...valid, ...patch }).success).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
it('accepts filtered line percentages and an explanation for other', () => {
|
|
21
|
+
expect(cpqPriceAdjustmentInputSchema.safeParse({ ...valid, level: 'line', chargeCode: undefined, chargeTypeFilter: 'mrc', adjustmentType: 'discount_percent', value: 20, reasonCode: 'other', reasonNote: 'One-time negotiation' }).success).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
it('never accepts target changes through PATCH; merged patches are validated by the full schema', () => {
|
|
24
|
+
expect(cpqPriceAdjustmentPatchSchema.safeParse({ quoteLineId: valid.quoteLineId }).success).toBe(false)
|
|
25
|
+
expect(cpqPriceAdjustmentPatchSchema.parse({ reasonNote: 'New note' })).toEqual({ reasonNote: 'New note' })
|
|
26
|
+
})
|
|
@@ -631,6 +631,7 @@ export class CpqPriceRule {
|
|
|
631
631
|
export class CpqQuoteConfiguration {
|
|
632
632
|
[OptionalProps]?:
|
|
633
633
|
| 'cpqStatus'
|
|
634
|
+
| 'pricingRevision'
|
|
634
635
|
| 'version'
|
|
635
636
|
| 'currencyCode'
|
|
636
637
|
| 'quoteType'
|
|
@@ -653,6 +654,9 @@ export class CpqQuoteConfiguration {
|
|
|
653
654
|
@Property({ name: 'customer_id', type: 'uuid' })
|
|
654
655
|
customerId!: string
|
|
655
656
|
|
|
657
|
+
@Property({ name: 'pricing_revision', type: 'integer', default: 0 })
|
|
658
|
+
pricingRevision: number = 0
|
|
659
|
+
|
|
656
660
|
@Property({ name: 'cpq_status', type: 'text', default: 'new' })
|
|
657
661
|
cpqStatus: string = 'new'
|
|
658
662
|
|
|
@@ -1475,3 +1479,82 @@ export class CpqSubscriptionChangeLog {
|
|
|
1475
1479
|
@Property({ name: 'deleted_at', type: Date, nullable: true })
|
|
1476
1480
|
deletedAt?: Date | null
|
|
1477
1481
|
}
|
|
1482
|
+
|
|
1483
|
+
|
|
1484
|
+
// ─── Deal-specific adjustments, separate from catalog price rules ───
|
|
1485
|
+
@Entity({ tableName: 'cpq_quote_price_adjustments' })
|
|
1486
|
+
@Index({ name: 'cpq_qpa_quote_config_idx', properties: ['organizationId', 'tenantId', 'quoteConfigurationId'] })
|
|
1487
|
+
@Index({ name: 'cpq_qpa_quote_line_idx', properties: ['organizationId', 'tenantId', 'quoteLineId'] })
|
|
1488
|
+
@Index({ name: 'cpq_qpa_quote_idx', properties: ['organizationId', 'tenantId', 'quoteId'] })
|
|
1489
|
+
export class CpqQuotePriceAdjustment {
|
|
1490
|
+
[OptionalProps]?: 'windowKind' | 'fromMonth' | 'toMonth' | 'isActive' | 'createdAt' | 'updatedAt' | 'deletedAt'
|
|
1491
|
+
|
|
1492
|
+
@PrimaryKey({ type: 'uuid', defaultRaw: 'gen_random_uuid()' })
|
|
1493
|
+
id!: string
|
|
1494
|
+
|
|
1495
|
+
@Property({ name: 'organization_id', type: 'uuid' })
|
|
1496
|
+
organizationId!: string
|
|
1497
|
+
|
|
1498
|
+
@Property({ name: 'tenant_id', type: 'uuid' })
|
|
1499
|
+
tenantId!: string
|
|
1500
|
+
|
|
1501
|
+
@Property({ name: 'quote_configuration_id', type: 'uuid' })
|
|
1502
|
+
quoteConfigurationId!: string
|
|
1503
|
+
|
|
1504
|
+
@Property({ name: 'quote_id', type: 'uuid' })
|
|
1505
|
+
quoteId!: string
|
|
1506
|
+
|
|
1507
|
+
@Property({ name: 'quote_line_id', type: 'uuid' })
|
|
1508
|
+
quoteLineId!: string
|
|
1509
|
+
|
|
1510
|
+
@Property({ type: 'text' })
|
|
1511
|
+
level!: 'charge' | 'line'
|
|
1512
|
+
|
|
1513
|
+
@Property({ name: 'charge_code', type: 'text', nullable: true })
|
|
1514
|
+
chargeCode: string | null = null
|
|
1515
|
+
|
|
1516
|
+
@Property({ name: 'charge_type_filter', type: 'text', nullable: true })
|
|
1517
|
+
chargeTypeFilter: 'nrc' | 'mrc' | null = null
|
|
1518
|
+
|
|
1519
|
+
@Property({ name: 'adjustment_type', type: 'text' })
|
|
1520
|
+
adjustmentType!: 'discount_percent' | 'discount_absolute' | 'price_override'
|
|
1521
|
+
|
|
1522
|
+
@Property({ type: 'numeric', columnType: 'numeric(18, 4)' })
|
|
1523
|
+
value!: string
|
|
1524
|
+
|
|
1525
|
+
@Property({ name: 'currency_code', type: 'text', nullable: true })
|
|
1526
|
+
currencyCode: string | null = null
|
|
1527
|
+
|
|
1528
|
+
@Property({ name: 'window_kind', type: 'text', default: 'relative' })
|
|
1529
|
+
windowKind: string = 'relative'
|
|
1530
|
+
|
|
1531
|
+
@Property({ name: 'from_month', type: 'integer', default: 1 })
|
|
1532
|
+
fromMonth: number = 1
|
|
1533
|
+
|
|
1534
|
+
@Property({ name: 'to_month', type: 'integer', nullable: true })
|
|
1535
|
+
toMonth: number | null = null
|
|
1536
|
+
|
|
1537
|
+
@Property({ name: 'reason_code', type: 'text' })
|
|
1538
|
+
reasonCode!: string
|
|
1539
|
+
|
|
1540
|
+
@Property({ name: 'reason_note', type: 'text', nullable: true })
|
|
1541
|
+
reasonNote: string | null = null
|
|
1542
|
+
|
|
1543
|
+
@Property({ name: 'created_by_user_id', type: 'uuid' })
|
|
1544
|
+
createdByUserId!: string
|
|
1545
|
+
|
|
1546
|
+
@Property({ name: 'updated_by_user_id', type: 'uuid', nullable: true })
|
|
1547
|
+
updatedByUserId: string | null = null
|
|
1548
|
+
|
|
1549
|
+
@Property({ name: 'is_active', type: 'boolean', default: true })
|
|
1550
|
+
isActive: boolean = true
|
|
1551
|
+
|
|
1552
|
+
@Property({ name: 'created_at', type: Date, onCreate: () => new Date() })
|
|
1553
|
+
createdAt: Date = new Date()
|
|
1554
|
+
|
|
1555
|
+
@Property({ name: 'updated_at', type: Date, onUpdate: () => new Date() })
|
|
1556
|
+
updatedAt: Date = new Date()
|
|
1557
|
+
|
|
1558
|
+
@Property({ name: 'deleted_at', type: Date, nullable: true })
|
|
1559
|
+
deletedAt: Date | null = null
|
|
1560
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const adjustmentTypeSchema = z.enum(['discount_percent', 'discount_absolute', 'price_override'])
|
|
4
|
+
const adjustmentFields = {
|
|
5
|
+
adjustmentType: adjustmentTypeSchema,
|
|
6
|
+
// Match numeric(18,4), so saving never silently rounds the previewed input.
|
|
7
|
+
value: z.number().finite().min(0).max(99_999_999_999_999).multipleOf(0.0001),
|
|
8
|
+
currencyCode: z.string().regex(/^[A-Z]{3}$/).nullish(),
|
|
9
|
+
chargeTypeFilter: z.enum(['nrc', 'mrc']).nullish(),
|
|
10
|
+
reasonCode: z.string().trim().min(1).max(100),
|
|
11
|
+
reasonNote: z.string().trim().max(500).nullish(),
|
|
12
|
+
expectedRevision: z.number().int().min(0).optional(),
|
|
13
|
+
}
|
|
14
|
+
export const cpqPriceAdjustmentInputSchema = z.object({
|
|
15
|
+
...adjustmentFields,
|
|
16
|
+
level: z.enum(['charge', 'line']),
|
|
17
|
+
quoteLineId: z.string().uuid(),
|
|
18
|
+
chargeCode: z.string().trim().min(1).max(255).nullish(),
|
|
19
|
+
windowKind: z.literal('relative').optional(),
|
|
20
|
+
fromMonth: z.literal(1).optional(),
|
|
21
|
+
toMonth: z.null().optional(),
|
|
22
|
+
}).strict().superRefine((input, ctx) => {
|
|
23
|
+
const error = (path: string, message: string) => ctx.addIssue({ code: 'custom', path: [path], message })
|
|
24
|
+
if (input.level === 'charge' && !input.chargeCode) error('chargeCode', 'TARGET_NOT_FOUND')
|
|
25
|
+
if (input.level === 'charge' && input.chargeTypeFilter) error('chargeTypeFilter', 'TYPE_NOT_ALLOWED')
|
|
26
|
+
if (input.level === 'line' && input.chargeCode) error('chargeCode', 'TYPE_NOT_ALLOWED')
|
|
27
|
+
if (input.level === 'line' && input.adjustmentType !== 'discount_percent') error('adjustmentType', 'TYPE_NOT_ALLOWED')
|
|
28
|
+
if (input.adjustmentType === 'discount_percent' && (input.value <= 0 || input.value > 100)) error('value', 'INVALID_PERCENT')
|
|
29
|
+
if (input.adjustmentType === 'discount_absolute' && input.value <= 0) error('value', 'INVALID_AMOUNT')
|
|
30
|
+
if (input.adjustmentType !== 'discount_percent' && !input.currencyCode) error('currencyCode', 'CURRENCY_REQUIRED')
|
|
31
|
+
if (input.reasonCode === 'other' && !input.reasonNote) error('reasonNote', 'REASON_NOTE_REQUIRED')
|
|
32
|
+
})
|
|
33
|
+
export const cpqPriceAdjustmentPatchSchema = z.object(adjustmentFields).partial().strict()
|
|
34
|
+
export type PriceAdjustmentInput = z.infer<typeof cpqPriceAdjustmentInputSchema>
|
|
35
|
+
export type PriceAdjustmentPatch = z.infer<typeof cpqPriceAdjustmentPatchSchema>
|
|
36
|
+
|
|
37
|
+
/** Pure pricing representation; persistence metadata stays outside the resolver. */
|
|
38
|
+
export const resolvedPriceAdjustmentSchema = z.object({
|
|
39
|
+
...cpqPriceAdjustmentInputSchema.shape,
|
|
40
|
+
id: z.string().uuid(),
|
|
41
|
+
createdByUserId: z.string().uuid().optional(),
|
|
42
|
+
})
|
|
43
|
+
export type PriceAdjustment = z.infer<typeof resolvedPriceAdjustmentSchema>
|
|
44
|
+
export const adjustmentErrorSchema = z.object({
|
|
45
|
+
adjustmentId: z.string(),
|
|
46
|
+
quoteLineId: z.string(),
|
|
47
|
+
chargeCode: z.string().optional(),
|
|
48
|
+
code: z.enum(['ADJUSTMENT_TARGET_MISSING', 'TARGET_AMBIGUOUS', 'ABOVE_STANDARD', 'CURRENCY_MISMATCH', 'TYPE_NOT_ALLOWED', 'LINE_NOT_ELIGIBLE', 'ADJUSTMENT_OVERLAP']),
|
|
49
|
+
})
|
|
50
|
+
export type AdjustmentError = z.infer<typeof adjustmentErrorSchema>
|
|
51
|
+
|
|
52
|
+
export const cpqPriceAdjustmentPreviewSchema = z.object({
|
|
53
|
+
draft: cpqPriceAdjustmentInputSchema,
|
|
54
|
+
replaceAdjustmentId: z.string().uuid().optional(),
|
|
55
|
+
}).strict()
|
|
56
|
+
|
|
57
|
+
export const ADJUSTMENT_TOGGLE = 'cpq.manual_price_adjustments'
|
|
58
|
+
export const REASON_DICTIONARY = 'cpq.price_adjustment_reason'
|
|
59
|
+
export const DEFAULT_ADJUSTMENT_REASONS = [
|
|
60
|
+
{ value: 'competitive_match', label: 'Competitive match' },
|
|
61
|
+
{ value: 'volume_commitment', label: 'Volume commitment' },
|
|
62
|
+
{ value: 'loyalty', label: 'Loyalty' },
|
|
63
|
+
{ value: 'promotion', label: 'Promotion' },
|
|
64
|
+
{ value: 'strategic_deal', label: 'Strategic deal' },
|
|
65
|
+
{ value: 'price_correction', label: 'Price correction' },
|
|
66
|
+
{ value: 'other', label: 'Other' },
|
|
67
|
+
] as const
|
|
@@ -378,6 +378,7 @@ const cpqPricePreviewItemSchema = z.object({
|
|
|
378
378
|
)
|
|
379
379
|
|
|
380
380
|
export const cpqPricePreviewSchema = z.object({
|
|
381
|
+
includeWaterfall: z.boolean().optional().default(false),
|
|
381
382
|
primaryItem: cpqPricePreviewItemSchema.nullish(),
|
|
382
383
|
childItems: z.array(cpqPricePreviewItemSchema),
|
|
383
384
|
currencyCode: z.string().min(1).optional(),
|
|
@@ -691,3 +692,5 @@ export const cpqConvertQuoteToOrderSchema = z.object({
|
|
|
691
692
|
export const cpqOrderStatusTransitionSchema = z.object({
|
|
692
693
|
targetStatus: z.string().min(1),
|
|
693
694
|
})
|
|
695
|
+
|
|
696
|
+
export { cpqPriceAdjustmentInputSchema, cpqPriceAdjustmentPatchSchema } from './price-adjustments'
|
package/src/modules/cpq/di.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { DefaultCpqQuotingService } from './services/cpqQuotingService'
|
|
|
9
9
|
import { DefaultCpqWizardService } from './services/cpqWizardService'
|
|
10
10
|
import { DefaultCpqInventoryService } from './services/cpqInventoryService'
|
|
11
11
|
import { DefaultCpqOrderService } from './services/cpqOrderService'
|
|
12
|
+
import { CpqAdjustmentService } from './services/priceAdjustments/adjustmentService'
|
|
12
13
|
import { DefaultCpqBundleService } from './services/cpqBundleService'
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -20,6 +21,11 @@ import { DefaultCpqBundleService } from './services/cpqBundleService'
|
|
|
20
21
|
*/
|
|
21
22
|
export function register(container: AppContainer) {
|
|
22
23
|
container.register({
|
|
24
|
+
cpqAdjustmentService: asFunction(() => new CpqAdjustmentService({
|
|
25
|
+
em: container.resolve('em'),
|
|
26
|
+
quotingService: container.resolve('cpqQuotingService'),
|
|
27
|
+
featureTogglesService: container.resolve('featureTogglesService'),
|
|
28
|
+
})).scoped(),
|
|
23
29
|
cpqPricingService: asFunction(({ em }: { em: EntityManager }) => {
|
|
24
30
|
return new DefaultCpqPricingService(em)
|
|
25
31
|
}).scoped().proxy(),
|
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
2
|
+
import type { EventPayloadSchemaField } from '@open-mercato/shared/modules/events'
|
|
3
|
+
|
|
4
|
+
/** Fields shared by both quote pricing signals (see `quotePricingEvents.ts`). */
|
|
5
|
+
const quotePricingSnapshotFields: EventPayloadSchemaField[] = [
|
|
6
|
+
{ path: 'tenantId', type: 'text' },
|
|
7
|
+
{ path: 'organizationId', type: 'text' },
|
|
8
|
+
{ path: 'quoteId', type: 'text', label: 'CPQ quote id' },
|
|
9
|
+
{ path: 'quoteType', type: 'select', label: 'Quote type' },
|
|
10
|
+
{ path: 'cpqStatus', type: 'text', label: 'Status' },
|
|
11
|
+
{ path: 'currencyCode', type: 'text' },
|
|
12
|
+
{ path: 'pricingRevision', type: 'number' },
|
|
13
|
+
{ path: 'snapshotHash', type: 'text' },
|
|
14
|
+
{ path: 'dealValue.list', type: 'number', label: 'Deal value (list)' },
|
|
15
|
+
{ path: 'dealValue.standard', type: 'number', label: 'Deal value (standard)' },
|
|
16
|
+
{ path: 'dealValue.offer', type: 'number', label: 'Deal value (offer)' },
|
|
17
|
+
{ path: 'discountDepth.rule', type: 'number', label: 'Rule discount depth', optional: true },
|
|
18
|
+
{ path: 'discountDepth.manual', type: 'number', label: 'Manual discount depth', optional: true },
|
|
19
|
+
{ path: 'discountDepth.total', type: 'number', label: 'Total discount depth', optional: true },
|
|
20
|
+
{ path: 'discountDepth.maxLineManual', type: 'number', label: 'Max line manual discount depth', optional: true },
|
|
21
|
+
{ path: 'hasTimeBasedAdjustments', type: 'boolean' },
|
|
22
|
+
{ path: 'occurredAt', type: 'date' },
|
|
23
|
+
]
|
|
2
24
|
|
|
3
25
|
/**
|
|
4
26
|
* CPQ Module Events.
|
|
@@ -8,8 +30,9 @@ import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
|
8
30
|
* modules (billing, notifications) subscribe to these to react to
|
|
9
31
|
* subscription mutations driven by CPQ orders.
|
|
10
32
|
*
|
|
11
|
-
* Emission rule: events fire AFTER
|
|
12
|
-
* `cpqOrderService.activateOrder` is the sole emitter
|
|
33
|
+
* Emission rule: events fire AFTER their transaction commits.
|
|
34
|
+
* Subscription events: `cpqOrderService.activateOrder` is the sole emitter.
|
|
35
|
+
* Quote events: `withQuotePricingWrite` (plus `cloneQuote` for the copy).
|
|
13
36
|
*/
|
|
14
37
|
const events = [
|
|
15
38
|
{
|
|
@@ -54,6 +77,34 @@ const events = [
|
|
|
54
77
|
category: 'lifecycle',
|
|
55
78
|
clientBroadcast: true,
|
|
56
79
|
},
|
|
80
|
+
// Quote pricing signals (manual price adjustments). Emitted after the quote
|
|
81
|
+
// write commits by `withQuotePricingWrite`; payload carries the pricing
|
|
82
|
+
// snapshot (deal value, discount depth, snapshot hash) so project workflows
|
|
83
|
+
// can route approvals without CPQ code.
|
|
84
|
+
{
|
|
85
|
+
id: 'cpq.quote.pricing_changed',
|
|
86
|
+
label: 'Quote Pricing Changed',
|
|
87
|
+
entity: 'quote',
|
|
88
|
+
category: 'custom',
|
|
89
|
+
clientBroadcast: true,
|
|
90
|
+
payloadSchema: { fields: [...quotePricingSnapshotFields, { path: 'cause', type: 'select' }] },
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'cpq.quote.status_changed',
|
|
94
|
+
label: 'Quote Status Changed',
|
|
95
|
+
entity: 'quote',
|
|
96
|
+
category: 'lifecycle',
|
|
97
|
+
clientBroadcast: true,
|
|
98
|
+
payloadSchema: {
|
|
99
|
+
fields: [
|
|
100
|
+
...quotePricingSnapshotFields,
|
|
101
|
+
{ path: 'fromStatus', type: 'text' },
|
|
102
|
+
{ path: 'toStatus', type: 'text' },
|
|
103
|
+
{ path: 'cause', type: 'select' },
|
|
104
|
+
{ path: 'actorUserId', type: 'text', optional: true },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
57
108
|
] as const
|
|
58
109
|
|
|
59
110
|
export const eventsConfig = createModuleEvents({
|