@dainamite/cpq 0.5.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 +48 -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/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/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.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/orders/page.meta.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.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.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/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 +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/package.json +1 -1
- 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/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/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
- 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/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 +174 -263
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +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/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__/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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
|
+
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
4
|
+
import { SalesQuoteLine } from "@open-mercato/core/modules/sales/data/entities";
|
|
5
|
+
import { CpqQuoteLineConfiguration, CpqQuotePriceAdjustment } from "../../data/entities.js";
|
|
6
|
+
async function cloneQuoteAdjustments(em, sourceId, target, lineIds, scope) {
|
|
7
|
+
let after;
|
|
8
|
+
for (; ; ) {
|
|
9
|
+
const rows = await findWithDecryption(em, CpqQuotePriceAdjustment, {
|
|
10
|
+
quoteConfigurationId: sourceId,
|
|
11
|
+
...scope,
|
|
12
|
+
deletedAt: null,
|
|
13
|
+
...after ? { id: { $gt: after } } : {}
|
|
14
|
+
}, { limit: 500, orderBy: { id: "asc" } }, scope);
|
|
15
|
+
for (const row of rows) {
|
|
16
|
+
em.persist(em.create(CpqQuotePriceAdjustment, {
|
|
17
|
+
id: randomUUID(),
|
|
18
|
+
...scope,
|
|
19
|
+
quoteConfigurationId: target.id,
|
|
20
|
+
quoteId: target.quoteId,
|
|
21
|
+
// An orphaned invalid target remains invalid, without pointing back into the source quote.
|
|
22
|
+
quoteLineId: lineIds.get(row.quoteLineId) ?? randomUUID(),
|
|
23
|
+
level: row.level,
|
|
24
|
+
chargeCode: row.chargeCode,
|
|
25
|
+
chargeTypeFilter: row.chargeTypeFilter,
|
|
26
|
+
adjustmentType: row.adjustmentType,
|
|
27
|
+
value: row.value,
|
|
28
|
+
currencyCode: row.currencyCode,
|
|
29
|
+
windowKind: row.windowKind,
|
|
30
|
+
fromMonth: row.fromMonth,
|
|
31
|
+
toMonth: row.toMonth,
|
|
32
|
+
reasonCode: row.reasonCode,
|
|
33
|
+
reasonNote: row.reasonNote,
|
|
34
|
+
createdByUserId: row.createdByUserId,
|
|
35
|
+
isActive: row.isActive
|
|
36
|
+
}));
|
|
37
|
+
}
|
|
38
|
+
if (rows.length < 500) return;
|
|
39
|
+
after = rows[rows.length - 1].id;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function removeQuoteLineTree(em, quoteConfigurationId, root, scope) {
|
|
43
|
+
const children = /* @__PURE__ */ new Map();
|
|
44
|
+
let after;
|
|
45
|
+
for (; ; ) {
|
|
46
|
+
const rows = await findWithDecryption(em, CpqQuoteLineConfiguration, {
|
|
47
|
+
quoteConfigurationId,
|
|
48
|
+
...scope,
|
|
49
|
+
deletedAt: null,
|
|
50
|
+
...after ? { id: { $gt: after } } : {}
|
|
51
|
+
}, { limit: 500, orderBy: { id: "asc" } }, scope);
|
|
52
|
+
for (const row of rows) if (row.parentLineId) {
|
|
53
|
+
const siblings = children.get(row.parentLineId) ?? [];
|
|
54
|
+
siblings.push(row);
|
|
55
|
+
children.set(row.parentLineId, siblings);
|
|
56
|
+
}
|
|
57
|
+
if (rows.length < 500) break;
|
|
58
|
+
after = rows[rows.length - 1].id;
|
|
59
|
+
}
|
|
60
|
+
const selected = [], seen = /* @__PURE__ */ new Set();
|
|
61
|
+
const queue = [root];
|
|
62
|
+
for (let index = 0; index < queue.length; index++) {
|
|
63
|
+
const line = queue[index];
|
|
64
|
+
if (seen.has(line.quoteLineId)) continue;
|
|
65
|
+
seen.add(line.quoteLineId);
|
|
66
|
+
selected.push(line);
|
|
67
|
+
queue.push(...children.get(line.quoteLineId) ?? []);
|
|
68
|
+
}
|
|
69
|
+
const sales = [], adjustments = [];
|
|
70
|
+
const ids = selected.map((line) => line.quoteLineId);
|
|
71
|
+
for (let index = 0; index < ids.length; index += 500) {
|
|
72
|
+
const lineIds = ids.slice(index, index + 500);
|
|
73
|
+
sales.push(...await findWithDecryption(em, SalesQuoteLine, { id: { $in: lineIds }, ...scope, deletedAt: null }, { limit: 500 }, scope));
|
|
74
|
+
let cursor;
|
|
75
|
+
for (; ; ) {
|
|
76
|
+
const rows = await findWithDecryption(em, CpqQuotePriceAdjustment, {
|
|
77
|
+
quoteConfigurationId,
|
|
78
|
+
quoteLineId: { $in: lineIds },
|
|
79
|
+
...scope,
|
|
80
|
+
deletedAt: null,
|
|
81
|
+
...cursor ? { id: { $gt: cursor } } : {}
|
|
82
|
+
}, { limit: 500, orderBy: { id: "asc" } }, scope);
|
|
83
|
+
adjustments.push(...rows);
|
|
84
|
+
if (rows.length < 500) break;
|
|
85
|
+
cursor = rows[rows.length - 1].id;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const now = /* @__PURE__ */ new Date();
|
|
89
|
+
await withAtomicFlush(em, [() => {
|
|
90
|
+
for (const line of selected) line.deletedAt = now;
|
|
91
|
+
for (const line of sales) line.deletedAt = now;
|
|
92
|
+
for (const adjustment of adjustments) {
|
|
93
|
+
adjustment.deletedAt = now;
|
|
94
|
+
adjustment.isActive = false;
|
|
95
|
+
}
|
|
96
|
+
}]);
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
cloneQuoteAdjustments,
|
|
100
|
+
removeQuoteLineTree
|
|
101
|
+
};
|
|
102
|
+
//# sourceMappingURL=quoteAdjustmentLifecycle.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts"],
|
|
4
|
+
"sourcesContent": ["import { randomUUID } from 'node:crypto'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { SalesQuoteLine } from '@open-mercato/core/modules/sales/data/entities'\nimport { CpqQuoteLineConfiguration, CpqQuotePriceAdjustment } from '../../data/entities'\nimport type { TenantScope } from '../types'\n\nexport async function cloneQuoteAdjustments(em: EntityManager, sourceId: string, target: { id: string; quoteId: string }, lineIds: Map<string, string>, scope: TenantScope): Promise<void> {\n let after: string | undefined\n for (;;) {\n const rows = await findWithDecryption(em, CpqQuotePriceAdjustment, { quoteConfigurationId: sourceId, ...scope, deletedAt: null,\n ...(after ? { id: { $gt: after } } : {}) }, { limit: 500, orderBy: { id: 'asc' } }, scope)\n for (const row of rows) {\n em.persist(em.create(CpqQuotePriceAdjustment, { id: randomUUID(), ...scope, quoteConfigurationId: target.id, quoteId: target.quoteId,\n // An orphaned invalid target remains invalid, without pointing back into the source quote.\n quoteLineId: lineIds.get(row.quoteLineId) ?? randomUUID(), level: row.level, chargeCode: row.chargeCode,\n chargeTypeFilter: row.chargeTypeFilter, adjustmentType: row.adjustmentType, value: row.value, currencyCode: row.currencyCode,\n windowKind: row.windowKind, fromMonth: row.fromMonth, toMonth: row.toMonth,\n reasonCode: row.reasonCode, reasonNote: row.reasonNote, createdByUserId: row.createdByUserId, isActive: row.isActive }))\n }\n if (rows.length < 500) return\n after = rows[rows.length - 1].id\n }\n}\n\n/** Delete the complete bundle subtree and its adjustments before recalculation reads it. */\nexport async function removeQuoteLineTree(em: EntityManager, quoteConfigurationId: string, root: CpqQuoteLineConfiguration, scope: TenantScope): Promise<void> {\n const children = new Map<string, CpqQuoteLineConfiguration[]>()\n let after: string | undefined\n for (;;) {\n const rows = await findWithDecryption(em, CpqQuoteLineConfiguration, { quoteConfigurationId, ...scope, deletedAt: null,\n ...(after ? { id: { $gt: after } } : {}) }, { limit: 500, orderBy: { id: 'asc' } }, scope)\n for (const row of rows) if (row.parentLineId) {\n const siblings = children.get(row.parentLineId) ?? []\n siblings.push(row); children.set(row.parentLineId, siblings)\n }\n if (rows.length < 500) break\n after = rows[rows.length - 1].id\n }\n const selected: CpqQuoteLineConfiguration[] = [], seen = new Set<string>()\n const queue = [root]\n for (let index = 0; index < queue.length; index++) {\n const line = queue[index]\n if (seen.has(line.quoteLineId)) continue\n seen.add(line.quoteLineId); selected.push(line); queue.push(...(children.get(line.quoteLineId) ?? []))\n }\n const sales: SalesQuoteLine[] = [], adjustments: CpqQuotePriceAdjustment[] = []\n const ids = selected.map((line) => line.quoteLineId)\n for (let index = 0; index < ids.length; index += 500) {\n const lineIds = ids.slice(index, index + 500)\n sales.push(...await findWithDecryption(em, SalesQuoteLine, { id: { $in: lineIds }, ...scope, deletedAt: null }, { limit: 500 }, scope))\n let cursor: string | undefined\n for (;;) {\n const rows = await findWithDecryption(em, CpqQuotePriceAdjustment, { quoteConfigurationId, quoteLineId: { $in: lineIds }, ...scope, deletedAt: null,\n ...(cursor ? { id: { $gt: cursor } } : {}) }, { limit: 500, orderBy: { id: 'asc' } }, scope)\n adjustments.push(...rows)\n if (rows.length < 500) break\n cursor = rows[rows.length - 1].id\n }\n }\n const now = new Date()\n await withAtomicFlush(em, [() => {\n for (const line of selected) line.deletedAt = now\n for (const line of sales) line.deletedAt = now\n for (const adjustment of adjustments) { adjustment.deletedAt = now; adjustment.isActive = false }\n }])\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,kBAAkB;AAE3B,SAAS,0BAA0B;AACnC,SAAS,uBAAuB;AAChC,SAAS,sBAAsB;AAC/B,SAAS,2BAA2B,+BAA+B;AAGnE,eAAsB,sBAAsB,IAAmB,UAAkB,QAAyC,SAA8B,OAAmC;AACzL,MAAI;AACJ,aAAS;AACP,UAAM,OAAO,MAAM,mBAAmB,IAAI,yBAAyB;AAAA,MAAE,sBAAsB;AAAA,MAAU,GAAG;AAAA,MAAO,WAAW;AAAA,MACxH,GAAI,QAAQ,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,IAAI,CAAC;AAAA,IAAG,GAAG,EAAE,OAAO,KAAK,SAAS,EAAE,IAAI,MAAM,EAAE,GAAG,KAAK;AAC3F,eAAW,OAAO,MAAM;AACtB,SAAG,QAAQ,GAAG,OAAO,yBAAyB;AAAA,QAAE,IAAI,WAAW;AAAA,QAAG,GAAG;AAAA,QAAO,sBAAsB,OAAO;AAAA,QAAI,SAAS,OAAO;AAAA;AAAA,QAE3H,aAAa,QAAQ,IAAI,IAAI,WAAW,KAAK,WAAW;AAAA,QAAG,OAAO,IAAI;AAAA,QAAO,YAAY,IAAI;AAAA,QAC7F,kBAAkB,IAAI;AAAA,QAAkB,gBAAgB,IAAI;AAAA,QAAgB,OAAO,IAAI;AAAA,QAAO,cAAc,IAAI;AAAA,QAChH,YAAY,IAAI;AAAA,QAAY,WAAW,IAAI;AAAA,QAAW,SAAS,IAAI;AAAA,QACnE,YAAY,IAAI;AAAA,QAAY,YAAY,IAAI;AAAA,QAAY,iBAAiB,IAAI;AAAA,QAAiB,UAAU,IAAI;AAAA,MAAS,CAAC,CAAC;AAAA,IAC3H;AACA,QAAI,KAAK,SAAS,IAAK;AACvB,YAAQ,KAAK,KAAK,SAAS,CAAC,EAAE;AAAA,EAChC;AACF;AAGA,eAAsB,oBAAoB,IAAmB,sBAA8B,MAAiC,OAAmC;AAC7J,QAAM,WAAW,oBAAI,IAAyC;AAC9D,MAAI;AACJ,aAAS;AACP,UAAM,OAAO,MAAM,mBAAmB,IAAI,2BAA2B;AAAA,MAAE;AAAA,MAAsB,GAAG;AAAA,MAAO,WAAW;AAAA,MAChH,GAAI,QAAQ,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE,IAAI,CAAC;AAAA,IAAG,GAAG,EAAE,OAAO,KAAK,SAAS,EAAE,IAAI,MAAM,EAAE,GAAG,KAAK;AAC3F,eAAW,OAAO,KAAM,KAAI,IAAI,cAAc;AAC5C,YAAM,WAAW,SAAS,IAAI,IAAI,YAAY,KAAK,CAAC;AACpD,eAAS,KAAK,GAAG;AAAG,eAAS,IAAI,IAAI,cAAc,QAAQ;AAAA,IAC7D;AACA,QAAI,KAAK,SAAS,IAAK;AACvB,YAAQ,KAAK,KAAK,SAAS,CAAC,EAAE;AAAA,EAChC;AACA,QAAM,WAAwC,CAAC,GAAG,OAAO,oBAAI,IAAY;AACzE,QAAM,QAAQ,CAAC,IAAI;AACnB,WAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS;AACjD,UAAM,OAAO,MAAM,KAAK;AACxB,QAAI,KAAK,IAAI,KAAK,WAAW,EAAG;AAChC,SAAK,IAAI,KAAK,WAAW;AAAG,aAAS,KAAK,IAAI;AAAG,UAAM,KAAK,GAAI,SAAS,IAAI,KAAK,WAAW,KAAK,CAAC,CAAE;AAAA,EACvG;AACA,QAAM,QAA0B,CAAC,GAAG,cAAyC,CAAC;AAC9E,QAAM,MAAM,SAAS,IAAI,CAAC,SAAS,KAAK,WAAW;AACnD,WAAS,QAAQ,GAAG,QAAQ,IAAI,QAAQ,SAAS,KAAK;AACpD,UAAM,UAAU,IAAI,MAAM,OAAO,QAAQ,GAAG;AAC5C,UAAM,KAAK,GAAG,MAAM,mBAAmB,IAAI,gBAAgB,EAAE,IAAI,EAAE,KAAK,QAAQ,GAAG,GAAG,OAAO,WAAW,KAAK,GAAG,EAAE,OAAO,IAAI,GAAG,KAAK,CAAC;AACtI,QAAI;AACJ,eAAS;AACP,YAAM,OAAO,MAAM,mBAAmB,IAAI,yBAAyB;AAAA,QAAE;AAAA,QAAsB,aAAa,EAAE,KAAK,QAAQ;AAAA,QAAG,GAAG;AAAA,QAAO,WAAW;AAAA,QAC7I,GAAI,SAAS,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,IAAI,CAAC;AAAA,MAAG,GAAG,EAAE,OAAO,KAAK,SAAS,EAAE,IAAI,MAAM,EAAE,GAAG,KAAK;AAC7F,kBAAY,KAAK,GAAG,IAAI;AACxB,UAAI,KAAK,SAAS,IAAK;AACvB,eAAS,KAAK,KAAK,SAAS,CAAC,EAAE;AAAA,IACjC;AAAA,EACF;AACA,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,gBAAgB,IAAI,CAAC,MAAM;AAC/B,eAAW,QAAQ,SAAU,MAAK,YAAY;AAC9C,eAAW,QAAQ,MAAO,MAAK,YAAY;AAC3C,eAAW,cAAc,aAAa;AAAE,iBAAW,YAAY;AAAK,iBAAW,WAAW;AAAA,IAAM;AAAA,EAClG,CAAC,CAAC;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { emitCpqEvent } from "../../events.js";
|
|
2
|
+
function storedHash(summary) {
|
|
3
|
+
const hash = summary?.snapshotHash;
|
|
4
|
+
return typeof hash === "string" && hash.length > 0 ? hash : null;
|
|
5
|
+
}
|
|
6
|
+
function captureQuoteEventState(quote) {
|
|
7
|
+
return { snapshotHash: storedHash(quote.pricingSummary), cpqStatus: quote.cpqStatus };
|
|
8
|
+
}
|
|
9
|
+
function buildQuotePricingSnapshot(quote) {
|
|
10
|
+
const summary = quote.pricingSummary ?? {};
|
|
11
|
+
const quoteType = quote.quoteType === "amend" || quote.quoteType === "renew" || quote.quoteType === "cancel" ? quote.quoteType : "new";
|
|
12
|
+
return {
|
|
13
|
+
quoteId: quote.id,
|
|
14
|
+
quoteType,
|
|
15
|
+
cpqStatus: quote.cpqStatus,
|
|
16
|
+
currencyCode: quote.currencyCode,
|
|
17
|
+
pricingRevision: quote.pricingRevision ?? 0,
|
|
18
|
+
snapshotHash: storedHash(quote.pricingSummary) ?? "",
|
|
19
|
+
dealValue: summary.dealValue ?? { list: 0, standard: 0, offer: 0 },
|
|
20
|
+
discountDepth: summary.discountDepth ?? { rule: null, manual: null, total: null, maxLineManual: null },
|
|
21
|
+
hasTimeBasedAdjustments: summary.hasTimeBasedAdjustments === true
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
async function emitQuoteWriteEvents(before, quote, options, emit = emitCpqEvent) {
|
|
25
|
+
const after = captureQuoteEventState(quote);
|
|
26
|
+
const base = { tenantId: quote.tenantId, organizationId: quote.organizationId, ...buildQuotePricingSnapshot(quote), occurredAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
27
|
+
const pricingChanged = after.snapshotHash !== null && (before === null ? true : before.snapshotHash !== null && before.snapshotHash !== after.snapshotHash);
|
|
28
|
+
const pending = [];
|
|
29
|
+
if (pricingChanged) pending.push(["cpq.quote.pricing_changed", { ...base, cause: options.pricingCause }]);
|
|
30
|
+
if (before !== null && before.cpqStatus !== after.cpqStatus) {
|
|
31
|
+
pending.push(["cpq.quote.status_changed", {
|
|
32
|
+
...base,
|
|
33
|
+
fromStatus: before.cpqStatus,
|
|
34
|
+
toStatus: after.cpqStatus,
|
|
35
|
+
cause: options.statusCause ?? "auto",
|
|
36
|
+
actorUserId: options.actorUserId ?? null
|
|
37
|
+
}]);
|
|
38
|
+
}
|
|
39
|
+
for (const [eventId, payload] of pending) {
|
|
40
|
+
try {
|
|
41
|
+
await emit(eventId, payload);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(`[cpq] failed to emit ${eventId} for quote ${quote.id}`, error);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export {
|
|
48
|
+
buildQuotePricingSnapshot,
|
|
49
|
+
captureQuoteEventState,
|
|
50
|
+
emitQuoteWriteEvents
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=quotePricingEvents.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts"],
|
|
4
|
+
"sourcesContent": ["import { emitCpqEvent } from '../../events'\nimport type { CpqQuoteConfiguration } from '../../data/entities'\nimport type { PricingWaterfallSummary } from '../types'\n\nexport type QuotePricingCause = 'adjustment' | 'line' | 'recalculate' | 'clone'\nexport type QuoteStatusCause = 'manual' | 'auto'\n\n/** Describes the write so its committed effect can be named in the events. */\nexport interface QuoteWriteEvents {\n pricingCause: QuotePricingCause\n /** `manual` only for an operator-requested transition; recalculation-driven changes are `auto`. */\n statusCause?: QuoteStatusCause\n actorUserId?: string | null\n}\n\nexport interface QuoteEventState {\n snapshotHash: string | null\n cpqStatus: string\n}\n\ntype SnapshotSource = Pick<CpqQuoteConfiguration, 'id' | 'cpqStatus' | 'currencyCode' | 'pricingRevision' | 'pricingSummary' | 'tenantId' | 'organizationId'>\n & { quoteType?: string | null }\n\nexport interface QuotePricingSnapshot {\n quoteId: string\n quoteType: 'new' | 'amend' | 'renew' | 'cancel'\n cpqStatus: string\n currencyCode: string\n pricingRevision: number\n snapshotHash: string\n dealValue: PricingWaterfallSummary['dealValue']\n discountDepth: PricingWaterfallSummary['discountDepth']\n hasTimeBasedAdjustments: boolean\n}\n\ntype Emit = (eventId: string, payload: Record<string, unknown>) => Promise<unknown> | unknown\n\nfunction storedHash(summary: Record<string, unknown> | null | undefined): string | null {\n const hash = summary?.snapshotHash\n return typeof hash === 'string' && hash.length > 0 ? hash : null\n}\n\nexport function captureQuoteEventState(quote: Pick<CpqQuoteConfiguration, 'cpqStatus' | 'pricingSummary'>): QuoteEventState {\n return { snapshotHash: storedHash(quote.pricingSummary), cpqStatus: quote.cpqStatus }\n}\n\nexport function buildQuotePricingSnapshot(quote: SnapshotSource): QuotePricingSnapshot {\n const summary = (quote.pricingSummary ?? {}) as Partial<PricingWaterfallSummary>\n const quoteType = quote.quoteType === 'amend' || quote.quoteType === 'renew' || quote.quoteType === 'cancel' ? quote.quoteType : 'new'\n return {\n quoteId: quote.id,\n quoteType,\n cpqStatus: quote.cpqStatus,\n currencyCode: quote.currencyCode,\n pricingRevision: quote.pricingRevision ?? 0,\n snapshotHash: storedHash(quote.pricingSummary) ?? '',\n dealValue: summary.dealValue ?? { list: 0, standard: 0, offer: 0 },\n discountDepth: summary.discountDepth ?? { rule: null, manual: null, total: null, maxLineManual: null },\n hasTimeBasedAdjustments: summary.hasTimeBasedAdjustments === true,\n }\n}\n\n/**\n * Emits the committed effect of one quote write. Call only after the\n * transaction has committed. A null previous hash is a legacy quote's first\n * baseline and never counts as a pricing change. Dispatch failures are logged\n * and swallowed: the write stands and the stored hash stays authoritative.\n */\nexport async function emitQuoteWriteEvents(\n before: QuoteEventState | null,\n quote: SnapshotSource,\n options: QuoteWriteEvents,\n emit: Emit = emitCpqEvent as unknown as Emit,\n): Promise<void> {\n const after = captureQuoteEventState(quote)\n const base = { tenantId: quote.tenantId, organizationId: quote.organizationId, ...buildQuotePricingSnapshot(quote), occurredAt: new Date().toISOString() }\n const pricingChanged = after.snapshotHash !== null\n && (before === null ? true : before.snapshotHash !== null && before.snapshotHash !== after.snapshotHash)\n const pending: Array<[string, Record<string, unknown>]> = []\n if (pricingChanged) pending.push(['cpq.quote.pricing_changed', { ...base, cause: options.pricingCause }])\n if (before !== null && before.cpqStatus !== after.cpqStatus) {\n pending.push(['cpq.quote.status_changed', {\n ...base, fromStatus: before.cpqStatus, toStatus: after.cpqStatus,\n cause: options.statusCause ?? 'auto', actorUserId: options.actorUserId ?? null,\n }])\n }\n for (const [eventId, payload] of pending) {\n try {\n await emit(eventId, payload)\n } catch (error) {\n console.error(`[cpq] failed to emit ${eventId} for quote ${quote.id}`, error)\n }\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAqC7B,SAAS,WAAW,SAAoE;AACtF,QAAM,OAAO,SAAS;AACtB,SAAO,OAAO,SAAS,YAAY,KAAK,SAAS,IAAI,OAAO;AAC9D;AAEO,SAAS,uBAAuB,OAAqF;AAC1H,SAAO,EAAE,cAAc,WAAW,MAAM,cAAc,GAAG,WAAW,MAAM,UAAU;AACtF;AAEO,SAAS,0BAA0B,OAA6C;AACrF,QAAM,UAAW,MAAM,kBAAkB,CAAC;AAC1C,QAAM,YAAY,MAAM,cAAc,WAAW,MAAM,cAAc,WAAW,MAAM,cAAc,WAAW,MAAM,YAAY;AACjI,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf;AAAA,IACA,WAAW,MAAM;AAAA,IACjB,cAAc,MAAM;AAAA,IACpB,iBAAiB,MAAM,mBAAmB;AAAA,IAC1C,cAAc,WAAW,MAAM,cAAc,KAAK;AAAA,IAClD,WAAW,QAAQ,aAAa,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,EAAE;AAAA,IACjE,eAAe,QAAQ,iBAAiB,EAAE,MAAM,MAAM,QAAQ,MAAM,OAAO,MAAM,eAAe,KAAK;AAAA,IACrG,yBAAyB,QAAQ,4BAA4B;AAAA,EAC/D;AACF;AAQA,eAAsB,qBACpB,QACA,OACA,SACA,OAAa,cACE;AACf,QAAM,QAAQ,uBAAuB,KAAK;AAC1C,QAAM,OAAO,EAAE,UAAU,MAAM,UAAU,gBAAgB,MAAM,gBAAgB,GAAG,0BAA0B,KAAK,GAAG,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AACzJ,QAAM,iBAAiB,MAAM,iBAAiB,SACxC,WAAW,OAAO,OAAO,OAAO,iBAAiB,QAAQ,OAAO,iBAAiB,MAAM;AAC7F,QAAM,UAAoD,CAAC;AAC3D,MAAI,eAAgB,SAAQ,KAAK,CAAC,6BAA6B,EAAE,GAAG,MAAM,OAAO,QAAQ,aAAa,CAAC,CAAC;AACxG,MAAI,WAAW,QAAQ,OAAO,cAAc,MAAM,WAAW;AAC3D,YAAQ,KAAK,CAAC,4BAA4B;AAAA,MACxC,GAAG;AAAA,MAAM,YAAY,OAAO;AAAA,MAAW,UAAU,MAAM;AAAA,MACvD,OAAO,QAAQ,eAAe;AAAA,MAAQ,aAAa,QAAQ,eAAe;AAAA,IAC5E,CAAC,CAAC;AAAA,EACJ;AACA,aAAW,CAAC,SAAS,OAAO,KAAK,SAAS;AACxC,QAAI;AACF,YAAM,KAAK,SAAS,OAAO;AAAA,IAC7B,SAAS,OAAO;AACd,cAAQ,MAAM,wBAAwB,OAAO,cAAc,MAAM,EAAE,IAAI,KAAK;AAAA,IAC9E;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { withAtomicFlush } from "@open-mercato/shared/lib/commands/flush";
|
|
2
|
+
import { LockMode } from "@mikro-orm/core";
|
|
3
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
4
|
+
import { CpqQuoteConfiguration } from "../../data/entities.js";
|
|
5
|
+
import { captureQuoteEventState, emitQuoteWriteEvents } from "./quotePricingEvents.js";
|
|
6
|
+
async function withQuotePricingWrite(em, quoteId, scope, execute, makeError, expectedRevision, validate, events = { pricingCause: "recalculate" }) {
|
|
7
|
+
if (em.isInTransaction?.()) throw new Error("withQuotePricingWrite must not run inside an existing transaction");
|
|
8
|
+
const isolated = em.fork({ clear: true, useContext: false });
|
|
9
|
+
const committed = {};
|
|
10
|
+
const result = await isolated.transactional(async (transaction) => {
|
|
11
|
+
const scoped = { organizationId: scope.organizationId, tenantId: scope.tenantId, deletedAt: null };
|
|
12
|
+
const options = { lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true };
|
|
13
|
+
const quote = await findOneWithDecryption(transaction, CpqQuoteConfiguration, { ...scoped, id: quoteId }, options, scope) ?? await findOneWithDecryption(transaction, CpqQuoteConfiguration, { ...scoped, quoteId }, options, scope);
|
|
14
|
+
if (!quote) throw makeError(404, "Quote not found");
|
|
15
|
+
const revision = quote.pricingRevision ?? 0;
|
|
16
|
+
if (expectedRevision !== void 0 && expectedRevision !== revision) throw makeError(409, "STALE_REVISION");
|
|
17
|
+
validate?.(quote);
|
|
18
|
+
committed.quote = quote;
|
|
19
|
+
committed.before = captureQuoteEventState(quote);
|
|
20
|
+
let written;
|
|
21
|
+
await withAtomicFlush(transaction, [
|
|
22
|
+
() => {
|
|
23
|
+
quote.pricingRevision = revision + 1;
|
|
24
|
+
},
|
|
25
|
+
async () => {
|
|
26
|
+
written = await execute(transaction, quote);
|
|
27
|
+
}
|
|
28
|
+
]);
|
|
29
|
+
return written;
|
|
30
|
+
}, { clear: true });
|
|
31
|
+
if (committed.quote && committed.before) await emitQuoteWriteEvents(committed.before, committed.quote, events);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
withQuotePricingWrite
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=quotePricingWrite.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts"],
|
|
4
|
+
"sourcesContent": ["import { withAtomicFlush } from '@open-mercato/shared/lib/commands/flush'\nimport { LockMode } from '@mikro-orm/core'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { CpqQuoteConfiguration } from '../../data/entities'\nimport type { TenantScope } from '../types'\nimport { captureQuoteEventState, emitQuoteWriteEvents, type QuoteEventState, type QuoteWriteEvents } from './quotePricingEvents'\n\n/** Every pricing write and conversion locks the same scoped aggregate row. */\nexport async function withQuotePricingWrite<T>(\n em: EntityManager,\n quoteId: string,\n scope: TenantScope,\n execute: (transaction: EntityManager, quote: CpqQuoteConfiguration) => Promise<T>,\n makeError: (status: number, message: string) => Error,\n expectedRevision?: number,\n validate?: (quote: CpqQuoteConfiguration) => void,\n events: QuoteWriteEvents = { pricingCause: 'recalculate' },\n): Promise<T> {\n // Do not inherit managed objects/pending writes or propagate committed state\n // back into the caller's identity map. The returned DTO is authoritative;\n // callers that need fresh entities must reload them in their own context.\n // The isolated fork opens its own connection. Called from inside another\n // transaction it could not see that transaction's uncommitted quote, and it\n // would block forever on a row lock that transaction holds \u2014 fail fast.\n if (em.isInTransaction?.()) throw new Error('withQuotePricingWrite must not run inside an existing transaction')\n const isolated = em.fork({ clear: true, useContext: false })\n const committed: { quote?: CpqQuoteConfiguration; before?: QuoteEventState } = {}\n const result = await isolated.transactional(async (transaction) => {\n const scoped = { organizationId: scope.organizationId, tenantId: scope.tenantId, deletedAt: null }\n const options = { lockMode: LockMode.PESSIMISTIC_WRITE, refresh: true }\n const quote = await findOneWithDecryption(transaction, CpqQuoteConfiguration, { ...scoped, id: quoteId }, options, scope)\n ?? await findOneWithDecryption(transaction, CpqQuoteConfiguration, { ...scoped, quoteId }, options, scope)\n if (!quote) throw makeError(404, 'Quote not found')\n const revision = quote.pricingRevision ?? 0\n if (expectedRevision !== undefined && expectedRevision !== revision) throw makeError(409, 'STALE_REVISION')\n validate?.(quote)\n committed.quote = quote\n committed.before = captureQuoteEventState(quote)\n // This scalar update and every later flush are inside the same DB transaction.\n let written!: T\n await withAtomicFlush(transaction, [\n () => { quote.pricingRevision = revision + 1 },\n async () => { written = await execute(transaction, quote) },\n ])\n return written\n }, { clear: true })\n // `transactional` resolved, so the write is committed; a rolled-back write never reaches here.\n if (committed.quote && committed.before) await emitQuoteWriteEvents(committed.before, committed.quote, events)\n return result\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,uBAAuB;AAChC,SAAS,gBAAgB;AACzB,SAAS,6BAA6B;AAEtC,SAAS,6BAA6B;AAEtC,SAAS,wBAAwB,4BAAyE;AAG1G,eAAsB,sBACpB,IACA,SACA,OACA,SACA,WACA,kBACA,UACA,SAA2B,EAAE,cAAc,cAAc,GAC7C;AAOZ,MAAI,GAAG,kBAAkB,EAAG,OAAM,IAAI,MAAM,mEAAmE;AAC/G,QAAM,WAAW,GAAG,KAAK,EAAE,OAAO,MAAM,YAAY,MAAM,CAAC;AAC3D,QAAM,YAAyE,CAAC;AAChF,QAAM,SAAS,MAAM,SAAS,cAAc,OAAO,gBAAgB;AACjE,UAAM,SAAS,EAAE,gBAAgB,MAAM,gBAAgB,UAAU,MAAM,UAAU,WAAW,KAAK;AACjG,UAAM,UAAU,EAAE,UAAU,SAAS,mBAAmB,SAAS,KAAK;AACtE,UAAM,QAAQ,MAAM,sBAAsB,aAAa,uBAAuB,EAAE,GAAG,QAAQ,IAAI,QAAQ,GAAG,SAAS,KAAK,KACnH,MAAM,sBAAsB,aAAa,uBAAuB,EAAE,GAAG,QAAQ,QAAQ,GAAG,SAAS,KAAK;AAC3G,QAAI,CAAC,MAAO,OAAM,UAAU,KAAK,iBAAiB;AAClD,UAAM,WAAW,MAAM,mBAAmB;AAC1C,QAAI,qBAAqB,UAAa,qBAAqB,SAAU,OAAM,UAAU,KAAK,gBAAgB;AAC1G,eAAW,KAAK;AAChB,cAAU,QAAQ;AAClB,cAAU,SAAS,uBAAuB,KAAK;AAE/C,QAAI;AACJ,UAAM,gBAAgB,aAAa;AAAA,MACjC,MAAM;AAAE,cAAM,kBAAkB,WAAW;AAAA,MAAE;AAAA,MAC7C,YAAY;AAAE,kBAAU,MAAM,QAAQ,aAAa,KAAK;AAAA,MAAE;AAAA,IAC5D,CAAC;AACD,WAAO;AAAA,EACT,GAAG,EAAE,OAAO,KAAK,CAAC;AAElB,MAAI,UAAU,SAAS,UAAU,OAAQ,OAAM,qBAAqB,UAAU,QAAQ,UAAU,OAAO,MAAM;AAC7G,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { withChargeWaterfall } from "./chargeWaterfall.js";
|
|
2
|
+
function resolveManualAdjustments(lines, adjustments, currencyCode) {
|
|
3
|
+
const errors = [];
|
|
4
|
+
const byId = new Map(lines.map((line) => [line.lineId, line]));
|
|
5
|
+
const eligible = (line) => line.enginePriced && !line.mirrored;
|
|
6
|
+
const output = lines.map((line) => ({
|
|
7
|
+
...line,
|
|
8
|
+
charges: line.charges.map((charge) => {
|
|
9
|
+
const normalized = withChargeWaterfall(charge);
|
|
10
|
+
return {
|
|
11
|
+
...normalized,
|
|
12
|
+
unitPrice: eligible(line) ? normalized.standardUnitPrice : normalized.unitPrice,
|
|
13
|
+
totalPrice: eligible(line) ? normalized.standardTotalPrice : normalized.totalPrice,
|
|
14
|
+
adjustments: (eligible(line) ? normalized.adjustments?.filter((entry) => entry.origin !== "manual") : normalized.adjustments) ?? [],
|
|
15
|
+
adjustmentErrors: []
|
|
16
|
+
};
|
|
17
|
+
})
|
|
18
|
+
}));
|
|
19
|
+
const outById = new Map(output.map((line) => [line.lineId, line]));
|
|
20
|
+
const addError = (row, code, chargeCode, lineId = row.quoteLineId) => {
|
|
21
|
+
if (errors.some((entry) => entry.adjustmentId === row.id && entry.quoteLineId === lineId && entry.chargeCode === chargeCode && entry.code === code)) return;
|
|
22
|
+
const error = { adjustmentId: row.id, quoteLineId: lineId, ...chargeCode ? { chargeCode } : {}, code };
|
|
23
|
+
errors.push(error);
|
|
24
|
+
for (const charge of outById.get(lineId)?.charges ?? []) {
|
|
25
|
+
if (!chargeCode || charge.chargeCode === chargeCode) charge.adjustmentErrors.push(error);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
29
|
+
for (const row of adjustments) {
|
|
30
|
+
const target = byId.get(row.quoteLineId);
|
|
31
|
+
let code;
|
|
32
|
+
if (!target) code = "ADJUSTMENT_TARGET_MISSING";
|
|
33
|
+
else if (!eligible(target)) code = "LINE_NOT_ELIGIBLE";
|
|
34
|
+
else if (row.level === "charge") {
|
|
35
|
+
const matches = target.charges.filter((charge) => charge.chargeCode === row.chargeCode);
|
|
36
|
+
if (!matches.length) code = "ADJUSTMENT_TARGET_MISSING";
|
|
37
|
+
else if (matches.length > 1) code = "TARGET_AMBIGUOUS";
|
|
38
|
+
}
|
|
39
|
+
if (code) {
|
|
40
|
+
addError(row, code, row.chargeCode ?? void 0);
|
|
41
|
+
invalid.add(row.id);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const line of output) {
|
|
45
|
+
if (!eligible(line)) continue;
|
|
46
|
+
const lineage = [];
|
|
47
|
+
const visited = /* @__PURE__ */ new Set([line.lineId]);
|
|
48
|
+
let parent = line.parentLineId;
|
|
49
|
+
while (parent && !visited.has(parent)) {
|
|
50
|
+
visited.add(parent);
|
|
51
|
+
lineage.push(parent);
|
|
52
|
+
parent = byId.get(parent)?.parentLineId ?? null;
|
|
53
|
+
}
|
|
54
|
+
const rank = (row) => row.level === "charge" ? 0 : row.quoteLineId === line.lineId ? 1 : 2 + lineage.indexOf(row.quoteLineId);
|
|
55
|
+
for (const charge of line.charges) {
|
|
56
|
+
const candidates = adjustments.filter((row) => !invalid.has(row.id) && (row.level === "charge" ? row.quoteLineId === line.lineId && row.chargeCode === charge.chargeCode : (row.quoteLineId === line.lineId || lineage.includes(row.quoteLineId)) && (!row.chargeTypeFilter || row.chargeTypeFilter === charge.chargeType))).sort((a, b) => rank(a) - rank(b) || a.id.localeCompare(b.id));
|
|
57
|
+
const standard = charge.standardUnitPrice;
|
|
58
|
+
const total = charge.standardTotalPrice;
|
|
59
|
+
const valid = candidates.filter((row) => {
|
|
60
|
+
if (charge.chargeType === "usage" && row.level === "line") return false;
|
|
61
|
+
let code;
|
|
62
|
+
if (charge.chargeType === "usage" || total === null || !Number.isFinite(row.value) || row.value < 0 || row.adjustmentType === "price_override" && charge.pricingMethod !== "flat" && charge.pricingMethod !== "per_unit" || row.level === "line" && row.adjustmentType !== "discount_percent" || row.adjustmentType === "discount_percent" && (row.value <= 0 || row.value > 100) || row.adjustmentType === "discount_absolute" && row.value <= 0) code = "TYPE_NOT_ALLOWED";
|
|
63
|
+
else if (row.currencyCode && row.currencyCode !== currencyCode || charge.currencyCode !== currencyCode || row.adjustmentType !== "discount_percent" && !row.currencyCode) code = "CURRENCY_MISMATCH";
|
|
64
|
+
else if (row.adjustmentType === "price_override" && row.value > standard) code = "ABOVE_STANDARD";
|
|
65
|
+
if (code) addError(row, code, charge.chargeCode, line.lineId);
|
|
66
|
+
return !code;
|
|
67
|
+
});
|
|
68
|
+
let applied = false;
|
|
69
|
+
for (const row of valid) {
|
|
70
|
+
if (valid.filter((other) => rank(other) === rank(row)).length > 1) {
|
|
71
|
+
addError(row, "ADJUSTMENT_OVERLAP", charge.chargeCode, line.lineId);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (applied) continue;
|
|
75
|
+
const quantity = charge.quantity ?? 1;
|
|
76
|
+
const afterTotal = row.adjustmentType === "discount_absolute" ? Math.max(0, total - row.value) : row.adjustmentType === "discount_percent" ? Math.max(0, total * (1 - row.value / 100)) : row.value * quantity;
|
|
77
|
+
const afterUnit = row.adjustmentType === "discount_percent" ? standard * (1 - row.value / 100) : row.adjustmentType === "price_override" ? row.value : quantity > 0 ? afterTotal / quantity : 0;
|
|
78
|
+
charge.unitPrice = afterUnit;
|
|
79
|
+
charge.totalPrice = afterTotal;
|
|
80
|
+
charge.adjustments.push({
|
|
81
|
+
origin: "manual",
|
|
82
|
+
adjustmentId: row.id,
|
|
83
|
+
class: "rep",
|
|
84
|
+
level: row.level,
|
|
85
|
+
windowKind: "relative",
|
|
86
|
+
fromMonth: 1,
|
|
87
|
+
toMonth: null,
|
|
88
|
+
ruleCode: row.id,
|
|
89
|
+
ruleName: row.reasonCode,
|
|
90
|
+
ruleType: row.adjustmentType,
|
|
91
|
+
reasonCode: row.reasonCode,
|
|
92
|
+
createdByUserId: row.createdByUserId,
|
|
93
|
+
value: row.value,
|
|
94
|
+
unitPriceBefore: standard,
|
|
95
|
+
unitPriceAfter: afterUnit,
|
|
96
|
+
delta: afterUnit - standard
|
|
97
|
+
});
|
|
98
|
+
applied = true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return { lines: output, errors };
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
resolveManualAdjustments
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=resolveManualAdjustments.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AdjustmentError, PriceAdjustment } from '../../data/price-adjustments'\nimport type { ResolvedCharge } from '../types'\nimport { withChargeWaterfall } from './chargeWaterfall'\n\nexport interface AdjustmentPricingLine {\n lineId: string\n parentLineId: string | null\n enginePriced: boolean\n mirrored: boolean\n charges: ResolvedCharge[]\n}\n\n/** Most-specific valid manual adjustment wins; each is evaluated against standard. */\nexport function resolveManualAdjustments(\n lines: AdjustmentPricingLine[],\n adjustments: PriceAdjustment[],\n currencyCode: string,\n): { lines: AdjustmentPricingLine[]; errors: AdjustmentError[] } {\n const errors: AdjustmentError[] = []\n const byId = new Map(lines.map((line) => [line.lineId, line]))\n const eligible = (line: AdjustmentPricingLine) => line.enginePriced && !line.mirrored\n const output = lines.map((line) => ({\n ...line,\n charges: line.charges.map((charge) => {\n const normalized = withChargeWaterfall(charge)\n return { ...normalized, unitPrice: eligible(line) ? normalized.standardUnitPrice! : normalized.unitPrice,\n totalPrice: eligible(line) ? normalized.standardTotalPrice! : normalized.totalPrice,\n adjustments: (eligible(line) ? normalized.adjustments?.filter((entry) => entry.origin !== 'manual') : normalized.adjustments) ?? [], adjustmentErrors: [] as AdjustmentError[] }\n }),\n }))\n const outById = new Map(output.map((line) => [line.lineId, line]))\n const addError = (row: PriceAdjustment, code: AdjustmentError['code'], chargeCode?: string, lineId = row.quoteLineId) => {\n if (errors.some((entry) => entry.adjustmentId === row.id && entry.quoteLineId === lineId && entry.chargeCode === chargeCode && entry.code === code)) return\n const error: AdjustmentError = { adjustmentId: row.id, quoteLineId: lineId, ...(chargeCode ? { chargeCode } : {}), code }\n errors.push(error)\n for (const charge of outById.get(lineId)?.charges ?? []) {\n if (!chargeCode || charge.chargeCode === chargeCode) charge.adjustmentErrors.push(error)\n }\n }\n // Missing/ambiguous targets are invalid even if another adjustment could win.\n const invalid = new Set<string>()\n for (const row of adjustments) {\n const target = byId.get(row.quoteLineId)\n let code: AdjustmentError['code'] | undefined\n if (!target) code = 'ADJUSTMENT_TARGET_MISSING'\n else if (!eligible(target)) code = 'LINE_NOT_ELIGIBLE'\n else if (row.level === 'charge') {\n const matches = target.charges.filter((charge) => charge.chargeCode === row.chargeCode)\n if (!matches.length) code = 'ADJUSTMENT_TARGET_MISSING'\n else if (matches.length > 1) code = 'TARGET_AMBIGUOUS'\n }\n if (code) { addError(row, code, row.chargeCode ?? undefined); invalid.add(row.id) }\n }\n for (const line of output) {\n if (!eligible(line)) continue\n const lineage: string[] = []\n const visited = new Set<string>([line.lineId])\n let parent = line.parentLineId\n while (parent && !visited.has(parent)) {\n visited.add(parent); lineage.push(parent); parent = byId.get(parent)?.parentLineId ?? null\n }\n // Declared before use: `.sort()` below calls it eagerly (a `const` after it would hit the TDZ).\n const rank = (row: PriceAdjustment) =>\n row.level === 'charge' ? 0 : row.quoteLineId === line.lineId ? 1 : 2 + lineage.indexOf(row.quoteLineId)\n for (const charge of line.charges) {\n const candidates = adjustments.filter((row) => !invalid.has(row.id) && (\n row.level === 'charge'\n ? row.quoteLineId === line.lineId && row.chargeCode === charge.chargeCode\n : (row.quoteLineId === line.lineId || lineage.includes(row.quoteLineId)) &&\n (!row.chargeTypeFilter || row.chargeTypeFilter === charge.chargeType)\n )).sort((a, b) => rank(a) - rank(b) || a.id.localeCompare(b.id))\n const standard = charge.standardUnitPrice!\n const total = charge.standardTotalPrice\n const valid = candidates.filter((row) => {\n // Line discounts cover billable NRC/MRC, never metered usage.\n if (charge.chargeType === 'usage' && row.level === 'line') return false\n let code: AdjustmentError['code'] | undefined\n if (charge.chargeType === 'usage' || total === null || !Number.isFinite(row.value) || row.value < 0 ||\n (row.adjustmentType === 'price_override' && charge.pricingMethod !== 'flat' && charge.pricingMethod !== 'per_unit') ||\n (row.level === 'line' && row.adjustmentType !== 'discount_percent') ||\n (row.adjustmentType === 'discount_percent' && (row.value <= 0 || row.value > 100)) ||\n (row.adjustmentType === 'discount_absolute' && row.value <= 0)) code = 'TYPE_NOT_ALLOWED'\n else if ((row.currencyCode && row.currencyCode !== currencyCode) || charge.currencyCode !== currencyCode ||\n (row.adjustmentType !== 'discount_percent' && !row.currencyCode)) code = 'CURRENCY_MISMATCH'\n else if (row.adjustmentType === 'price_override' && row.value > standard) code = 'ABOVE_STANDARD'\n if (code) addError(row, code, charge.chargeCode, line.lineId)\n return !code\n })\n let applied = false\n for (const row of valid) {\n if (valid.filter((other) => rank(other) === rank(row)).length > 1) {\n addError(row, 'ADJUSTMENT_OVERLAP', charge.chargeCode, line.lineId)\n continue\n }\n if (applied) continue\n const quantity = charge.quantity ?? 1\n const afterTotal = row.adjustmentType === 'discount_absolute' ? Math.max(0, total! - row.value)\n : row.adjustmentType === 'discount_percent' ? Math.max(0, total! * (1 - row.value / 100))\n : row.value * quantity\n const afterUnit = row.adjustmentType === 'discount_percent' ? standard * (1 - row.value / 100)\n : row.adjustmentType === 'price_override' ? row.value : quantity > 0 ? afterTotal / quantity : 0\n charge.unitPrice = afterUnit\n charge.totalPrice = afterTotal\n charge.adjustments.push({\n origin: 'manual', adjustmentId: row.id, class: 'rep', level: row.level,\n windowKind: 'relative', fromMonth: 1, toMonth: null,\n ruleCode: row.id, ruleName: row.reasonCode, ruleType: row.adjustmentType,\n reasonCode: row.reasonCode, createdByUserId: row.createdByUserId,\n value: row.value, unitPriceBefore: standard, unitPriceAfter: afterUnit, delta: afterUnit - standard,\n })\n applied = true\n }\n }\n }\n return { lines: output, errors }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { adjustmentErrorSchema } from "../../data/price-adjustments.js";
|
|
2
|
+
function recordedError(value, fallback) {
|
|
3
|
+
if (!value || typeof value !== "object") return null;
|
|
4
|
+
const record = value;
|
|
5
|
+
const parsed = adjustmentErrorSchema.shape.code.safeParse(record.code ?? record.message);
|
|
6
|
+
return parsed.success ? { lineId: typeof record.lineId === "string" ? record.lineId : fallback, code: parsed.data } : null;
|
|
7
|
+
}
|
|
8
|
+
function storedAdjustmentFailure(quote, lines) {
|
|
9
|
+
for (const line of lines) {
|
|
10
|
+
for (const charge of line.charges ?? []) {
|
|
11
|
+
if (Array.isArray(charge.adjustmentErrors) && charge.adjustmentErrors.length) {
|
|
12
|
+
return recordedError(charge.adjustmentErrors[0], line.quoteLineId) ?? { lineId: line.quoteLineId, code: "ADJUSTMENT_INVALID" };
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
for (const error of line.validationErrors ?? []) {
|
|
16
|
+
const failure = recordedError(error, line.quoteLineId);
|
|
17
|
+
if (failure) return failure;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const errors = quote.validationResult?.errors;
|
|
21
|
+
if (Array.isArray(errors)) for (const error of errors) {
|
|
22
|
+
const failure = recordedError(error, null);
|
|
23
|
+
if (failure) return failure;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
storedAdjustmentFailure
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=storedAdjustmentFailure.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CpqQuoteConfiguration, CpqQuoteLineConfiguration } from '../../data/entities'\nimport { adjustmentErrorSchema } from '../../data/price-adjustments'\n\ntype Failure = { lineId: string | null; code: string }\nfunction recordedError(value: unknown, fallback: string | null): Failure | null {\n if (!value || typeof value !== 'object') return null\n const record = value as Record<string, unknown>\n const parsed = adjustmentErrorSchema.shape.code.safeParse(record.code ?? record.message)\n return parsed.success ? { lineId: typeof record.lineId === 'string' ? record.lineId : fallback, code: parsed.data } : null\n}\n\n/** Conversion validates the reviewed stored snapshot, without calling the price engine. */\nexport function storedAdjustmentFailure(quote: CpqQuoteConfiguration, lines: CpqQuoteLineConfiguration[]): Failure | null {\n for (const line of lines) {\n for (const charge of line.charges ?? []) {\n if (Array.isArray(charge.adjustmentErrors) && charge.adjustmentErrors.length) {\n return recordedError(charge.adjustmentErrors[0], line.quoteLineId) ?? { lineId: line.quoteLineId, code: 'ADJUSTMENT_INVALID' }\n }\n }\n for (const error of line.validationErrors ?? []) {\n const failure = recordedError(error, line.quoteLineId)\n if (failure) return failure\n }\n }\n const errors = quote.validationResult?.errors\n if (Array.isArray(errors)) for (const error of errors) {\n const failure = recordedError(error, null)\n if (failure) return failure\n }\n return null\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,6BAA6B;AAGtC,SAAS,cAAc,OAAgB,UAAyC;AAC9E,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,SAAS;AACf,QAAM,SAAS,sBAAsB,MAAM,KAAK,UAAU,OAAO,QAAQ,OAAO,OAAO;AACvF,SAAO,OAAO,UAAU,EAAE,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS,UAAU,MAAM,OAAO,KAAK,IAAI;AACxH;AAGO,SAAS,wBAAwB,OAA8B,OAAoD;AACxH,aAAW,QAAQ,OAAO;AACxB,eAAW,UAAU,KAAK,WAAW,CAAC,GAAG;AACvC,UAAI,MAAM,QAAQ,OAAO,gBAAgB,KAAK,OAAO,iBAAiB,QAAQ;AAC5E,eAAO,cAAc,OAAO,iBAAiB,CAAC,GAAG,KAAK,WAAW,KAAK,EAAE,QAAQ,KAAK,aAAa,MAAM,qBAAqB;AAAA,MAC/H;AAAA,IACF;AACA,eAAW,SAAS,KAAK,oBAAoB,CAAC,GAAG;AAC/C,YAAM,UAAU,cAAc,OAAO,KAAK,WAAW;AACrD,UAAI,QAAS,QAAO;AAAA,IACtB;AAAA,EACF;AACA,QAAM,SAAS,MAAM,kBAAkB;AACvC,MAAI,MAAM,QAAQ,MAAM,EAAG,YAAW,SAAS,QAAQ;AACrD,UAAM,UAAU,cAAc,OAAO,IAAI;AACzC,QAAI,QAAS,QAAO;AAAA,EACtB;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/cpq/services/types.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\n// Charge model / pricing source live in the data layer (no ORM/DI coupling).\nimport type { ChargeModel } from '../data/charge-pricing'\n\n// \u2500\u2500\u2500 Shared types for CPQ Quoting v2 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface TenantScope {\n organizationId: string\n tenantId: string\n}\n\nconst DEFAULT_CURRENCY = 'USD'\n\n/**\n * Returns the base currency code for a given tenant scope.\n * Falls back to 'USD' if no base currency is found.\n */\nexport async function getBaseCurrencyCode(\n em: EntityManager,\n scope: TenantScope,\n): Promise<string> {\n try {\n const { Currency } = await import(\n '@open-mercato/core/modules/currencies/data/entities'\n )\n const base = await em.findOne(Currency, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isBase: true,\n deletedAt: null,\n })\n return base?.code ?? DEFAULT_CURRENCY\n } catch {\n return DEFAULT_CURRENCY\n }\n}\n\n// \u2500\u2500\u2500 Quote lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const CPQ_STATUSES = [\n 'new',\n 'incomplete',\n 'ready',\n 'in_approval',\n 'pre_approved',\n 'approved',\n 'with_customer',\n 'accepted',\n 'rejected',\n 'cancelled',\n] as const\n\nexport type CpqStatus = (typeof CPQ_STATUSES)[number]\n\nexport const TERMINAL_STATUSES: CpqStatus[] = ['accepted', 'rejected', 'cancelled']\n\n// Per product decision: the CPQ status path is operator-driven and can\n// jump between any pair of statuses. Self-transitions are excluded\n// because they're no-ops. ARC-specific guards (`validateArcQuote`,\n// concurrent-ARC checks) still run inside `transitionStatus` and are\n// the real safety net \u2014 this table only enforces the \"no self loop\"\n// invariant.\nexport const ALLOWED_TRANSITIONS: Record<CpqStatus, CpqStatus[]> = Object.fromEntries(\n CPQ_STATUSES.map((from) => [from, CPQ_STATUSES.filter((to) => to !== from)]),\n) as Record<CpqStatus, CpqStatus[]>\n\n// \u2500\u2500\u2500 Service input/output types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CreateQuoteInput {\n customerId: string\n quoteId?: string // Link to existing SalesQuote instead of creating a new one\n dealId?: string\n currencyCode?: string\n quoteContext?: Record<string, string | number | boolean>\n}\n\nexport interface QuoteItemInput {\n offeringId?: string\n productId?: string\n configuration: Record<string, unknown>\n quantity?: number\n usageEstimates?: Array<{ chargeCode: string; estimatedQuantity: number }>\n action?: 'add' | 'modify' | 'cancel'\n parentLineId?: string\n startDate?: string\n termMonths?: number\n endDate?: string\n /** XD-250 ARC: target subscription this line acts on. Auto-inherited from\n * the quote's single attached target when omitted. Required when the ARC\n * quote has multiple non-merge targets (operator must pick). */\n targetSubscriptionId?: string | null\n}\n\nexport interface ValidationError {\n type?: 'attribute' | 'relationship'\n attribute?: string\n relationshipType?: string\n sourceSpec?: string\n targetSpec?: string\n message: string\n}\n\n/** XD-250 ARC: snapshot of the source subscription item's *current* state.\n * Surfaced on quote / order lines so the operator can compare \"before\" (this\n * snapshot) vs \"after\" (the line's current configuration). Null for lines\n * that aren't ARC-mirrored (e.g. a fresh `add` line on an amend quote). */\nexport interface ArcLineSource {\n subscriptionItemId: string\n name: string\n mrcAmount: number\n nrcAmount: number\n quantity: number\n}\n\nexport interface QuoteLineResult {\n lineId: string\n offeringId: string | null\n offeringName: string\n offeringType: string | null\n productId: string | null\n action: string\n parentLineId: string | null\n quantity: number\n configuration: Record<string, unknown>\n startDate: string | null\n termMonths: number | null\n endDate: string | null\n nrcTotal: number\n mrcTotal: number\n usageEstimates: Array<{ chargeCode: string; estimatedQuantity: number; estimatedTotal: number }> | null\n usageTotalEstimated: number | null\n charges: ResolvedCharge[]\n isConfigured: boolean\n validationErrors: ValidationError[] | null\n arcSource: ArcLineSource | null\n /** XD-250 multi-target ARC: which target subscription this line acts on. */\n targetSubscriptionId: string | null\n}\n\nexport interface PricingSummary {\n nrcTotal: number\n mrcTotal: number\n usageCharges: Array<{\n chargeCode: string\n chargeName: string\n unitPrice: number\n note: string | null\n }>\n usageTotalEstimated: number | null\n discountTotal: number\n surchargeTotal: number\n currencyCode: string\n}\n\nexport interface QuoteResult {\n id: string\n quoteId: string\n quoteNumber: string\n customerId: string\n /** Resolved customer display name (CustomerEntity.displayName). */\n customerName: string | null\n cpqStatus: string\n version: number\n parentQuoteId: string | null\n currencyCode: string\n /**\n * Set when this quote has been converted to a CPQ order. UI uses this\n * to lock editing and surface a \"Go to order\" entry point instead of\n * the Convert button. Backed by `CpqOrderConfiguration.sourceQuoteId`.\n */\n convertedOrderId: string | null\n validationResult: {\n valid: boolean\n errors: ValidationError[]\n }\n pricingSummary: PricingSummary\n lines: QuoteLineResult[]\n}\n\n// \u2500\u2500\u2500 Resolved charge (extended for v2) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface RuleAdjustment {\n ruleCode: string\n ruleName: string\n ruleType: string\n value: number\n unitPriceBefore: number\n unitPriceAfter: number\n delta: number\n}\n\nexport interface TierBreakdown {\n tiers: Array<{\n tier: number\n from: number\n to: number\n pricePerUnit: number\n quantity: number\n subtotal: number\n }>\n}\n\nexport interface ResolvedCharge {\n chargeCode: string\n chargeName: string\n chargeType: 'nrc' | 'mrc' | 'usage'\n // Display axis \u2014 the charge model (flat | per_unit | volume | tiered).\n pricingMethod: ChargeModel\n unitPrice: number\n quantity: number | null\n totalPrice: number | null\n currencyCode: string\n breakdown?: TierBreakdown | null\n note?: string | null\n adjustments?: RuleAdjustment[] | null\n}\n\n// \u2500\u2500\u2500 Inventory status constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const INVENTORY_SUBSCRIPTION_STATUSES = [\n 'pending',\n 'active',\n 'suspended',\n 'terminated',\n 'expired',\n 'superseded',\n] as const\nexport type InventorySubscriptionStatus = (typeof INVENTORY_SUBSCRIPTION_STATUSES)[number]\n\nexport const INVENTORY_SUBSCRIPTION_TRANSITIONS: Record<InventorySubscriptionStatus, InventorySubscriptionStatus[]> = {\n pending: ['active', 'terminated'],\n active: ['suspended', 'terminated', 'expired', 'superseded'],\n suspended: ['active', 'terminated', 'superseded'],\n terminated: [],\n expired: [],\n superseded: [],\n}\n\nexport const INVENTORY_SUBSCRIPTION_ITEM_STATUSES = [\n 'pending',\n 'active',\n 'suspended',\n 'terminated',\n 'expired',\n 'superseded',\n] as const\nexport type InventorySubscriptionItemStatus = (typeof INVENTORY_SUBSCRIPTION_ITEM_STATUSES)[number]\n\nexport const INVENTORY_ASSET_STATUSES = ['pending', 'delivered', 'active', 'returned', 'cancelled'] as const\nexport type InventoryAssetStatus = (typeof INVENTORY_ASSET_STATUSES)[number]\n\nexport const INVENTORY_ASSET_TRANSITIONS: Record<InventoryAssetStatus, InventoryAssetStatus[]> = {\n pending: ['delivered', 'active', 'cancelled'],\n delivered: ['active', 'returned'],\n active: ['returned'],\n returned: [],\n cancelled: [],\n}\n\n// \u2500\u2500\u2500 Order status constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const CPQ_ORDER_STATUSES = ['draft', 'pending_activation', 'active', 'cancelled', 'fulfilled'] as const\nexport type CpqOrderStatus = (typeof CPQ_ORDER_STATUSES)[number]\n\nexport const CPQ_ORDER_TRANSITIONS: Record<CpqOrderStatus, CpqOrderStatus[]> = {\n draft: ['pending_activation', 'active', 'cancelled'],\n pending_activation: ['active', 'cancelled'],\n active: ['fulfilled', 'cancelled'],\n cancelled: [],\n fulfilled: [],\n}\n\n// \u2500\u2500\u2500 ARC (Amend / Renew / Cancel) constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const CPQ_QUOTE_TYPES = ['new', 'amend', 'renew', 'cancel'] as const\nexport type CpqQuoteType = (typeof CPQ_QUOTE_TYPES)[number]\n\nexport const ARC_QUOTE_TYPES: ReadonlyArray<Exclude<CpqQuoteType, 'new'>> = ['amend', 'renew', 'cancel']\n\nexport const CHANGE_LOG_TYPES = [\n 'created',\n 'amend',\n 'renew',\n 'cancel',\n 'merge-result',\n 'merge-source',\n] as const\nexport type ChangeLogType = (typeof CHANGE_LOG_TYPES)[number]\n\nexport const MERGE_ACTIONS = ['standalone', 'absorb'] as const\nexport type MergeAction = (typeof MERGE_ACTIONS)[number]\n\nexport const ARC_REASON_CODES = [\n 'upgrade',\n 'downgrade',\n 'config-change',\n 'price-adjustment',\n 'term-extension',\n 'term-reduction',\n 'consolidation',\n 'customer-request',\n 'non-payment',\n 'contract-breach',\n 'other',\n] as const\nexport type ArcReasonCode = (typeof ARC_REASON_CODES)[number]\n"],
|
|
5
|
-
"mappings": "AAWA,MAAM,mBAAmB;AAMzB,eAAsB,oBACpB,IACA,OACiB;AACjB,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,OACzB,qDACF;AACA,UAAM,OAAO,MAAM,GAAG,QAAQ,UAAU;AAAA,MACtC,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ;AAAA,MACR,WAAW;AAAA,IACb,CAAC;AACD,WAAO,MAAM,QAAQ;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIO,MAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,MAAM,oBAAiC,CAAC,YAAY,YAAY,WAAW;AAQ3E,MAAM,sBAAsD,OAAO;AAAA,EACxE,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,aAAa,OAAO,CAAC,OAAO,OAAO,IAAI,CAAC,CAAC;AAC7E;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\n// Charge model / pricing source live in the data layer (no ORM/DI coupling).\nimport type { ChargeModel } from '../data/charge-pricing'\n\n// \u2500\u2500\u2500 Shared types for CPQ Quoting v2 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface TenantScope {\n organizationId: string\n tenantId: string\n}\n\nconst DEFAULT_CURRENCY = 'USD'\n\n/**\n * Returns the base currency code for a given tenant scope.\n * Falls back to 'USD' if no base currency is found.\n */\nexport async function getBaseCurrencyCode(\n em: EntityManager,\n scope: TenantScope,\n): Promise<string> {\n try {\n const { Currency } = await import(\n '@open-mercato/core/modules/currencies/data/entities'\n )\n const base = await em.findOne(Currency, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n isBase: true,\n deletedAt: null,\n })\n return base?.code ?? DEFAULT_CURRENCY\n } catch {\n return DEFAULT_CURRENCY\n }\n}\n\n// \u2500\u2500\u2500 Quote lifecycle \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const CPQ_STATUSES = [\n 'new',\n 'incomplete',\n 'ready',\n 'in_approval',\n 'pre_approved',\n 'approved',\n 'with_customer',\n 'accepted',\n 'rejected',\n 'cancelled',\n] as const\n\nexport type CpqStatus = (typeof CPQ_STATUSES)[number]\n\nexport const TERMINAL_STATUSES: CpqStatus[] = ['accepted', 'rejected', 'cancelled']\n\n// Per product decision: the CPQ status path is operator-driven and can\n// jump between any pair of statuses. Self-transitions are excluded\n// because they're no-ops. ARC-specific guards (`validateArcQuote`,\n// concurrent-ARC checks) still run inside `transitionStatus` and are\n// the real safety net \u2014 this table only enforces the \"no self loop\"\n// invariant.\nexport const ALLOWED_TRANSITIONS: Record<CpqStatus, CpqStatus[]> = Object.fromEntries(\n CPQ_STATUSES.map((from) => [from, CPQ_STATUSES.filter((to) => to !== from)]),\n) as Record<CpqStatus, CpqStatus[]>\n\n// \u2500\u2500\u2500 Service input/output types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface CreateQuoteInput {\n customerId: string\n quoteId?: string // Link to existing SalesQuote instead of creating a new one\n dealId?: string\n currencyCode?: string\n quoteContext?: Record<string, string | number | boolean>\n}\n\nexport interface QuoteItemInput {\n offeringId?: string\n productId?: string\n configuration: Record<string, unknown>\n quantity?: number\n usageEstimates?: Array<{ chargeCode: string; estimatedQuantity: number }>\n action?: 'add' | 'modify' | 'cancel'\n parentLineId?: string\n startDate?: string\n termMonths?: number\n endDate?: string\n /** XD-250 ARC: target subscription this line acts on. Auto-inherited from\n * the quote's single attached target when omitted. Required when the ARC\n * quote has multiple non-merge targets (operator must pick). */\n targetSubscriptionId?: string | null\n}\n\nexport interface ValidationError {\n lineId?: string\n adjustmentId?: string\n type?: 'attribute' | 'relationship'\n attribute?: string\n relationshipType?: string\n sourceSpec?: string\n targetSpec?: string\n message: string\n}\n\n/** XD-250 ARC: snapshot of the source subscription item's *current* state.\n * Surfaced on quote / order lines so the operator can compare \"before\" (this\n * snapshot) vs \"after\" (the line's current configuration). Null for lines\n * that aren't ARC-mirrored (e.g. a fresh `add` line on an amend quote). */\nexport interface ArcLineSource {\n subscriptionItemId: string\n name: string\n mrcAmount: number\n nrcAmount: number\n quantity: number\n}\n\nexport interface QuoteLineResult {\n lineId: string\n offeringId: string | null\n offeringName: string\n offeringType: string | null\n productId: string | null\n action: string\n parentLineId: string | null\n quantity: number\n configuration: Record<string, unknown>\n startDate: string | null\n termMonths: number | null\n endDate: string | null\n nrcTotal: number\n mrcTotal: number\n usageEstimates: Array<{ chargeCode: string; estimatedQuantity: number; estimatedTotal: number }> | null\n usageTotalEstimated: number | null\n charges: ResolvedCharge[]\n isConfigured: boolean\n validationErrors: ValidationError[] | null\n arcSource: ArcLineSource | null\n /** XD-250 multi-target ARC: which target subscription this line acts on. */\n targetSubscriptionId: string | null\n}\n\nexport interface PricingWaterfallSummary {\n listNrcTotal: number\n listMrcTotal: number\n standardNrcTotal: number\n standardMrcTotal: number\n ruleDiscountTotal: number\n manualDiscountTotal: number\n dealValue: { list: number; standard: number; offer: number }\n discountDepth: { rule: number | null; manual: number | null; total: number | null; maxLineManual: number | null }\n hasTimeBasedAdjustments: boolean\n snapshotHash: string\n}\n\nexport interface PricingSummary extends Partial<PricingWaterfallSummary> {\n nrcTotal: number\n mrcTotal: number\n usageCharges: Array<{\n chargeCode: string\n chargeName: string\n unitPrice: number\n note: string | null\n }>\n usageTotalEstimated: number | null\n discountTotal: number\n surchargeTotal: number\n currencyCode: string\n}\n\nexport interface QuoteResult {\n adjustmentErrors?: import('../data/price-adjustments').AdjustmentError[]\n pricingRevision?: number\n id: string\n quoteId: string\n quoteNumber: string\n customerId: string\n /** Resolved customer display name (CustomerEntity.displayName). */\n customerName: string | null\n cpqStatus: string\n version: number\n parentQuoteId: string | null\n currencyCode: string\n /**\n * Set when this quote has been converted to a CPQ order. UI uses this\n * to lock editing and surface a \"Go to order\" entry point instead of\n * the Convert button. Backed by `CpqOrderConfiguration.sourceQuoteId`.\n */\n convertedOrderId: string | null\n validationResult: {\n valid: boolean\n errors: ValidationError[]\n }\n pricingSummary: PricingSummary\n lines: QuoteLineResult[]\n}\n\n// \u2500\u2500\u2500 Resolved charge (extended for v2) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface RuleAdjustment {\n adjustmentId?: string\n class?: 'rep' | 'carried_over'\n level?: 'charge' | 'line' | 'quote'\n windowKind?: 'relative' | 'absolute'\n fromMonth?: number\n toMonth?: number | null\n reasonCode?: string\n createdByUserId?: string\n /** Missing on legacy snapshots. */\n origin?: 'rule' | 'manual'\n ruleCode: string\n ruleName: string\n ruleType: string\n value: number\n unitPriceBefore: number\n unitPriceAfter: number\n delta: number\n}\n\nexport interface TierBreakdown {\n tiers: Array<{\n tier: number\n from: number\n to: number\n pricePerUnit: number\n quantity: number\n subtotal: number\n }>\n}\n\nexport interface ResolvedCharge {\n chargeCode: string\n chargeName: string\n chargeType: 'nrc' | 'mrc' | 'usage'\n // Display axis \u2014 the charge model (flat | per_unit | volume | tiered).\n pricingMethod: ChargeModel\n unitPrice: number\n quantity: number | null\n totalPrice: number | null\n currencyCode: string\n /** Additive waterfall fields; absent on snapshots saved before negotiated pricing. */\n adjustmentErrors?: import('../data/price-adjustments').AdjustmentError[]\n listUnitPrice?: number\n listTotalPrice?: number | null\n standardUnitPrice?: number\n standardTotalPrice?: number | null\n breakdown?: TierBreakdown | null\n note?: string | null\n adjustments?: RuleAdjustment[] | null\n}\n\n// \u2500\u2500\u2500 Inventory status constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const INVENTORY_SUBSCRIPTION_STATUSES = [\n 'pending',\n 'active',\n 'suspended',\n 'terminated',\n 'expired',\n 'superseded',\n] as const\nexport type InventorySubscriptionStatus = (typeof INVENTORY_SUBSCRIPTION_STATUSES)[number]\n\nexport const INVENTORY_SUBSCRIPTION_TRANSITIONS: Record<InventorySubscriptionStatus, InventorySubscriptionStatus[]> = {\n pending: ['active', 'terminated'],\n active: ['suspended', 'terminated', 'expired', 'superseded'],\n suspended: ['active', 'terminated', 'superseded'],\n terminated: [],\n expired: [],\n superseded: [],\n}\n\nexport const INVENTORY_SUBSCRIPTION_ITEM_STATUSES = [\n 'pending',\n 'active',\n 'suspended',\n 'terminated',\n 'expired',\n 'superseded',\n] as const\nexport type InventorySubscriptionItemStatus = (typeof INVENTORY_SUBSCRIPTION_ITEM_STATUSES)[number]\n\nexport const INVENTORY_ASSET_STATUSES = ['pending', 'delivered', 'active', 'returned', 'cancelled'] as const\nexport type InventoryAssetStatus = (typeof INVENTORY_ASSET_STATUSES)[number]\n\nexport const INVENTORY_ASSET_TRANSITIONS: Record<InventoryAssetStatus, InventoryAssetStatus[]> = {\n pending: ['delivered', 'active', 'cancelled'],\n delivered: ['active', 'returned'],\n active: ['returned'],\n returned: [],\n cancelled: [],\n}\n\n// \u2500\u2500\u2500 Order status constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const CPQ_ORDER_STATUSES = ['draft', 'pending_activation', 'active', 'cancelled', 'fulfilled'] as const\nexport type CpqOrderStatus = (typeof CPQ_ORDER_STATUSES)[number]\n\nexport const CPQ_ORDER_TRANSITIONS: Record<CpqOrderStatus, CpqOrderStatus[]> = {\n draft: ['pending_activation', 'active', 'cancelled'],\n pending_activation: ['active', 'cancelled'],\n active: ['fulfilled', 'cancelled'],\n cancelled: [],\n fulfilled: [],\n}\n\n// \u2500\u2500\u2500 ARC (Amend / Renew / Cancel) constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const CPQ_QUOTE_TYPES = ['new', 'amend', 'renew', 'cancel'] as const\nexport type CpqQuoteType = (typeof CPQ_QUOTE_TYPES)[number]\n\nexport const ARC_QUOTE_TYPES: ReadonlyArray<Exclude<CpqQuoteType, 'new'>> = ['amend', 'renew', 'cancel']\n\nexport const CHANGE_LOG_TYPES = [\n 'created',\n 'amend',\n 'renew',\n 'cancel',\n 'merge-result',\n 'merge-source',\n] as const\nexport type ChangeLogType = (typeof CHANGE_LOG_TYPES)[number]\n\nexport const MERGE_ACTIONS = ['standalone', 'absorb'] as const\nexport type MergeAction = (typeof MERGE_ACTIONS)[number]\n\nexport const ARC_REASON_CODES = [\n 'upgrade',\n 'downgrade',\n 'config-change',\n 'price-adjustment',\n 'term-extension',\n 'term-reduction',\n 'consolidation',\n 'customer-request',\n 'non-payment',\n 'contract-breach',\n 'other',\n] as const\nexport type ArcReasonCode = (typeof ARC_REASON_CODES)[number]\n"],
|
|
5
|
+
"mappings": "AAWA,MAAM,mBAAmB;AAMzB,eAAsB,oBACpB,IACA,OACiB;AACjB,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,OACzB,qDACF;AACA,UAAM,OAAO,MAAM,GAAG,QAAQ,UAAU;AAAA,MACtC,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ;AAAA,MACR,WAAW;AAAA,IACb,CAAC;AACD,WAAO,MAAM,QAAQ;AAAA,EACvB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAIO,MAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAIO,MAAM,oBAAiC,CAAC,YAAY,YAAY,WAAW;AAQ3E,MAAM,sBAAsD,OAAO;AAAA,EACxE,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,aAAa,OAAO,CAAC,OAAO,OAAO,IAAI,CAAC,CAAC;AAC7E;AA4LO,MAAM,kCAAkC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,MAAM,qCAAyG;AAAA,EACpH,SAAS,CAAC,UAAU,YAAY;AAAA,EAChC,QAAQ,CAAC,aAAa,cAAc,WAAW,YAAY;AAAA,EAC3D,WAAW,CAAC,UAAU,cAAc,YAAY;AAAA,EAChD,YAAY,CAAC;AAAA,EACb,SAAS,CAAC;AAAA,EACV,YAAY,CAAC;AACf;AAEO,MAAM,uCAAuC;AAAA,EAClD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,MAAM,2BAA2B,CAAC,WAAW,aAAa,UAAU,YAAY,WAAW;AAG3F,MAAM,8BAAoF;AAAA,EAC/F,SAAS,CAAC,aAAa,UAAU,WAAW;AAAA,EAC5C,WAAW,CAAC,UAAU,UAAU;AAAA,EAChC,QAAQ,CAAC,UAAU;AAAA,EACnB,UAAU,CAAC;AAAA,EACX,WAAW,CAAC;AACd;AAIO,MAAM,qBAAqB,CAAC,SAAS,sBAAsB,UAAU,aAAa,WAAW;AAG7F,MAAM,wBAAkE;AAAA,EAC7E,OAAO,CAAC,sBAAsB,UAAU,WAAW;AAAA,EACnD,oBAAoB,CAAC,UAAU,WAAW;AAAA,EAC1C,QAAQ,CAAC,aAAa,WAAW;AAAA,EACjC,WAAW,CAAC;AAAA,EACZ,WAAW,CAAC;AACd;AAIO,MAAM,kBAAkB,CAAC,OAAO,SAAS,SAAS,QAAQ;AAG1D,MAAM,kBAA+D,CAAC,SAAS,SAAS,QAAQ;AAEhG,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,MAAM,gBAAgB,CAAC,cAAc,QAAQ;AAG7C,MAAM,mBAAmB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { seedPriceAdjustmentDefaults } from "./lib/priceAdjustmentDefaults.js";
|
|
1
2
|
const setup = {
|
|
3
|
+
async seedDefaults({ em, tenantId, organizationId }) {
|
|
4
|
+
if (tenantId && organizationId) await seedPriceAdjustmentDefaults(em, { tenantId, organizationId });
|
|
5
|
+
},
|
|
2
6
|
defaultRoleFeatures: {
|
|
3
7
|
admin: [
|
|
4
8
|
"cpq.quotes.view",
|
|
5
9
|
"cpq.quotes.manage",
|
|
10
|
+
"cpq.quotes.adjust_price",
|
|
6
11
|
"cpq.pricing.view",
|
|
7
12
|
"cpq.pricing.manage",
|
|
8
13
|
"cpq.products.view",
|
|
@@ -26,6 +31,7 @@ const setup = {
|
|
|
26
31
|
employee: [
|
|
27
32
|
"cpq.quotes.view",
|
|
28
33
|
"cpq.quotes.manage",
|
|
34
|
+
"cpq.quotes.adjust_price",
|
|
29
35
|
"cpq.pricing.view",
|
|
30
36
|
"cpq.products.view",
|
|
31
37
|
"cpq.specifications.view",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/cpq/setup.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\n\n/**\n * CPQ module setup. After XD-276 the cpq module ships with NO customer\n * demo data \u2014 that lives in app-level demo modules (`src/modules/demo_<x>/`)\n * which register use-case bundles via `cpq/lib/seeds/api.ts`. This keeps\n * `@dainamite/cpq` clean of vertical-specific seeds when it extracts to a\n * standalone npm package per SPEC-001.\n *\n * What still lives here:\n * - `defaultRoleFeatures` \u2014 required for any tenant that has cpq enabled\n * - structural defaults (
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import { seedPriceAdjustmentDefaults } from './lib/priceAdjustmentDefaults'\nimport type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'\n\n/**\n * CPQ module setup. After XD-276 the cpq module ships with NO customer\n * demo data \u2014 that lives in app-level demo modules (`src/modules/demo_<x>/`)\n * which register use-case bundles via `cpq/lib/seeds/api.ts`. This keeps\n * `@dainamite/cpq` clean of vertical-specific seeds when it extracts to a\n * standalone npm package per SPEC-001.\n *\n * What still lives here:\n * - `defaultRoleFeatures` \u2014 required for any tenant that has cpq enabled\n * - structural defaults (price adjustment reasons and rollout toggle)\n *\n * What used to live here and moved out:\n * - GIX product/pricing/charge/rule/bundle/wizard seed \u2192 `src/modules/demo_gix/`\n * - Acme/Globex demo customers + quotes \u2192 `src/modules/demo_gix/seeds/examples.ts`\n * - GIX wizard definition \u2192 `src/modules/demo_gix/seeds/wizards.ts`\n */\nexport const setup: ModuleSetupConfig = {\n async seedDefaults({ em, tenantId, organizationId }) {\n if (tenantId && organizationId) await seedPriceAdjustmentDefaults(em, { tenantId, organizationId })\n },\n defaultRoleFeatures: {\n admin: [\n 'cpq.quotes.view',\n 'cpq.quotes.manage',\n 'cpq.quotes.adjust_price',\n 'cpq.pricing.view',\n 'cpq.pricing.manage',\n 'cpq.products.view',\n 'cpq.products.manage',\n 'cpq.specifications.view',\n 'cpq.specifications.manage',\n 'cpq.offerings.view',\n 'cpq.offerings.manage',\n 'cpq.wizards.view',\n 'cpq.wizards.manage',\n 'cpq.inventory.view',\n 'cpq.inventory.manage',\n 'cpq.orders.view',\n 'cpq.orders.manage',\n // XD-250 ARC features \u2014 admins can drive every ARC flow.\n 'cpq.arc.amend.manage',\n 'cpq.arc.renew.manage',\n 'cpq.arc.cancel.manage',\n 'cpq.arc.changelog.view',\n ],\n employee: [\n 'cpq.quotes.view',\n 'cpq.quotes.manage',\n 'cpq.quotes.adjust_price',\n 'cpq.pricing.view',\n 'cpq.products.view',\n 'cpq.specifications.view',\n 'cpq.offerings.view',\n 'cpq.wizards.view',\n 'cpq.inventory.view',\n 'cpq.orders.view',\n // XD-250 ARC: anyone with cpq.quotes.manage drives ARC quotes.\n 'cpq.arc.amend.manage',\n 'cpq.arc.renew.manage',\n 'cpq.arc.cancel.manage',\n 'cpq.arc.changelog.view',\n ],\n },\n}\n\nexport default setup\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mCAAmC;AAmBrC,MAAM,QAA2B;AAAA,EACtC,MAAM,aAAa,EAAE,IAAI,UAAU,eAAe,GAAG;AACnD,QAAI,YAAY,eAAgB,OAAM,4BAA4B,IAAI,EAAE,UAAU,eAAe,CAAC;AAAA,EACpG;AAAA,EACA,qBAAqB;AAAA,IACnB,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,MAEA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dainamite/cpq",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Dainamite CPQ — Configure, Price, Quote engine for Open Mercato (configurable products, multi-charge pricing tables, guided quoting, customer inventory, orders).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/modules/cpq/index.js",
|