@dainamite/cpq 0.4.0 → 0.6.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 +70 -0
- package/MIGRATION.md +101 -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/handleArcError.js +2 -2
- package/dist/modules/cpq/api/_helpers/handleArcError.js.map +2 -2
- 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]/activate/route.js +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/route.js +19 -3
- package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/status/route.js +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/route.js +49 -4
- package/dist/modules/cpq/api/orders/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/clone/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 +45 -5
- 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 +34 -4
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/route.js +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/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/api/quotes/route.js +2 -2
- package/dist/modules/cpq/api/quotes/route.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +22 -8
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +89 -232
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/page.js.map +1 -1
- 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/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/pricing/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +205 -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 +144 -221
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
- package/dist/modules/cpq/backend/cpq/quotes/page.js.map +1 -1
- 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/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/wizards/page.js.map +1 -1
- 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 +154 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
- package/dist/modules/cpq/components/CpqListView.js.map +2 -2
- package/dist/modules/cpq/components/PriceWaterfall.js +66 -0
- package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js +117 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
- 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/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 +34 -30
- 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 +339 -111
- 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/package.json +9 -9
- package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +8 -5
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -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/handleArcError.ts +12 -12
- 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]/activate/route.ts +2 -2
- package/src/modules/cpq/api/orders/[orderId]/route.ts +20 -2
- package/src/modules/cpq/api/orders/[orderId]/status/route.ts +2 -2
- package/src/modules/cpq/api/orders/route.ts +53 -3
- package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +2 -2
- 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 +51 -4
- 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 +42 -3
- package/src/modules/cpq/api/quotes/[quoteId]/route.ts +2 -2
- 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/api/quotes/route.ts +2 -2
- package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +1 -1
- package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +1 -1
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
- package/src/modules/cpq/backend/cpq/offerings/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +60 -269
- package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/orders/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/price-rules/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
- 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 +178 -266
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/quotes/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/specifications/page.tsx +1 -1
- 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 +201 -0
- package/src/modules/cpq/components/CpqListView.tsx +2 -1
- package/src/modules/cpq/components/PriceWaterfall.tsx +94 -0
- package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
- package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
- 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/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__/errorBrands.test.ts +72 -0
- 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 +32 -28
- package/src/modules/cpq/services/cpqPricingService.ts +8 -0
- package/src/modules/cpq/services/cpqQuotingService.ts +388 -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
|
@@ -1,3 +1,12 @@
|
|
|
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";
|
|
1
10
|
import { SalesQuote, SalesQuoteLine } from "@open-mercato/core/modules/sales/data/entities";
|
|
2
11
|
import { CustomerEntity } from "@open-mercato/core/modules/customers/data/entities";
|
|
3
12
|
import {
|
|
@@ -16,6 +25,17 @@ import {
|
|
|
16
25
|
ARC_QUOTE_TYPES,
|
|
17
26
|
getBaseCurrencyCode
|
|
18
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";
|
|
19
39
|
class DefaultCpqQuotingService {
|
|
20
40
|
constructor(deps) {
|
|
21
41
|
this.em = deps.em;
|
|
@@ -107,12 +127,26 @@ class DefaultCpqQuotingService {
|
|
|
107
127
|
}
|
|
108
128
|
// ─── Add Quote Item ────────────────────────────────────────────
|
|
109
129
|
async addQuoteItem(quoteId, item, scope) {
|
|
110
|
-
|
|
111
|
-
|
|
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) };
|
|
112
134
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
113
135
|
if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
114
136
|
throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
|
|
115
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
|
+
}
|
|
116
150
|
const { offering, spec } = await this.resolveOffering(em, item, scope);
|
|
117
151
|
const mergedConfig = {
|
|
118
152
|
...cpqConfig.quoteContext ?? {},
|
|
@@ -223,8 +257,10 @@ class DefaultCpqQuotingService {
|
|
|
223
257
|
}
|
|
224
258
|
// ─── Update Quote Item ─────────────────────────────────────────
|
|
225
259
|
async updateQuoteItem(quoteId, lineId, update, scope) {
|
|
226
|
-
|
|
227
|
-
|
|
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) };
|
|
228
264
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
229
265
|
if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
230
266
|
throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
|
|
@@ -237,6 +273,9 @@ class DefaultCpqQuotingService {
|
|
|
237
273
|
deletedAt: null
|
|
238
274
|
});
|
|
239
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
|
+
}
|
|
240
279
|
const quoteLine = await em.findOne(SalesQuoteLine, {
|
|
241
280
|
id: lineId,
|
|
242
281
|
organizationId: scope.organizationId,
|
|
@@ -278,12 +317,27 @@ class DefaultCpqQuotingService {
|
|
|
278
317
|
if (existingCfg?._arcMirroredName !== void 0) {
|
|
279
318
|
arcMarkers._arcMirroredName = existingCfg._arcMirroredName;
|
|
280
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;
|
|
281
335
|
update.configuration = { ...update.configuration, ...arcMarkers };
|
|
282
336
|
mergedConfig._arcMirroredFromItemId = arcMarkers._arcMirroredFromItemId;
|
|
283
337
|
mergedConfig._arcMirroredName = arcMarkers._arcMirroredName;
|
|
284
338
|
const isArcMirrored = !!arcMarkers._arcMirroredFromItemId;
|
|
285
339
|
let charges;
|
|
286
|
-
if (lineConfig.offeringId || lineConfig.productId) {
|
|
340
|
+
if ((lineConfig.offeringId || lineConfig.productId) && !staysFrozen) {
|
|
287
341
|
charges = await this.pricingService.resolveProductCharges({
|
|
288
342
|
offeringId: lineConfig.offeringId ?? void 0,
|
|
289
343
|
productId: lineConfig.productId ?? void 0,
|
|
@@ -329,8 +383,9 @@ class DefaultCpqQuotingService {
|
|
|
329
383
|
}
|
|
330
384
|
// ─── Remove Quote Item ─────────────────────────────────────────
|
|
331
385
|
async removeQuoteItem(quoteId, lineId, scope) {
|
|
332
|
-
|
|
333
|
-
|
|
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) {
|
|
334
389
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
335
390
|
if (TERMINAL_STATUSES.includes(cpqConfig.cpqStatus)) {
|
|
336
391
|
throw new QuotingError(409, `Cannot modify quote in '${cpqConfig.cpqStatus}' status`);
|
|
@@ -343,31 +398,78 @@ class DefaultCpqQuotingService {
|
|
|
343
398
|
deletedAt: null
|
|
344
399
|
});
|
|
345
400
|
if (!lineConfig) throw new QuotingError(404, "Quote line not found");
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
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) },
|
|
351
445
|
organizationId: scope.organizationId,
|
|
352
446
|
tenantId: scope.tenantId,
|
|
353
447
|
deletedAt: null
|
|
354
448
|
});
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
const salesLine = await em.findOne(SalesQuoteLine, {
|
|
365
|
-
id: lineId,
|
|
366
|
-
organizationId: scope.organizationId,
|
|
367
|
-
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 };
|
|
368
457
|
});
|
|
369
|
-
|
|
370
|
-
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
|
+
}]);
|
|
371
473
|
return this.recalculateInternal(em, cpqConfig, salesQuote, scope, true);
|
|
372
474
|
}
|
|
373
475
|
// ─── Get Quote (read from DB, no recalculation) ────────────────
|
|
@@ -385,15 +487,42 @@ class DefaultCpqQuotingService {
|
|
|
385
487
|
}
|
|
386
488
|
// ─── Recalculate ───────────────────────────────────────────────
|
|
387
489
|
async recalculate(quoteId, options, scope) {
|
|
388
|
-
|
|
389
|
-
|
|
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
|
+
}
|
|
390
508
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
391
|
-
return this.recalculateInternal(em, cpqConfig, salesQuote, scope,
|
|
509
|
+
return this.recalculateInternal(em, cpqConfig, salesQuote, scope, save);
|
|
392
510
|
}
|
|
393
511
|
// ─── Clone Quote ───────────────────────────────────────────────
|
|
394
512
|
async cloneQuote(quoteId, scope) {
|
|
395
|
-
const
|
|
396
|
-
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 = {}) {
|
|
397
526
|
const originalQuote = await this.loadSalesQuote(em, original.quoteId, scope);
|
|
398
527
|
const { number: quoteNumber } = await this.numberGenerator.generate({
|
|
399
528
|
kind: "quote",
|
|
@@ -421,11 +550,13 @@ class DefaultCpqQuotingService {
|
|
|
421
550
|
});
|
|
422
551
|
em.persist(newSalesQuote);
|
|
423
552
|
const newCpqConfig = em.create(CpqQuoteConfiguration, {
|
|
553
|
+
id: crypto.randomUUID(),
|
|
424
554
|
organizationId: scope.organizationId,
|
|
425
555
|
tenantId: scope.tenantId,
|
|
426
556
|
quoteId: newSalesQuoteId,
|
|
427
557
|
customerId: original.customerId,
|
|
428
558
|
cpqStatus: "new",
|
|
559
|
+
pricingRevision: 1,
|
|
429
560
|
version: original.version + 1,
|
|
430
561
|
parentQuoteId: original.id,
|
|
431
562
|
currencyCode: original.currencyCode,
|
|
@@ -434,6 +565,8 @@ class DefaultCpqQuotingService {
|
|
|
434
565
|
pricingSummary: emptyPricingSummary(original.currencyCode)
|
|
435
566
|
});
|
|
436
567
|
em.persist(newCpqConfig);
|
|
568
|
+
created.quote = newCpqConfig;
|
|
569
|
+
await em.flush();
|
|
437
570
|
const originalLines = await em.find(CpqQuoteLineConfiguration, {
|
|
438
571
|
quoteConfigurationId: original.id,
|
|
439
572
|
organizationId: scope.organizationId,
|
|
@@ -488,7 +621,8 @@ class DefaultCpqQuotingService {
|
|
|
488
621
|
startDate: origLine.startDate,
|
|
489
622
|
termMonths: origLine.termMonths,
|
|
490
623
|
endDate: origLine.endDate,
|
|
491
|
-
|
|
624
|
+
// A clone is a new quote: quote-only ARC pricing markers don't apply.
|
|
625
|
+
configuration: { ...stripQuoteOnlyArcMarkers(origLine.configuration) },
|
|
492
626
|
quantity: origLine.quantity,
|
|
493
627
|
charges: origLine.charges ? [...origLine.charges] : null,
|
|
494
628
|
nrcTotal: origLine.nrcTotal,
|
|
@@ -515,12 +649,22 @@ class DefaultCpqQuotingService {
|
|
|
515
649
|
}
|
|
516
650
|
}
|
|
517
651
|
await em.flush();
|
|
652
|
+
await cloneQuoteAdjustments(em, original.id, newCpqConfig, lineIdMap, scope);
|
|
653
|
+
await em.flush();
|
|
518
654
|
return this.recalculateInternal(em, newCpqConfig, newSalesQuote, scope, true);
|
|
519
655
|
}
|
|
520
656
|
// ─── Transition Status ─────────────────────────────────────────
|
|
521
|
-
async transitionStatus(quoteId, targetStatus, scope) {
|
|
522
|
-
|
|
523
|
-
|
|
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) {
|
|
524
668
|
const salesQuote = await this.loadSalesQuote(em, cpqConfig.quoteId, scope);
|
|
525
669
|
const currentStatus = cpqConfig.cpqStatus;
|
|
526
670
|
const allowed = ALLOWED_TRANSITIONS[currentStatus] ?? [];
|
|
@@ -530,7 +674,7 @@ class DefaultCpqQuotingService {
|
|
|
530
674
|
const isSubmitForApproval = targetStatus === "in_approval" || targetStatus === "pre_approved" || targetStatus === "with_customer";
|
|
531
675
|
const quoteType = cpqConfig.quoteType ?? "new";
|
|
532
676
|
if (isSubmitForApproval && quoteType !== "new") {
|
|
533
|
-
const validation = await this.validateArcQuote(quoteId, scope);
|
|
677
|
+
const validation = await this.validateArcQuote(quoteId, scope, em);
|
|
534
678
|
if (!validation.ok) {
|
|
535
679
|
throw new QuotingError(
|
|
536
680
|
422,
|
|
@@ -543,8 +687,26 @@ class DefaultCpqQuotingService {
|
|
|
543
687
|
const lines = await this.loadLineConfigs(em, cpqConfig.id, scope);
|
|
544
688
|
return this.buildQuoteResult(em, cpqConfig, salesQuote, lines);
|
|
545
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
|
+
}
|
|
546
708
|
// ─── Internal: Recalculate ─────────────────────────────────────
|
|
547
|
-
async recalculateInternal(em, cpqConfig, salesQuote, scope, save) {
|
|
709
|
+
async recalculateInternal(em, cpqConfig, salesQuote, scope, save, draftAdjustments) {
|
|
548
710
|
const lineConfigs = await em.find(CpqQuoteLineConfiguration, {
|
|
549
711
|
quoteConfigurationId: cpqConfig.id,
|
|
550
712
|
organizationId: scope.organizationId,
|
|
@@ -573,7 +735,7 @@ class DefaultCpqQuotingService {
|
|
|
573
735
|
mergedConfig.term_months = lineConfig.termMonths;
|
|
574
736
|
}
|
|
575
737
|
let validationErrors = [];
|
|
576
|
-
if (lineConfig.offeringId) {
|
|
738
|
+
if (lineConfig.offeringId && !isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
|
|
577
739
|
const validation = await this.validationService.validateOfferingConfiguration({
|
|
578
740
|
offeringId: lineConfig.offeringId,
|
|
579
741
|
configuration: lineConfig.configuration,
|
|
@@ -583,8 +745,11 @@ class DefaultCpqQuotingService {
|
|
|
583
745
|
}
|
|
584
746
|
const cfgRecord = lineConfig.configuration;
|
|
585
747
|
const isArcMirrored = !!cfgRecord?._arcMirroredFromItemId;
|
|
748
|
+
const isFrozen = isFrozenAmendMirrorLine(cfgRecord, cpqConfig.quoteType);
|
|
586
749
|
let charges;
|
|
587
|
-
if (lineConfig
|
|
750
|
+
if (isArcCancelledLine(lineConfig, cpqConfig.quoteType)) {
|
|
751
|
+
charges = [];
|
|
752
|
+
} else if ((lineConfig.offeringId || lineConfig.productId) && !isFrozen) {
|
|
588
753
|
charges = await this.pricingService.resolveProductCharges({
|
|
589
754
|
offeringId: lineConfig.offeringId ?? void 0,
|
|
590
755
|
productId: lineConfig.productId ?? void 0,
|
|
@@ -605,36 +770,74 @@ class DefaultCpqQuotingService {
|
|
|
605
770
|
if (!isConfigured) allValid = false;
|
|
606
771
|
allErrors.push(...validationErrors);
|
|
607
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;
|
|
608
801
|
if (save) {
|
|
609
|
-
em
|
|
610
|
-
|
|
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
|
-
|
|
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
|
+
]);
|
|
636
835
|
}
|
|
637
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
|
+
}
|
|
638
841
|
const relationshipErrors = await this.validationService.validateRelationships({
|
|
639
842
|
lines: lineConfigs.map((l) => ({
|
|
640
843
|
lineId: l.quoteLineId,
|
|
@@ -658,6 +861,13 @@ class DefaultCpqQuotingService {
|
|
|
658
861
|
cpqConfig.cpqStatus = cpqStatus;
|
|
659
862
|
}
|
|
660
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));
|
|
661
871
|
const validationResult = { valid: allValid, errors: allErrors };
|
|
662
872
|
if (save) {
|
|
663
873
|
cpqConfig.validationResult = validationResult;
|
|
@@ -673,7 +883,9 @@ class DefaultCpqQuotingService {
|
|
|
673
883
|
});
|
|
674
884
|
await em.flush();
|
|
675
885
|
}
|
|
676
|
-
|
|
886
|
+
const result = await this.buildQuoteResultFromComputed(em, cpqConfig, salesQuote, computedLines, validationResult, pricingSummary);
|
|
887
|
+
result.adjustmentErrors = adjusted.errors;
|
|
888
|
+
return result;
|
|
677
889
|
}
|
|
678
890
|
// ─── Helpers ───────────────────────────────────────────────────
|
|
679
891
|
async loadCpqConfig(em, quoteId, scope) {
|
|
@@ -763,7 +975,7 @@ class DefaultCpqQuotingService {
|
|
|
763
975
|
mrcTotal: Number(lc.mrcTotal),
|
|
764
976
|
usageEstimates: lc.usageEstimates ?? null,
|
|
765
977
|
usageTotalEstimated: lc.usageTotalEstimated != null ? Number(lc.usageTotalEstimated) : null,
|
|
766
|
-
charges: lc.charges ?? [],
|
|
978
|
+
charges: (lc.charges ?? []).map(withChargeWaterfall),
|
|
767
979
|
isConfigured: lc.isConfigured,
|
|
768
980
|
validationErrors: lc.validationErrors ?? null,
|
|
769
981
|
arcSource: pickArcSource(lc, arcSourceMap),
|
|
@@ -772,6 +984,16 @@ class DefaultCpqQuotingService {
|
|
|
772
984
|
}
|
|
773
985
|
const finalValidation = validationResult ?? cpqConfig.validationResult ?? { valid: true, errors: [] };
|
|
774
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
|
+
}
|
|
775
997
|
const { customerName, convertedOrderId } = await resolveQuoteSideData(em, cpqConfig);
|
|
776
998
|
return {
|
|
777
999
|
id: cpqConfig.id,
|
|
@@ -781,6 +1003,7 @@ class DefaultCpqQuotingService {
|
|
|
781
1003
|
customerName,
|
|
782
1004
|
cpqStatus: cpqConfig.cpqStatus,
|
|
783
1005
|
version: cpqConfig.version,
|
|
1006
|
+
pricingRevision: cpqConfig.pricingRevision ?? 0,
|
|
784
1007
|
parentQuoteId: cpqConfig.parentQuoteId ?? null,
|
|
785
1008
|
currencyCode: cpqConfig.currencyCode,
|
|
786
1009
|
convertedOrderId,
|
|
@@ -835,7 +1058,7 @@ class DefaultCpqQuotingService {
|
|
|
835
1058
|
mrcTotal,
|
|
836
1059
|
usageEstimates,
|
|
837
1060
|
usageTotalEstimated,
|
|
838
|
-
charges,
|
|
1061
|
+
charges: charges.map(withChargeWaterfall),
|
|
839
1062
|
isConfigured,
|
|
840
1063
|
validationErrors: validationErrors.length > 0 ? validationErrors : null,
|
|
841
1064
|
arcSource: pickArcSource(lc, arcSourceMap),
|
|
@@ -851,6 +1074,7 @@ class DefaultCpqQuotingService {
|
|
|
851
1074
|
customerName,
|
|
852
1075
|
cpqStatus: cpqConfig.cpqStatus,
|
|
853
1076
|
version: cpqConfig.version,
|
|
1077
|
+
pricingRevision: cpqConfig.pricingRevision ?? 0,
|
|
854
1078
|
parentQuoteId: cpqConfig.parentQuoteId ?? null,
|
|
855
1079
|
currencyCode: cpqConfig.currencyCode,
|
|
856
1080
|
convertedOrderId,
|
|
@@ -940,6 +1164,7 @@ function aggregateAdjustmentTotals(allCharges) {
|
|
|
940
1164
|
for (const charge of allCharges) {
|
|
941
1165
|
if (!charge.adjustments) continue;
|
|
942
1166
|
for (const adj of charge.adjustments) {
|
|
1167
|
+
if (adj.origin === "manual") continue;
|
|
943
1168
|
if (adj.delta < 0) {
|
|
944
1169
|
discountTotal += Math.abs(adj.delta) * (charge.quantity ?? 1);
|
|
945
1170
|
} else if (adj.delta > 0) {
|
|
@@ -1020,8 +1245,12 @@ function computePricingSummaryFromComputed(computedLines, currencyCode) {
|
|
|
1020
1245
|
currencyCode
|
|
1021
1246
|
};
|
|
1022
1247
|
}
|
|
1248
|
+
const LINE_EVENTS = { pricingCause: "line" };
|
|
1023
1249
|
function emptyPricingSummary(currencyCode) {
|
|
1024
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,
|
|
1025
1254
|
nrcTotal: 0,
|
|
1026
1255
|
mrcTotal: 0,
|
|
1027
1256
|
usageCharges: [],
|
|
@@ -1031,15 +1260,20 @@ function emptyPricingSummary(currencyCode) {
|
|
|
1031
1260
|
currencyCode
|
|
1032
1261
|
};
|
|
1033
1262
|
}
|
|
1034
|
-
|
|
1263
|
+
const QUOTING_ERROR_BRAND = /* @__PURE__ */ Symbol.for("@dainamite/cpq/QuotingError");
|
|
1264
|
+
class QuotingError extends (_b = Error, _a = QUOTING_ERROR_BRAND, _b) {
|
|
1035
1265
|
constructor(status, message, cpqStatus, details) {
|
|
1036
1266
|
super(message);
|
|
1037
1267
|
this.status = status;
|
|
1038
1268
|
this.cpqStatus = cpqStatus;
|
|
1039
1269
|
this.details = details;
|
|
1270
|
+
this[_a] = true;
|
|
1040
1271
|
this.name = "QuotingError";
|
|
1041
1272
|
}
|
|
1042
1273
|
}
|
|
1274
|
+
function isQuotingError(err) {
|
|
1275
|
+
return !!err && typeof err === "object" && err[QUOTING_ERROR_BRAND] === true;
|
|
1276
|
+
}
|
|
1043
1277
|
const EDITABLE_QUOTE_STATUSES = /* @__PURE__ */ new Set([
|
|
1044
1278
|
"new",
|
|
1045
1279
|
"incomplete",
|
|
@@ -1333,43 +1567,39 @@ DefaultCpqQuotingService.prototype.attachTargetSubscription = async function(quo
|
|
|
1333
1567
|
};
|
|
1334
1568
|
DefaultCpqQuotingService.prototype.detachTargetSubscription = async function(quoteId, targetId, scope) {
|
|
1335
1569
|
const em = this.em;
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
id: targetId,
|
|
1340
|
-
quoteId: cpqConfig.id,
|
|
1341
|
-
organizationId: scope.organizationId,
|
|
1342
|
-
tenantId: scope.tenantId,
|
|
1343
|
-
deletedAt: null
|
|
1344
|
-
});
|
|
1345
|
-
if (!target) throw new QuotingError(404, "Target subscription not found");
|
|
1346
|
-
const lines = await em.find(CpqQuoteLineConfiguration, {
|
|
1347
|
-
quoteConfigurationId: cpqConfig.id,
|
|
1348
|
-
targetSubscriptionId: target.subscriptionId,
|
|
1349
|
-
organizationId: scope.organizationId,
|
|
1350
|
-
tenantId: scope.tenantId,
|
|
1351
|
-
deletedAt: null
|
|
1352
|
-
});
|
|
1353
|
-
const now = /* @__PURE__ */ new Date();
|
|
1354
|
-
for (const line of lines) {
|
|
1355
|
-
line.deletedAt = now;
|
|
1356
|
-
const salesLine = await em.findOne(SalesQuoteLine, { id: line.quoteLineId });
|
|
1357
|
-
if (salesLine) salesLine.deletedAt = now;
|
|
1358
|
-
}
|
|
1359
|
-
target.deletedAt = now;
|
|
1360
|
-
if (target.quoteType === "renew") {
|
|
1361
|
-
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,
|
|
1362
1573
|
quoteId: cpqConfig.id,
|
|
1363
1574
|
organizationId: scope.organizationId,
|
|
1364
1575
|
tenantId: scope.tenantId,
|
|
1365
|
-
deletedAt: null
|
|
1366
|
-
id: { $ne: target.id }
|
|
1576
|
+
deletedAt: null
|
|
1367
1577
|
});
|
|
1368
|
-
if (
|
|
1369
|
-
|
|
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
|
|
1585
|
+
});
|
|
1586
|
+
for (const line of lines) {
|
|
1587
|
+
if (line.deletedAt) continue;
|
|
1588
|
+
await removeQuoteLineTree(tx, cpqConfig.id, line, scope);
|
|
1370
1589
|
}
|
|
1371
|
-
|
|
1372
|
-
|
|
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" });
|
|
1373
1603
|
return { ok: true };
|
|
1374
1604
|
};
|
|
1375
1605
|
DefaultCpqQuotingService.prototype.updateTargetSubscription = async function(quoteId, targetId, patch, scope) {
|
|
@@ -1436,8 +1666,8 @@ DefaultCpqQuotingService.prototype.setMergeMeta = async function(quoteId, meta,
|
|
|
1436
1666
|
const lines = await loadLines(em, cpqConfig.id, scope);
|
|
1437
1667
|
return this.buildQuoteResult(em, cpqConfig, salesQuote, lines);
|
|
1438
1668
|
};
|
|
1439
|
-
DefaultCpqQuotingService.prototype.validateArcQuote = async function(quoteId, scope) {
|
|
1440
|
-
const em = this.em;
|
|
1669
|
+
DefaultCpqQuotingService.prototype.validateArcQuote = async function(quoteId, scope, transactionEm) {
|
|
1670
|
+
const em = transactionEm ?? this.em;
|
|
1441
1671
|
const cpqConfig = await loadCpqConfigStrict(em, quoteId, scope);
|
|
1442
1672
|
const errors = [];
|
|
1443
1673
|
if (cpqConfig.quoteType === "new") {
|
|
@@ -1663,6 +1893,7 @@ async function mapTargetView(em, target, scope) {
|
|
|
1663
1893
|
}
|
|
1664
1894
|
function emptyArcPricingSummary(currencyCode) {
|
|
1665
1895
|
return {
|
|
1896
|
+
snapshotHash: computeWaterfallSummary([], [], currencyCode).snapshotHash,
|
|
1666
1897
|
nrcTotal: 0,
|
|
1667
1898
|
mrcTotal: 0,
|
|
1668
1899
|
usageCharges: [],
|
|
@@ -1777,11 +2008,7 @@ async function mirrorTargetSubscriptionItems(em, cpqConfig, sub, salesQuote, quo
|
|
|
1777
2008
|
startDate: null,
|
|
1778
2009
|
termMonths: null,
|
|
1779
2010
|
endDate: null,
|
|
1780
|
-
configuration:
|
|
1781
|
-
...item.configuration,
|
|
1782
|
-
_arcMirroredFromItemId: item.id,
|
|
1783
|
-
_arcMirroredName: item.name
|
|
1784
|
-
},
|
|
2011
|
+
configuration: buildArcMirrorConfiguration(item.configuration, item, quoteType),
|
|
1785
2012
|
quantity: item.quantity,
|
|
1786
2013
|
charges: pickArcMirrorCharges(item, sub.currencyCode),
|
|
1787
2014
|
nrcTotal: String(item.nrcAmount ?? 0),
|
|
@@ -1862,6 +2089,7 @@ function pickArcMirrorCharges(item, currencyCode) {
|
|
|
1862
2089
|
}
|
|
1863
2090
|
export {
|
|
1864
2091
|
DefaultCpqQuotingService,
|
|
1865
|
-
QuotingError
|
|
2092
|
+
QuotingError,
|
|
2093
|
+
isQuotingError
|
|
1866
2094
|
};
|
|
1867
2095
|
//# sourceMappingURL=cpqQuotingService.js.map
|