@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
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
var _a, _b;
|
|
2
|
+
import { cloneQuoteAdjustments, removeQuoteLineTree } from "./priceAdjustments/quoteAdjustmentLifecycle.js";
|
|
3
|
+
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
4
|
+
import { loadQuoteAdjustments } from "./priceAdjustments/loadQuoteAdjustments.js";
|
|
5
|
+
import { resolveManualAdjustments } from "./priceAdjustments/resolveManualAdjustments.js";
|
|
6
|
+
import { withChargeWaterfall } from "./priceAdjustments/chargeWaterfall.js";
|
|
7
|
+
import { withQuotePricingWrite } from "./priceAdjustments/quotePricingWrite.js";
|
|
8
|
+
import { emitQuoteWriteEvents } from "./priceAdjustments/quotePricingEvents.js";
|
|
9
|
+
import { computeWaterfallSummary } from "./priceAdjustments/pricingSnapshot.js";
|
|
2
10
|
import { SalesQuote, SalesQuoteLine } from "@open-mercato/core/modules/sales/data/entities";
|
|
3
11
|
import { CustomerEntity } from "@open-mercato/core/modules/customers/data/entities";
|
|
4
12
|
import {
|
|
@@ -17,6 +25,17 @@ import {
|
|
|
17
25
|
ARC_QUOTE_TYPES,
|
|
18
26
|
getBaseCurrencyCode
|
|
19
27
|
} from "./types.js";
|
|
28
|
+
import {
|
|
29
|
+
ARC_PRICE_FROZEN_KEY,
|
|
30
|
+
ARC_REPRICED_KEY,
|
|
31
|
+
arcPricingInputsChanged,
|
|
32
|
+
buildArcMirrorConfiguration,
|
|
33
|
+
isFrozenAmendMirrorLine,
|
|
34
|
+
isArcMirrorLine,
|
|
35
|
+
stripArcMarkers,
|
|
36
|
+
stripQuoteOnlyArcMarkers
|
|
37
|
+
} from "./arcPriceFreeze.js";
|
|
38
|
+
import { isArcCancelledLine, planArcLineRemoval, planArcLineRestore, removalCancelsItem } from "./arcLineRemoval.js";
|
|
20
39
|
class DefaultCpqQuotingService {
|
|
21
40
|
constructor(deps) {
|
|
22
41
|
this.em = deps.em;
|
|
@@ -108,12 +127,26 @@ class DefaultCpqQuotingService {
|
|
|
108
127
|
}
|
|
109
128
|
// ─── Add Quote Item ────────────────────────────────────────────
|
|
110
129
|
async addQuoteItem(quoteId, item, scope) {
|
|
111
|
-
|
|
112
|
-
|
|
130
|
+
return this.writeQuote(quoteId, scope, (em, quote) => this.addQuoteItemInTransaction(quoteId, item, scope, em, quote), void 0, void 0, LINE_EVENTS);
|
|
131
|
+
}
|
|
132
|
+
async addQuoteItemInTransaction(quoteId, item, scope, em, cpqConfig) {
|
|
133
|
+
item = { ...item, configuration: stripArcMarkers(item.configuration) };
|
|
113
134
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
114
135
|
if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
115
136
|
throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
|
|
116
137
|
}
|
|
138
|
+
if (item.parentLineId && cpqConfig.quoteType && cpqConfig.quoteType !== "new") {
|
|
139
|
+
const parent = await em.findOne(CpqQuoteLineConfiguration, {
|
|
140
|
+
quoteLineId: item.parentLineId,
|
|
141
|
+
quoteConfigurationId: cpqConfig.id,
|
|
142
|
+
organizationId: scope.organizationId,
|
|
143
|
+
tenantId: scope.tenantId,
|
|
144
|
+
deletedAt: null
|
|
145
|
+
});
|
|
146
|
+
if (parent && isArcCancelledLine(parent, cpqConfig.quoteType)) {
|
|
147
|
+
throw new QuotingError(409, "Cannot add a component to a subscription item marked for removal");
|
|
148
|
+
}
|
|
149
|
+
}
|
|
117
150
|
const { offering, spec } = await this.resolveOffering(em, item, scope);
|
|
118
151
|
const mergedConfig = {
|
|
119
152
|
...cpqConfig.quoteContext ?? {},
|
|
@@ -224,8 +257,10 @@ class DefaultCpqQuotingService {
|
|
|
224
257
|
}
|
|
225
258
|
// ─── Update Quote Item ─────────────────────────────────────────
|
|
226
259
|
async updateQuoteItem(quoteId, lineId, update, scope) {
|
|
227
|
-
|
|
228
|
-
|
|
260
|
+
return this.writeQuote(quoteId, scope, (em, quote) => this.updateQuoteItemInTransaction(quoteId, lineId, update, scope, em, quote), void 0, void 0, LINE_EVENTS);
|
|
261
|
+
}
|
|
262
|
+
async updateQuoteItemInTransaction(quoteId, lineId, update, scope, em, cpqConfig) {
|
|
263
|
+
update = { ...update, configuration: stripArcMarkers(update.configuration) };
|
|
229
264
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
230
265
|
if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
231
266
|
throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
|
|
@@ -238,6 +273,9 @@ class DefaultCpqQuotingService {
|
|
|
238
273
|
deletedAt: null
|
|
239
274
|
});
|
|
240
275
|
if (!lineConfig) throw new QuotingError(404, "Quote line not found");
|
|
276
|
+
if (isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
|
|
277
|
+
throw new QuotingError(409, "This subscription item is marked for removal \u2014 restore it before changing it");
|
|
278
|
+
}
|
|
241
279
|
const quoteLine = await em.findOne(SalesQuoteLine, {
|
|
242
280
|
id: lineId,
|
|
243
281
|
organizationId: scope.organizationId,
|
|
@@ -279,12 +317,27 @@ class DefaultCpqQuotingService {
|
|
|
279
317
|
if (existingCfg?._arcMirroredName !== void 0) {
|
|
280
318
|
arcMarkers._arcMirroredName = existingCfg._arcMirroredName;
|
|
281
319
|
}
|
|
320
|
+
const wasFrozen = isFrozenAmendMirrorLine(existingCfg, cpqConfig.quoteType);
|
|
321
|
+
const pricingInputsChanged = wasFrozen && arcPricingInputsChanged(
|
|
322
|
+
{
|
|
323
|
+
configuration: existingCfg,
|
|
324
|
+
quantity: lineConfig.quantity,
|
|
325
|
+
termMonths: lineConfig.termMonths,
|
|
326
|
+
startDate: lineConfig.startDate,
|
|
327
|
+
endDate: lineConfig.endDate,
|
|
328
|
+
usageEstimates: lineConfig.usageEstimates
|
|
329
|
+
},
|
|
330
|
+
update
|
|
331
|
+
);
|
|
332
|
+
const staysFrozen = wasFrozen && !pricingInputsChanged;
|
|
333
|
+
if (staysFrozen) arcMarkers[ARC_PRICE_FROZEN_KEY] = true;
|
|
334
|
+
if (pricingInputsChanged || existingCfg?.[ARC_REPRICED_KEY] === true) arcMarkers[ARC_REPRICED_KEY] = true;
|
|
282
335
|
update.configuration = { ...update.configuration, ...arcMarkers };
|
|
283
336
|
mergedConfig._arcMirroredFromItemId = arcMarkers._arcMirroredFromItemId;
|
|
284
337
|
mergedConfig._arcMirroredName = arcMarkers._arcMirroredName;
|
|
285
338
|
const isArcMirrored = !!arcMarkers._arcMirroredFromItemId;
|
|
286
339
|
let charges;
|
|
287
|
-
if (lineConfig.offeringId || lineConfig.productId) {
|
|
340
|
+
if ((lineConfig.offeringId || lineConfig.productId) && !staysFrozen) {
|
|
288
341
|
charges = await this.pricingService.resolveProductCharges({
|
|
289
342
|
offeringId: lineConfig.offeringId ?? void 0,
|
|
290
343
|
productId: lineConfig.productId ?? void 0,
|
|
@@ -330,8 +383,9 @@ class DefaultCpqQuotingService {
|
|
|
330
383
|
}
|
|
331
384
|
// ─── Remove Quote Item ─────────────────────────────────────────
|
|
332
385
|
async removeQuoteItem(quoteId, lineId, scope) {
|
|
333
|
-
|
|
334
|
-
|
|
386
|
+
return this.writeQuote(quoteId, scope, (em, quote) => this.removeQuoteItemInTransaction(quoteId, lineId, scope, em, quote), void 0, void 0, LINE_EVENTS);
|
|
387
|
+
}
|
|
388
|
+
async removeQuoteItemInTransaction(quoteId, lineId, scope, em, cpqConfig) {
|
|
335
389
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
336
390
|
if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
337
391
|
throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
|
|
@@ -344,31 +398,78 @@ class DefaultCpqQuotingService {
|
|
|
344
398
|
deletedAt: null
|
|
345
399
|
});
|
|
346
400
|
if (!lineConfig) throw new QuotingError(404, "Quote line not found");
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
401
|
+
if (removalCancelsItem(lineConfig, cpqConfig.quoteType)) {
|
|
402
|
+
if (lineConfig.action === "cancel") {
|
|
403
|
+
throw new QuotingError(409, "This subscription item is already marked for removal");
|
|
404
|
+
}
|
|
405
|
+
const plan = planArcLineRemoval(lineConfig, await loadLines(em, cpqConfig.id, scope), cpqConfig.quoteType);
|
|
406
|
+
for (const root of plan.removeRoots) await removeQuoteLineTree(em, cpqConfig.id, root, scope);
|
|
407
|
+
await withAtomicFlush(em, [() => {
|
|
408
|
+
for (const line of plan.cancel) {
|
|
409
|
+
line.action = "cancel";
|
|
410
|
+
line.charges = [];
|
|
411
|
+
line.nrcTotal = "0";
|
|
412
|
+
line.mrcTotal = "0";
|
|
413
|
+
line.isConfigured = true;
|
|
414
|
+
line.validationErrors = null;
|
|
415
|
+
}
|
|
416
|
+
}]);
|
|
417
|
+
} else {
|
|
418
|
+
await removeQuoteLineTree(em, cpqConfig.id, lineConfig, scope);
|
|
419
|
+
}
|
|
420
|
+
return this.recalculateInternal(em, cpqConfig, salesQuote, scope, true);
|
|
421
|
+
}
|
|
422
|
+
// ─── Restore a removed subscription item (Amend / Renew) ───────
|
|
423
|
+
async restoreQuoteItem(quoteId, lineId, scope) {
|
|
424
|
+
return this.writeQuote(quoteId, scope, (em, quote) => this.restoreQuoteItemInTransaction(lineId, scope, em, quote), void 0, void 0, LINE_EVENTS);
|
|
425
|
+
}
|
|
426
|
+
async restoreQuoteItemInTransaction(lineId, scope, em, cpqConfig) {
|
|
427
|
+
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
428
|
+
if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
429
|
+
throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
|
|
430
|
+
}
|
|
431
|
+
const lines = await loadLines(em, cpqConfig.id, scope);
|
|
432
|
+
const lineConfig = lines.find((line) => line.quoteLineId === lineId);
|
|
433
|
+
if (!lineConfig) throw new QuotingError(404, "Quote line not found");
|
|
434
|
+
if (!isArcCancelledLine(lineConfig, cpqConfig.quoteType) || !isArcMirrorLine(lineConfig.configuration, cpqConfig.quoteType)) {
|
|
435
|
+
throw new QuotingError(409, "Only subscription items marked for removal can be restored");
|
|
436
|
+
}
|
|
437
|
+
const plan = planArcLineRestore(lineConfig, lines, cpqConfig.quoteType);
|
|
438
|
+
if (!plan.ok) throw new QuotingError(409, "Restore the parent item first");
|
|
439
|
+
const itemIdOf = (line) => line.sourceSubscriptionItemId ?? line.configuration?._arcMirroredFromItemId ?? null;
|
|
440
|
+
const itemIds = plan.restore.map(itemIdOf).filter((id) => !!id);
|
|
441
|
+
const items = itemIds.length ? await em.find(CpqInventorySubscriptionItem, { id: { $in: itemIds }, organizationId: scope.organizationId, tenantId: scope.tenantId, deletedAt: null }) : [];
|
|
442
|
+
const itemById = new Map(items.map((item) => [item.id, item]));
|
|
443
|
+
const salesLines = await em.find(SalesQuoteLine, {
|
|
444
|
+
id: { $in: plan.restore.map((line) => line.quoteLineId) },
|
|
352
445
|
organizationId: scope.organizationId,
|
|
353
446
|
tenantId: scope.tenantId,
|
|
354
447
|
deletedAt: null
|
|
355
448
|
});
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
const salesLine = await em.findOne(SalesQuoteLine, {
|
|
366
|
-
id: lineId,
|
|
367
|
-
organizationId: scope.organizationId,
|
|
368
|
-
tenantId: scope.tenantId
|
|
449
|
+
const salesLineById = new Map(salesLines.map((line) => [line.id, line]));
|
|
450
|
+
const restores = plan.restore.map((line) => {
|
|
451
|
+
const itemId = itemIdOf(line);
|
|
452
|
+
const item = itemId ? itemById.get(itemId) : void 0;
|
|
453
|
+
if (!item || ["terminated", "expired", "superseded"].includes(item.status)) {
|
|
454
|
+
throw new QuotingError(409, "The subscription item is no longer active and cannot be restored");
|
|
455
|
+
}
|
|
456
|
+
return { line, item };
|
|
369
457
|
});
|
|
370
|
-
|
|
371
|
-
await em
|
|
458
|
+
const arcType = cpqConfig.quoteType;
|
|
459
|
+
await withAtomicFlush(em, [() => {
|
|
460
|
+
for (const { line, item } of restores) {
|
|
461
|
+
line.action = "modify";
|
|
462
|
+
line.configuration = buildArcMirrorConfiguration(item.configuration, item, arcType);
|
|
463
|
+
line.charges = pickArcMirrorCharges(item, cpqConfig.currencyCode);
|
|
464
|
+
line.quantity = item.quantity;
|
|
465
|
+
line.nrcTotal = String(item.nrcAmount ?? 0);
|
|
466
|
+
line.mrcTotal = String(item.mrcAmount);
|
|
467
|
+
line.isConfigured = true;
|
|
468
|
+
line.validationErrors = null;
|
|
469
|
+
const salesLine = salesLineById.get(line.quoteLineId);
|
|
470
|
+
if (salesLine) salesLine.quantity = String(item.quantity);
|
|
471
|
+
}
|
|
472
|
+
}]);
|
|
372
473
|
return this.recalculateInternal(em, cpqConfig, salesQuote, scope, true);
|
|
373
474
|
}
|
|
374
475
|
// ─── Get Quote (read from DB, no recalculation) ────────────────
|
|
@@ -386,15 +487,42 @@ class DefaultCpqQuotingService {
|
|
|
386
487
|
}
|
|
387
488
|
// ─── Recalculate ───────────────────────────────────────────────
|
|
388
489
|
async recalculate(quoteId, options, scope) {
|
|
389
|
-
|
|
390
|
-
|
|
490
|
+
if (options.save) {
|
|
491
|
+
return this.writeQuote(
|
|
492
|
+
quoteId,
|
|
493
|
+
scope,
|
|
494
|
+
(em, quote) => this.recalculateFromConfig(em, quote, scope, true),
|
|
495
|
+
void 0,
|
|
496
|
+
(quote) => {
|
|
497
|
+
if (TERMINAL_STATUSES.includes(quote.cpqStatus)) throw new QuotingError(409, `Cannot recalculate quote in '${quote.cpqStatus}' status`);
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
const cpqConfig = await this.loadCpqConfig(this.em, quoteId, scope);
|
|
502
|
+
return this.recalculateFromConfig(this.em, { ...cpqConfig }, scope, false);
|
|
503
|
+
}
|
|
504
|
+
async recalculateFromConfig(em, cpqConfig, scope, save) {
|
|
505
|
+
if (save && TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
506
|
+
throw new QuotingError(409, `Cannot recalculate quote in '${cpqConfig.cpqStatus}' status`);
|
|
507
|
+
}
|
|
391
508
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
392
|
-
return this.recalculateInternal(em, cpqConfig, salesQuote, scope,
|
|
509
|
+
return this.recalculateInternal(em, cpqConfig, salesQuote, scope, save);
|
|
393
510
|
}
|
|
394
511
|
// ─── Clone Quote ───────────────────────────────────────────────
|
|
395
512
|
async cloneQuote(quoteId, scope) {
|
|
396
|
-
const
|
|
397
|
-
const
|
|
513
|
+
const created = {};
|
|
514
|
+
const result = await this.writeQuote(
|
|
515
|
+
quoteId,
|
|
516
|
+
scope,
|
|
517
|
+
(em, quote) => this.cloneQuoteInTransaction(quoteId, scope, em, quote, created),
|
|
518
|
+
void 0,
|
|
519
|
+
void 0,
|
|
520
|
+
{ pricingCause: "clone" }
|
|
521
|
+
);
|
|
522
|
+
if (created.quote) await emitQuoteWriteEvents(null, created.quote, { pricingCause: "clone" });
|
|
523
|
+
return result;
|
|
524
|
+
}
|
|
525
|
+
async cloneQuoteInTransaction(quoteId, scope, em, original, created = {}) {
|
|
398
526
|
const originalQuote = await this.loadSalesQuote(em, original.quoteId, scope);
|
|
399
527
|
const { number: quoteNumber } = await this.numberGenerator.generate({
|
|
400
528
|
kind: "quote",
|
|
@@ -422,11 +550,13 @@ class DefaultCpqQuotingService {
|
|
|
422
550
|
});
|
|
423
551
|
em.persist(newSalesQuote);
|
|
424
552
|
const newCpqConfig = em.create(CpqQuoteConfiguration, {
|
|
553
|
+
id: crypto.randomUUID(),
|
|
425
554
|
organizationId: scope.organizationId,
|
|
426
555
|
tenantId: scope.tenantId,
|
|
427
556
|
quoteId: newSalesQuoteId,
|
|
428
557
|
customerId: original.customerId,
|
|
429
558
|
cpqStatus: "new",
|
|
559
|
+
pricingRevision: 1,
|
|
430
560
|
version: original.version + 1,
|
|
431
561
|
parentQuoteId: original.id,
|
|
432
562
|
currencyCode: original.currencyCode,
|
|
@@ -435,6 +565,8 @@ class DefaultCpqQuotingService {
|
|
|
435
565
|
pricingSummary: emptyPricingSummary(original.currencyCode)
|
|
436
566
|
});
|
|
437
567
|
em.persist(newCpqConfig);
|
|
568
|
+
created.quote = newCpqConfig;
|
|
569
|
+
await em.flush();
|
|
438
570
|
const originalLines = await em.find(CpqQuoteLineConfiguration, {
|
|
439
571
|
quoteConfigurationId: original.id,
|
|
440
572
|
organizationId: scope.organizationId,
|
|
@@ -489,7 +621,8 @@ class DefaultCpqQuotingService {
|
|
|
489
621
|
startDate: origLine.startDate,
|
|
490
622
|
termMonths: origLine.termMonths,
|
|
491
623
|
endDate: origLine.endDate,
|
|
492
|
-
|
|
624
|
+
// A clone is a new quote: quote-only ARC pricing markers don't apply.
|
|
625
|
+
configuration: { ...stripQuoteOnlyArcMarkers(origLine.configuration) },
|
|
493
626
|
quantity: origLine.quantity,
|
|
494
627
|
charges: origLine.charges ? [...origLine.charges] : null,
|
|
495
628
|
nrcTotal: origLine.nrcTotal,
|
|
@@ -516,12 +649,22 @@ class DefaultCpqQuotingService {
|
|
|
516
649
|
}
|
|
517
650
|
}
|
|
518
651
|
await em.flush();
|
|
652
|
+
await cloneQuoteAdjustments(em, original.id, newCpqConfig, lineIdMap, scope);
|
|
653
|
+
await em.flush();
|
|
519
654
|
return this.recalculateInternal(em, newCpqConfig, newSalesQuote, scope, true);
|
|
520
655
|
}
|
|
521
656
|
// ─── Transition Status ─────────────────────────────────────────
|
|
522
|
-
async transitionStatus(quoteId, targetStatus, scope) {
|
|
523
|
-
|
|
524
|
-
|
|
657
|
+
async transitionStatus(quoteId, targetStatus, scope, actorUserId = null) {
|
|
658
|
+
return this.writeQuote(
|
|
659
|
+
quoteId,
|
|
660
|
+
scope,
|
|
661
|
+
(em, quote) => this.transitionStatusInTransaction(quoteId, targetStatus, scope, em, quote),
|
|
662
|
+
void 0,
|
|
663
|
+
void 0,
|
|
664
|
+
{ pricingCause: "recalculate", statusCause: "manual", actorUserId }
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
async transitionStatusInTransaction(quoteId, targetStatus, scope, em, cpqConfig) {
|
|
525
668
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
526
669
|
const currentStatus = cpqConfig.cpqStatus;
|
|
527
670
|
const allowed = ALLOWED_TRANSITIONS[currentStatus] ?? [];
|
|
@@ -531,7 +674,7 @@ class DefaultCpqQuotingService {
|
|
|
531
674
|
const isSubmitForApproval = targetStatus === "in_approval" || targetStatus === "pre_approved" || targetStatus === "with_customer";
|
|
532
675
|
const quoteType = cpqConfig.quoteType ?? "new";
|
|
533
676
|
if (isSubmitForApproval && quoteType !== "new") {
|
|
534
|
-
const validation = await this.validateArcQuote(quoteId, scope);
|
|
677
|
+
const validation = await this.validateArcQuote(quoteId, scope, em);
|
|
535
678
|
if (!validation.ok) {
|
|
536
679
|
throw new QuotingError(
|
|
537
680
|
422,
|
|
@@ -544,8 +687,26 @@ class DefaultCpqQuotingService {
|
|
|
544
687
|
const lines = await this.loadLineConfigs(em, cpqConfig.id, scope);
|
|
545
688
|
return this.buildQuoteResult(em, cpqConfig, salesQuote, lines);
|
|
546
689
|
}
|
|
690
|
+
async writeQuote(quoteId, scope, execute, expectedRevision, validate, events = { pricingCause: "recalculate" }) {
|
|
691
|
+
return withQuotePricingWrite(
|
|
692
|
+
this.em,
|
|
693
|
+
quoteId,
|
|
694
|
+
scope,
|
|
695
|
+
execute,
|
|
696
|
+
(status, message) => new QuotingError(status, message),
|
|
697
|
+
expectedRevision,
|
|
698
|
+
validate,
|
|
699
|
+
events
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
/** Called by adjustment commands that already own the quote lock/revision. */
|
|
703
|
+
async recalculateLockedQuote(em, quote, scope, save = true, adjustments) {
|
|
704
|
+
const salesQuote = await this.loadSalesQuote(em, quote.quoteId, scope);
|
|
705
|
+
const config = save ? quote : { ...quote };
|
|
706
|
+
return this.recalculateInternal(em, config, salesQuote, scope, save, adjustments);
|
|
707
|
+
}
|
|
547
708
|
// ─── Internal: Recalculate ─────────────────────────────────────
|
|
548
|
-
async recalculateInternal(em, cpqConfig, salesQuote, scope, save) {
|
|
709
|
+
async recalculateInternal(em, cpqConfig, salesQuote, scope, save, draftAdjustments) {
|
|
549
710
|
const lineConfigs = await em.find(CpqQuoteLineConfiguration, {
|
|
550
711
|
quoteConfigurationId: cpqConfig.id,
|
|
551
712
|
organizationId: scope.organizationId,
|
|
@@ -574,7 +735,7 @@ class DefaultCpqQuotingService {
|
|
|
574
735
|
mergedConfig.term_months = lineConfig.termMonths;
|
|
575
736
|
}
|
|
576
737
|
let validationErrors = [];
|
|
577
|
-
if (lineConfig.offeringId) {
|
|
738
|
+
if (lineConfig.offeringId && !isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
|
|
578
739
|
const validation = await this.validationService.validateOfferingConfiguration({
|
|
579
740
|
offeringId: lineConfig.offeringId,
|
|
580
741
|
configuration: lineConfig.configuration,
|
|
@@ -584,8 +745,11 @@ class DefaultCpqQuotingService {
|
|
|
584
745
|
}
|
|
585
746
|
const cfgRecord = lineConfig.configuration;
|
|
586
747
|
const isArcMirrored = !!cfgRecord?._arcMirroredFromItemId;
|
|
748
|
+
const isFrozen = isFrozenAmendMirrorLine(cfgRecord, cpqConfig.quoteType);
|
|
587
749
|
let charges;
|
|
588
|
-
if (lineConfig
|
|
750
|
+
if (isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
|
|
751
|
+
charges = [];
|
|
752
|
+
} else if ((lineConfig.offeringId || lineConfig.productId) && !isFrozen) {
|
|
589
753
|
charges = await this.pricingService.resolveProductCharges({
|
|
590
754
|
offeringId: lineConfig.offeringId ?? void 0,
|
|
591
755
|
productId: lineConfig.productId ?? void 0,
|
|
@@ -606,36 +770,74 @@ class DefaultCpqQuotingService {
|
|
|
606
770
|
if (!isConfigured) allValid = false;
|
|
607
771
|
allErrors.push(...validationErrors);
|
|
608
772
|
computedLines.push({ lineConfig, charges, nrcTotal, mrcTotal, usageEstimates, usageTotalEstimated, validationErrors, isConfigured });
|
|
773
|
+
}
|
|
774
|
+
const adjustments = draftAdjustments ?? await loadQuoteAdjustments(em, cpqConfig.id, scope);
|
|
775
|
+
const adjusted = resolveManualAdjustments(computedLines.map(({ lineConfig, charges }) => ({
|
|
776
|
+
lineId: lineConfig.quoteLineId,
|
|
777
|
+
parentLineId: lineConfig.parentLineId ?? null,
|
|
778
|
+
enginePriced: !!(lineConfig.offeringId || lineConfig.productId),
|
|
779
|
+
mirrored: isArcMirrorLine(lineConfig.configuration, cpqConfig.quoteType),
|
|
780
|
+
charges
|
|
781
|
+
})), adjustments, cpqConfig.currencyCode);
|
|
782
|
+
const adjustedById = new Map(adjusted.lines.map((line) => [line.lineId, line]));
|
|
783
|
+
const computedIds = new Set(computedLines.map((line) => line.lineConfig.quoteLineId));
|
|
784
|
+
for (const computed of computedLines) {
|
|
785
|
+
const { lineConfig } = computed;
|
|
786
|
+
if (adjustments.length) computed.charges = adjustedById.get(lineConfig.quoteLineId).charges;
|
|
787
|
+
const adjustmentErrors = adjusted.errors.filter((error) => error.quoteLineId === lineConfig.quoteLineId);
|
|
788
|
+
if (adjustmentErrors.length) {
|
|
789
|
+
computed.isConfigured = false;
|
|
790
|
+
allValid = false;
|
|
791
|
+
const errors = adjustmentErrors.map((error) => ({ message: error.code, attribute: error.chargeCode, lineId: error.quoteLineId, adjustmentId: error.adjustmentId }));
|
|
792
|
+
computed.validationErrors.push(...errors);
|
|
793
|
+
allErrors.push(...errors);
|
|
794
|
+
}
|
|
795
|
+
Object.assign(computed, computeLineTotals(
|
|
796
|
+
computed.charges,
|
|
797
|
+
lineConfig.quantity,
|
|
798
|
+
lineConfig.usageEstimates?.map((entry) => ({ chargeCode: entry.chargeCode, estimatedQuantity: entry.estimatedQuantity }))
|
|
799
|
+
));
|
|
800
|
+
const { charges, nrcTotal, mrcTotal, usageEstimates, usageTotalEstimated, validationErrors, isConfigured } = computed;
|
|
609
801
|
if (save) {
|
|
610
|
-
em
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
802
|
+
await withAtomicFlush(em, [
|
|
803
|
+
() => {
|
|
804
|
+
em.assign(lineConfig, {
|
|
805
|
+
charges,
|
|
806
|
+
nrcTotal: String(nrcTotal),
|
|
807
|
+
mrcTotal: String(mrcTotal),
|
|
808
|
+
usageEstimates: usageEstimates ?? null,
|
|
809
|
+
usageTotalEstimated: usageTotalEstimated != null ? String(usageTotalEstimated) : null,
|
|
810
|
+
validationErrors: validationErrors.length > 0 ? validationErrors : null,
|
|
811
|
+
isConfigured
|
|
812
|
+
});
|
|
813
|
+
},
|
|
814
|
+
async () => {
|
|
815
|
+
const salesLine = await em.findOne(SalesQuoteLine, {
|
|
816
|
+
id: lineConfig.quoteLineId,
|
|
817
|
+
organizationId: scope.organizationId,
|
|
818
|
+
tenantId: scope.tenantId
|
|
819
|
+
});
|
|
820
|
+
if (salesLine) {
|
|
821
|
+
const perInstanceNrc = charges.filter((charge) => charge.chargeType === "nrc").reduce((sum, charge) => sum + (charge.totalPrice ?? 0), 0);
|
|
822
|
+
const perInstanceMrc = charges.filter((charge) => charge.chargeType === "mrc").reduce((sum, charge) => sum + (charge.totalPrice ?? 0), 0);
|
|
823
|
+
const unitPriceNet = perInstanceNrc + perInstanceMrc;
|
|
824
|
+
em.assign(salesLine, {
|
|
825
|
+
quantity: String(lineConfig.quantity),
|
|
826
|
+
unitPriceNet: String(unitPriceNet),
|
|
827
|
+
unitPriceGross: String(unitPriceNet),
|
|
828
|
+
totalNetAmount: String(unitPriceNet * lineConfig.quantity),
|
|
829
|
+
totalGrossAmount: String(unitPriceNet * lineConfig.quantity),
|
|
830
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
]);
|
|
637
835
|
}
|
|
638
836
|
}
|
|
837
|
+
for (const error of adjusted.errors.filter((entry) => !computedIds.has(entry.quoteLineId))) {
|
|
838
|
+
allValid = false;
|
|
839
|
+
allErrors.push({ message: error.code, attribute: error.chargeCode, lineId: error.quoteLineId, adjustmentId: error.adjustmentId });
|
|
840
|
+
}
|
|
639
841
|
const relationshipErrors = await this.validationService.validateRelationships({
|
|
640
842
|
lines: lineConfigs.map((l) => ({
|
|
641
843
|
lineId: l.quoteLineId,
|
|
@@ -659,6 +861,13 @@ class DefaultCpqQuotingService {
|
|
|
659
861
|
cpqConfig.cpqStatus = cpqStatus;
|
|
660
862
|
}
|
|
661
863
|
const pricingSummary = computePricingSummaryFromComputed(computedLines, cpqConfig.currencyCode);
|
|
864
|
+
Object.assign(pricingSummary, computeWaterfallSummary(computedLines.map(({ lineConfig, charges }) => ({
|
|
865
|
+
lineId: lineConfig.quoteLineId,
|
|
866
|
+
quantity: lineConfig.quantity,
|
|
867
|
+
termMonths: lineConfig.termMonths,
|
|
868
|
+
includeInDeal: !isArcMirrorLine(lineConfig.configuration, cpqConfig.quoteType),
|
|
869
|
+
charges
|
|
870
|
+
})), adjustments, cpqConfig.currencyCode));
|
|
662
871
|
const validationResult = { valid: allValid, errors: allErrors };
|
|
663
872
|
if (save) {
|
|
664
873
|
cpqConfig.validationResult = validationResult;
|
|
@@ -674,7 +883,9 @@ class DefaultCpqQuotingService {
|
|
|
674
883
|
});
|
|
675
884
|
await em.flush();
|
|
676
885
|
}
|
|
677
|
-
|
|
886
|
+
const result = await this.buildQuoteResultFromComputed(em, cpqConfig, salesQuote, computedLines, validationResult, pricingSummary);
|
|
887
|
+
result.adjustmentErrors = adjusted.errors;
|
|
888
|
+
return result;
|
|
678
889
|
}
|
|
679
890
|
// ─── Helpers ───────────────────────────────────────────────────
|
|
680
891
|
async loadCpqConfig(em, quoteId, scope) {
|
|
@@ -764,7 +975,7 @@ class DefaultCpqQuotingService {
|
|
|
764
975
|
mrcTotal: Number(lc.mrcTotal),
|
|
765
976
|
usageEstimates: lc.usageEstimates ?? null,
|
|
766
977
|
usageTotalEstimated: lc.usageTotalEstimated != null ? Number(lc.usageTotalEstimated) : null,
|
|
767
|
-
charges: lc.charges ?? [],
|
|
978
|
+
charges: (lc.charges ?? []).map(withChargeWaterfall),
|
|
768
979
|
isConfigured: lc.isConfigured,
|
|
769
980
|
validationErrors: lc.validationErrors ?? null,
|
|
770
981
|
arcSource: pickArcSource(lc, arcSourceMap),
|
|
@@ -773,6 +984,16 @@ class DefaultCpqQuotingService {
|
|
|
773
984
|
}
|
|
774
985
|
const finalValidation = validationResult ?? cpqConfig.validationResult ?? { valid: true, errors: [] };
|
|
775
986
|
const finalPricing = pricingSummary ?? computePricingSummary(lineConfigs, cpqConfig.currencyCode);
|
|
987
|
+
if (!pricingSummary) {
|
|
988
|
+
const adjustments = await loadQuoteAdjustments(em, cpqConfig.id, { organizationId: cpqConfig.organizationId, tenantId: cpqConfig.tenantId });
|
|
989
|
+
Object.assign(finalPricing, computeWaterfallSummary(lines.map((line) => ({
|
|
990
|
+
lineId: line.lineId,
|
|
991
|
+
quantity: line.quantity,
|
|
992
|
+
termMonths: line.termMonths,
|
|
993
|
+
includeInDeal: !isArcMirrorLine(line.configuration, cpqConfig.quoteType),
|
|
994
|
+
charges: line.charges
|
|
995
|
+
})), adjustments, cpqConfig.currencyCode));
|
|
996
|
+
}
|
|
776
997
|
const { customerName, convertedOrderId } = await resolveQuoteSideData(em, cpqConfig);
|
|
777
998
|
return {
|
|
778
999
|
id: cpqConfig.id,
|
|
@@ -782,6 +1003,7 @@ class DefaultCpqQuotingService {
|
|
|
782
1003
|
customerName,
|
|
783
1004
|
cpqStatus: cpqConfig.cpqStatus,
|
|
784
1005
|
version: cpqConfig.version,
|
|
1006
|
+
pricingRevision: cpqConfig.pricingRevision ?? 0,
|
|
785
1007
|
parentQuoteId: cpqConfig.parentQuoteId ?? null,
|
|
786
1008
|
currencyCode: cpqConfig.currencyCode,
|
|
787
1009
|
convertedOrderId,
|
|
@@ -836,7 +1058,7 @@ class DefaultCpqQuotingService {
|
|
|
836
1058
|
mrcTotal,
|
|
837
1059
|
usageEstimates,
|
|
838
1060
|
usageTotalEstimated,
|
|
839
|
-
charges,
|
|
1061
|
+
charges: charges.map(withChargeWaterfall),
|
|
840
1062
|
isConfigured,
|
|
841
1063
|
validationErrors: validationErrors.length > 0 ? validationErrors : null,
|
|
842
1064
|
arcSource: pickArcSource(lc, arcSourceMap),
|
|
@@ -852,6 +1074,7 @@ class DefaultCpqQuotingService {
|
|
|
852
1074
|
customerName,
|
|
853
1075
|
cpqStatus: cpqConfig.cpqStatus,
|
|
854
1076
|
version: cpqConfig.version,
|
|
1077
|
+
pricingRevision: cpqConfig.pricingRevision ?? 0,
|
|
855
1078
|
parentQuoteId: cpqConfig.parentQuoteId ?? null,
|
|
856
1079
|
currencyCode: cpqConfig.currencyCode,
|
|
857
1080
|
convertedOrderId,
|
|
@@ -941,6 +1164,7 @@ function aggregateAdjustmentTotals(allCharges) {
|
|
|
941
1164
|
for (const charge of allCharges) {
|
|
942
1165
|
if (!charge.adjustments) continue;
|
|
943
1166
|
for (const adj of charge.adjustments) {
|
|
1167
|
+
if (adj.origin === "manual") continue;
|
|
944
1168
|
if (adj.delta < 0) {
|
|
945
1169
|
discountTotal += Math.abs(adj.delta) * (charge.quantity ?? 1);
|
|
946
1170
|
} else if (adj.delta > 0) {
|
|
@@ -1021,8 +1245,12 @@ function computePricingSummaryFromComputed(computedLines, currencyCode) {
|
|
|
1021
1245
|
currencyCode
|
|
1022
1246
|
};
|
|
1023
1247
|
}
|
|
1248
|
+
const LINE_EVENTS = { pricingCause: "line" };
|
|
1024
1249
|
function emptyPricingSummary(currencyCode) {
|
|
1025
1250
|
return {
|
|
1251
|
+
// A new quote starts with a real baseline hash, so its first priced line is
|
|
1252
|
+
// a pricing change; only pre-feature quotes have no stored hash.
|
|
1253
|
+
snapshotHash: computeWaterfallSummary([], [], currencyCode).snapshotHash,
|
|
1026
1254
|
nrcTotal: 0,
|
|
1027
1255
|
mrcTotal: 0,
|
|
1028
1256
|
usageCharges: [],
|
|
@@ -1339,43 +1567,39 @@ DefaultCpqQuotingService.prototype.attachTargetSubscription = async function(quo
|
|
|
1339
1567
|
};
|
|
1340
1568
|
DefaultCpqQuotingService.prototype.detachTargetSubscription = async function(quoteId, targetId, scope) {
|
|
1341
1569
|
const em = this.em;
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
id: targetId,
|
|
1346
|
-
quoteId: cpqConfig.id,
|
|
1347
|
-
organizationId: scope.organizationId,
|
|
1348
|
-
tenantId: scope.tenantId,
|
|
1349
|
-
deletedAt: null
|
|
1350
|
-
});
|
|
1351
|
-
if (!target) throw new QuotingError(404, "Target subscription not found");
|
|
1352
|
-
const lines = await em.find(CpqQuoteLineConfiguration, {
|
|
1353
|
-
quoteConfigurationId: cpqConfig.id,
|
|
1354
|
-
targetSubscriptionId: target.subscriptionId,
|
|
1355
|
-
organizationId: scope.organizationId,
|
|
1356
|
-
tenantId: scope.tenantId,
|
|
1357
|
-
deletedAt: null
|
|
1358
|
-
});
|
|
1359
|
-
const now = /* @__PURE__ */ new Date();
|
|
1360
|
-
for (const line of lines) {
|
|
1361
|
-
line.deletedAt = now;
|
|
1362
|
-
const salesLine = await em.findOne(SalesQuoteLine, { id: line.quoteLineId });
|
|
1363
|
-
if (salesLine) salesLine.deletedAt = now;
|
|
1364
|
-
}
|
|
1365
|
-
target.deletedAt = now;
|
|
1366
|
-
if (target.quoteType === "renew") {
|
|
1367
|
-
const remaining = await em.find(CpqQuoteTargetSubscription, {
|
|
1570
|
+
await withQuotePricingWrite(em, quoteId, scope, async (tx, cpqConfig) => {
|
|
1571
|
+
const target = await tx.findOne(CpqQuoteTargetSubscription, {
|
|
1572
|
+
id: targetId,
|
|
1368
1573
|
quoteId: cpqConfig.id,
|
|
1369
1574
|
organizationId: scope.organizationId,
|
|
1370
1575
|
tenantId: scope.tenantId,
|
|
1371
|
-
deletedAt: null
|
|
1372
|
-
|
|
1576
|
+
deletedAt: null
|
|
1577
|
+
});
|
|
1578
|
+
if (!target) throw new QuotingError(404, "Target subscription not found");
|
|
1579
|
+
const lines = await tx.find(CpqQuoteLineConfiguration, {
|
|
1580
|
+
quoteConfigurationId: cpqConfig.id,
|
|
1581
|
+
targetSubscriptionId: target.subscriptionId,
|
|
1582
|
+
organizationId: scope.organizationId,
|
|
1583
|
+
tenantId: scope.tenantId,
|
|
1584
|
+
deletedAt: null
|
|
1373
1585
|
});
|
|
1374
|
-
|
|
1375
|
-
|
|
1586
|
+
for (const line of lines) {
|
|
1587
|
+
if (line.deletedAt) continue;
|
|
1588
|
+
await removeQuoteLineTree(tx, cpqConfig.id, line, scope);
|
|
1376
1589
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1590
|
+
const remaining = target.quoteType === "renew" ? await tx.find(CpqQuoteTargetSubscription, {
|
|
1591
|
+
quoteId: cpqConfig.id,
|
|
1592
|
+
organizationId: scope.organizationId,
|
|
1593
|
+
tenantId: scope.tenantId,
|
|
1594
|
+
deletedAt: null,
|
|
1595
|
+
id: { $ne: target.id }
|
|
1596
|
+
}) : [];
|
|
1597
|
+
await withAtomicFlush(tx, [() => {
|
|
1598
|
+
target.deletedAt = /* @__PURE__ */ new Date();
|
|
1599
|
+
if (remaining.length === 1) remaining[0].mergeAction = "standalone";
|
|
1600
|
+
}]);
|
|
1601
|
+
await this.recalculateLockedQuote(tx, cpqConfig, scope, true);
|
|
1602
|
+
}, (status, message) => new QuotingError(status, message), void 0, ensureEditable, { pricingCause: "line" });
|
|
1379
1603
|
return { ok: true };
|
|
1380
1604
|
};
|
|
1381
1605
|
DefaultCpqQuotingService.prototype.updateTargetSubscription = async function(quoteId, targetId, patch, scope) {
|
|
@@ -1442,8 +1666,8 @@ DefaultCpqQuotingService.prototype.setMergeMeta = async function(quoteId, meta,
|
|
|
1442
1666
|
const lines = await loadLines(em, cpqConfig.id, scope);
|
|
1443
1667
|
return this.buildQuoteResult(em, cpqConfig, salesQuote, lines);
|
|
1444
1668
|
};
|
|
1445
|
-
DefaultCpqQuotingService.prototype.validateArcQuote = async function(quoteId, scope) {
|
|
1446
|
-
const em = this.em;
|
|
1669
|
+
DefaultCpqQuotingService.prototype.validateArcQuote = async function(quoteId, scope, transactionEm) {
|
|
1670
|
+
const em = transactionEm ?? this.em;
|
|
1447
1671
|
const cpqConfig = await loadCpqConfigStrict(em, quoteId, scope);
|
|
1448
1672
|
const errors = [];
|
|
1449
1673
|
if (cpqConfig.quoteType === "new") {
|
|
@@ -1669,6 +1893,7 @@ async function mapTargetView(em, target, scope) {
|
|
|
1669
1893
|
}
|
|
1670
1894
|
function emptyArcPricingSummary(currencyCode) {
|
|
1671
1895
|
return {
|
|
1896
|
+
snapshotHash: computeWaterfallSummary([], [], currencyCode).snapshotHash,
|
|
1672
1897
|
nrcTotal: 0,
|
|
1673
1898
|
mrcTotal: 0,
|
|
1674
1899
|
usageCharges: [],
|
|
@@ -1783,11 +2008,7 @@ async function mirrorTargetSubscriptionItems(em, cpqConfig, sub, salesQuote, quo
|
|
|
1783
2008
|
startDate: null,
|
|
1784
2009
|
termMonths: null,
|
|
1785
2010
|
endDate: null,
|
|
1786
|
-
configuration:
|
|
1787
|
-
...item.configuration,
|
|
1788
|
-
_arcMirroredFromItemId: item.id,
|
|
1789
|
-
_arcMirroredName: item.name
|
|
1790
|
-
},
|
|
2011
|
+
configuration: buildArcMirrorConfiguration(item.configuration, item, quoteType),
|
|
1791
2012
|
quantity: item.quantity,
|
|
1792
2013
|
charges: pickArcMirrorCharges(item, sub.currencyCode),
|
|
1793
2014
|
nrcTotal: String(item.nrcAmount ?? 0),
|