@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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,75 @@
|
|
|
1
1
|
# @dainamite/cpq
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- adcb50f: Negotiated prices on quotes (manual price adjustments) with a price waterfall.
|
|
8
|
+
|
|
9
|
+
- New table `cpq_quote_price_adjustments` and column
|
|
10
|
+
`cpq_quote_configurations.pricing_revision` (migration
|
|
11
|
+
`Migration20261110213052`). **Upgrade steps for existing tenants (required):**
|
|
12
|
+
`yarn mercato db migrate`, then `yarn mercato cpq seed-price-adjustments`
|
|
13
|
+
(rollout toggle + reason dictionary) and `yarn mercato auth sync-role-acls`
|
|
14
|
+
(`cpq.quotes.adjust_price` grant). Without them writes return `403` and the
|
|
15
|
+
UI hides the actions. New tenants get all three at initialization.
|
|
16
|
+
- Reps with `cpq.quotes.manage` + `cpq.quotes.adjust_price` can override a
|
|
17
|
+
charge's unit price, take an amount off a charge or give a percentage on a
|
|
18
|
+
charge or line (`/api/cpq/quotes/{quoteId}/price-adjustments`, plus a
|
|
19
|
+
no-write `preview`). Adjustments are permanent, never exceed the standard
|
|
20
|
+
price, never compound, and lock once the quote leaves New / Incomplete /
|
|
21
|
+
Ready. Rollout toggle `cpq.manual_price_adjustments` (seeded on) and reason
|
|
22
|
+
dictionary `cpq.price_adjustment_reason`.
|
|
23
|
+
- Charges gain additive `listUnitPrice`, `listTotalPrice`, `standardUnitPrice`,
|
|
24
|
+
`standardTotalPrice` and `adjustmentErrors`; adjustment entries gain
|
|
25
|
+
`origin: 'rule' | 'manual'`. `pricingSummary` gains list / standard totals,
|
|
26
|
+
rule and manual discount totals, deal value, discount depth and
|
|
27
|
+
`snapshotHash`; `QuoteResult` gains `pricingRevision`. `discountTotal` is
|
|
28
|
+
unchanged.
|
|
29
|
+
- New events `cpq.quote.pricing_changed` and `cpq.quote.status_changed`
|
|
30
|
+
(after commit, with the pricing snapshot) for project approval workflows.
|
|
31
|
+
- Order conversion copies stored prices and returns `422 ADJUSTMENT_INVALID`
|
|
32
|
+
when a stored adjustment is invalid. `POST /api/cpq/quotes/price` accepts
|
|
33
|
+
`includeWaterfall` and never applies negotiated prices.
|
|
34
|
+
- Quote, order and subscription pages show List / Offer prices and the
|
|
35
|
+
waterfall; the quote page adds the adjustment dialog and panel.
|
|
36
|
+
|
|
37
|
+
- 8d57b1b: Harden quote pricing ahead of manual price adjustments.
|
|
38
|
+
|
|
39
|
+
- `POST /api/cpq/quotes/{quoteId}/recalculate` now requires `cpq.quotes.manage`
|
|
40
|
+
(was `cpq.quotes.view`), and a saved recalculation (`save: true`) on an
|
|
41
|
+
accepted, rejected or cancelled quote returns `409`. Integrations that
|
|
42
|
+
recalculate with a view-only API key must be granted `cpq.quotes.manage`.
|
|
43
|
+
- Amend quotes no longer re-price items the operator does not touch: mirror
|
|
44
|
+
lines keep the subscription's stored price until their configuration,
|
|
45
|
+
quantity, term, dates or usage estimates change.
|
|
46
|
+
- Status transitions, line add / update / remove, saved recalculations and
|
|
47
|
+
quote-to-order conversion run every registered mutation guard
|
|
48
|
+
(resource kinds `cpq.quote` and `cpq.quote_line`), so projects can enforce
|
|
49
|
+
approvals. No behaviour change when no guard is registered.
|
|
50
|
+
|
|
51
|
+
## 0.5.0
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- d7ce539: Require Open Mercato `0.9.0`. Peer ranges move from `^0.6.7` to `^0.9.0` for
|
|
56
|
+
`@open-mercato/core`, `shared`, `ui`, `events` and `queue`, and the MikroORM
|
|
57
|
+
peers from `^7.1.5` to `^7.1.14`. Consumers on Open Mercato `0.6.x`–`0.8.x`
|
|
58
|
+
must upgrade before taking these versions.
|
|
59
|
+
|
|
60
|
+
`@dainamite/cpq` and `@dainamite/billing` now peer on `@tanstack/react-table`
|
|
61
|
+
`^9.0.0` (was `^8.0.0 || ^9.0.0`): Open Mercato 0.7 moved its `DataTable` to
|
|
62
|
+
TanStack Table v9, and list pages now import `LegacyColumnDef` from
|
|
63
|
+
`@tanstack/react-table/legacy`, which v8 does not have.
|
|
64
|
+
|
|
65
|
+
`@dainamite/cpq` also fixes CPQ quote and order API routes answering `500`
|
|
66
|
+
instead of the service's typed status (e.g. `404 Quote not found`). Next
|
|
67
|
+
bundles the package once per server layer and the DI container is shared, so
|
|
68
|
+
the route could catch a `QuotingError` / `OrderError` from the other copy of
|
|
69
|
+
the class and `instanceof` failed. Both classes now carry a `Symbol.for`
|
|
70
|
+
brand; new `isQuotingError` / `isOrderError` guards are exported alongside
|
|
71
|
+
them and should be used instead of `instanceof` in route handlers.
|
|
72
|
+
|
|
3
73
|
## 0.4.0
|
|
4
74
|
|
|
5
75
|
### Minor Changes
|
package/MIGRATION.md
CHANGED
|
@@ -166,6 +166,107 @@ the `@dainamite/cpq` package, per
|
|
|
166
166
|
|
|
167
167
|
## Schema / API changelog (track breaking changes here)
|
|
168
168
|
|
|
169
|
+
### 2026-11-10 — Manual price adjustments and price waterfall (price adjustments, Phase 1)
|
|
170
|
+
|
|
171
|
+
Spec: `.ai/specs/2026-11-10-cpq-manual-price-adjustments.md`. Manual:
|
|
172
|
+
`manuals/cpq-price-adjustments.md`.
|
|
173
|
+
|
|
174
|
+
**Schema (additive).** Migration `Migration20261110213052` adds
|
|
175
|
+
`cpq_quote_price_adjustments` (tenant-scoped, soft-deleted, FK-string ids only)
|
|
176
|
+
and `cpq_quote_configurations.pricing_revision int default 0`. Rollback drops
|
|
177
|
+
both. No data migration: existing quotes keep their stored charges; list /
|
|
178
|
+
standard prices are derived for responses until the next saved recalculation.
|
|
179
|
+
|
|
180
|
+
**Upgrade steps for existing tenants (required).** The toggle, the reason
|
|
181
|
+
dictionary and the role grant are created by `setup.ts` at tenant
|
|
182
|
+
initialization only. After `yarn mercato db migrate`, run
|
|
183
|
+
`yarn mercato cpq seed-price-adjustments` (idempotent; `--tenant` / `--org`
|
|
184
|
+
to target one) and `yarn mercato auth sync-role-acls`. Skipping them leaves the
|
|
185
|
+
feature fail-closed: writes `403`, actions hidden, and every reason
|
|
186
|
+
`REASON_NOT_FOUND` if the toggle is switched on by hand.
|
|
187
|
+
|
|
188
|
+
**Write serialization.** Every pricing write (line add / update / remove, ARC
|
|
189
|
+
target detach, saved recalculation, clone, status change, adjustment CRUD,
|
|
190
|
+
order conversion) runs in
|
|
191
|
+
a transaction that locks the quote row (`SELECT … FOR UPDATE`) and bumps
|
|
192
|
+
`pricing_revision`. Adjustment writes accept `expectedRevision` and return
|
|
193
|
+
`409 STALE_REVISION` on mismatch.
|
|
194
|
+
|
|
195
|
+
**Response shapes (additive).** See the changeset. `discountTotal` keeps its
|
|
196
|
+
old meaning (rule discounts only) for existing consumers; use
|
|
197
|
+
`ruleDiscountTotal` / `manualDiscountTotal` for the split.
|
|
198
|
+
|
|
199
|
+
**Events.** `cpq.quote.pricing_changed` fires after a committed save whose
|
|
200
|
+
`snapshotHash` changed; `cpq.quote.status_changed` after every committed status
|
|
201
|
+
change (`cause: manual | auto`). Quotes saved before this release have no
|
|
202
|
+
stored hash: their first save records a baseline silently. New and ARC quotes
|
|
203
|
+
start with an empty-quote hash, so their first priced line is a signal.
|
|
204
|
+
`transitionStatus(quoteId, status, scope, actorUserId?)` gained an optional
|
|
205
|
+
actor argument.
|
|
206
|
+
|
|
207
|
+
**Conversion.** A quote whose stored waterfall contains an invalid manual
|
|
208
|
+
adjustment can no longer be converted (`422 ADJUSTMENT_INVALID` with
|
|
209
|
+
`lineId` / `code`).
|
|
210
|
+
|
|
211
|
+
### 2026-11-10 — Quote pricing integrity (price adjustments, Phase 1)
|
|
212
|
+
|
|
213
|
+
Spec: `.ai/specs/2026-11-10-cpq-quote-pricing-integrity.md`. No schema change.
|
|
214
|
+
|
|
215
|
+
**Recalculate needs `cpq.quotes.manage` (breaking for view-only callers).**
|
|
216
|
+
`POST /api/cpq/quotes/{quoteId}/recalculate` required only `cpq.quotes.view`,
|
|
217
|
+
so a read-only user or API key could persist re-priced quotes. It now requires
|
|
218
|
+
`cpq.quotes.manage`. A saved recalculation (`save: true`) on an `accepted`,
|
|
219
|
+
`rejected` or `cancelled` quote returns `409` — an accepted quote's prices are
|
|
220
|
+
the basis for order conversion. Dry runs (`save: false`) on terminal quotes
|
|
221
|
+
still work for managers.
|
|
222
|
+
|
|
223
|
+
**Amend mirror lines are frozen (behaviour change).** Engine-priced mirror
|
|
224
|
+
lines on Amend quotes were re-priced at today's catalog price on every
|
|
225
|
+
recalculation, and activation wrote that price back to the subscription item.
|
|
226
|
+
They now carry `_arcPriceFrozen: true` in the line `configuration` and keep the
|
|
227
|
+
source item's stored charges until a pricing input of the line changes
|
|
228
|
+
(configuration — ARC markers and key order ignored — quantity, `termMonths`,
|
|
229
|
+
`startDate`, `endDate`, `usageEstimates`); then the marker is dropped and the
|
|
230
|
+
line is re-priced as before. Renew and Cancel quotes are unchanged. Amend
|
|
231
|
+
quotes created before this release keep the old behaviour. The marker never
|
|
232
|
+
reaches subscription items (stripped at activation) and mirror lines ignore any
|
|
233
|
+
stale `_arc*` keys on an item's configuration.
|
|
234
|
+
|
|
235
|
+
**Mutation guards on quote writes.** The status, items, recalculate and
|
|
236
|
+
order-conversion routes now call `runRouteMutationGuards`
|
|
237
|
+
(`@open-mercato/shared/lib/crud/route-mutation-guard`) with resource kinds
|
|
238
|
+
`cpq.quote` (actions `transition_status`, `recalculate`, `convert_to_order`)
|
|
239
|
+
and `cpq.quote_line` (create / update / delete). A guard can block the write or
|
|
240
|
+
modify the line payload; after-success hooks run once the write commits.
|
|
241
|
+
These routes now export `openApi`.
|
|
242
|
+
|
|
243
|
+
### 2026-11-03 — Open Mercato 0.9.0 peers; `isQuotingError` / `isOrderError`
|
|
244
|
+
|
|
245
|
+
**Peer ranges (breaking for consumers).** `@open-mercato/*` `^0.9.0`,
|
|
246
|
+
MikroORM `^7.1.14`, `@tanstack/react-table` `^9.0.0` (list pages import
|
|
247
|
+
`LegacyColumnDef` from `@tanstack/react-table/legacy`, absent in v8). See
|
|
248
|
+
`.changeset/open-mercato-090-peers.md`.
|
|
249
|
+
|
|
250
|
+
**Do not use `instanceof QuotingError` / `instanceof OrderError` across the
|
|
251
|
+
DI boundary.** Next bundles this package once per server layer (page SSR vs
|
|
252
|
+
route handler) while the DI container is shared through `globalThis`, so a
|
|
253
|
+
route can catch an error built from the other copy of the class and
|
|
254
|
+
`instanceof` is `false` — a typed 404/409 becomes a 500. This was already
|
|
255
|
+
true before this release; it was just undetected. Both classes now carry a
|
|
256
|
+
`Symbol.for` brand, and two guards are exported next to them:
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { isQuotingError } from '@dainamite/cpq/modules/cpq/services/cpqQuotingService'
|
|
260
|
+
import { isOrderError } from '@dainamite/cpq/modules/cpq/services/cpqOrderService'
|
|
261
|
+
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (isQuotingError(err)) return NextResponse.json({ error: err.message }, { status: err.status })
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Consumer code that copied CPQ's old route catch blocks should switch to the
|
|
267
|
+
guards. `instanceof` remains fine inside the service layer itself, where the
|
|
268
|
+
thrower and the catcher are the same module copy.
|
|
269
|
+
|
|
169
270
|
### 2026-06-02 — XD-297 Charge model / pricing method split
|
|
170
271
|
|
|
171
272
|
Splits the conflated `cpq_product_charges.pricing_method` into two orthogonal
|
package/dist/modules/cpq/acl.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const features = [
|
|
2
|
+
{ id: "cpq.quotes.adjust_price", title: "Adjust CPQ quote prices", module: "cpq" },
|
|
2
3
|
{ id: "cpq.quotes.view", title: "View CPQ Quotes", module: "cpq" },
|
|
3
4
|
{ id: "cpq.quotes.manage", title: "Manage CPQ Quotes", module: "cpq" },
|
|
4
5
|
{ id: "cpq.pricing.view", title: "View Pricing", module: "cpq" },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/cpq/acl.ts"],
|
|
4
|
-
"sourcesContent": ["export const features = [\n { id: 'cpq.quotes.view', title: 'View CPQ Quotes', module: 'cpq' },\n { id: 'cpq.quotes.manage', title: 'Manage CPQ Quotes', module: 'cpq' },\n { id: 'cpq.pricing.view', title: 'View Pricing', module: 'cpq' },\n { id: 'cpq.pricing.manage', title: 'Manage Pricing', module: 'cpq' },\n { id: 'cpq.products.view', title: 'View CPQ Product Config', module: 'cpq' },\n { id: 'cpq.products.manage', title: 'Manage CPQ Product Config', module: 'cpq' },\n { id: 'cpq.specifications.view', title: 'View Product Specifications', module: 'cpq' },\n { id: 'cpq.specifications.manage', title: 'Manage Product Specifications', module: 'cpq' },\n { id: 'cpq.offerings.view', title: 'View Product Offerings', module: 'cpq' },\n { id: 'cpq.offerings.manage', title: 'Manage Product Offerings', module: 'cpq' },\n { id: 'cpq.wizards.view', title: 'View Wizards', module: 'cpq' },\n { id: 'cpq.wizards.manage', title: 'Manage Wizards', module: 'cpq' },\n { id: 'cpq.orders.view', title: 'View CPQ Orders', module: 'cpq' },\n { id: 'cpq.orders.manage', title: 'Manage CPQ Orders', module: 'cpq' },\n // XD-250 ARC (Amend / Renew / Cancel)\n { id: 'cpq.arc.amend.manage', title: 'Manage Amend Quotes', module: 'cpq' },\n { id: 'cpq.arc.renew.manage', title: 'Manage Renew Quotes', module: 'cpq' },\n { id: 'cpq.arc.cancel.manage', title: 'Manage Cancel Quotes', module: 'cpq' },\n { id: 'cpq.arc.changelog.view', title: 'View Subscription Change History', module: 'cpq' },\n // Expiring-subscriptions feature retired \u2014 the dedicated /expiring page\n // was replaced by an \"Expiring within (days)\" filter + saved perspective\n // on the main subscriptions list, which uses `cpq.inventory.view`.\n]\n\nexport default features\n"],
|
|
5
|
-
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACrE,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,qBAAqB,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,uBAAuB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,2BAA2B,OAAO,+BAA+B,QAAQ,MAAM;AAAA,EACrF,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACzF,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA;AAAA,EAErE,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,yBAAyB,OAAO,wBAAwB,QAAQ,MAAM;AAAA,EAC5E,EAAE,IAAI,0BAA0B,OAAO,oCAAoC,QAAQ,MAAM;AAAA;AAAA;AAAA;AAI3F;AAEA,IAAO,cAAQ;",
|
|
4
|
+
"sourcesContent": ["export const features = [\n { id: 'cpq.quotes.adjust_price', title: 'Adjust CPQ quote prices', module: 'cpq' },\n { id: 'cpq.quotes.view', title: 'View CPQ Quotes', module: 'cpq' },\n { id: 'cpq.quotes.manage', title: 'Manage CPQ Quotes', module: 'cpq' },\n { id: 'cpq.pricing.view', title: 'View Pricing', module: 'cpq' },\n { id: 'cpq.pricing.manage', title: 'Manage Pricing', module: 'cpq' },\n { id: 'cpq.products.view', title: 'View CPQ Product Config', module: 'cpq' },\n { id: 'cpq.products.manage', title: 'Manage CPQ Product Config', module: 'cpq' },\n { id: 'cpq.specifications.view', title: 'View Product Specifications', module: 'cpq' },\n { id: 'cpq.specifications.manage', title: 'Manage Product Specifications', module: 'cpq' },\n { id: 'cpq.offerings.view', title: 'View Product Offerings', module: 'cpq' },\n { id: 'cpq.offerings.manage', title: 'Manage Product Offerings', module: 'cpq' },\n { id: 'cpq.wizards.view', title: 'View Wizards', module: 'cpq' },\n { id: 'cpq.wizards.manage', title: 'Manage Wizards', module: 'cpq' },\n { id: 'cpq.orders.view', title: 'View CPQ Orders', module: 'cpq' },\n { id: 'cpq.orders.manage', title: 'Manage CPQ Orders', module: 'cpq' },\n // XD-250 ARC (Amend / Renew / Cancel)\n { id: 'cpq.arc.amend.manage', title: 'Manage Amend Quotes', module: 'cpq' },\n { id: 'cpq.arc.renew.manage', title: 'Manage Renew Quotes', module: 'cpq' },\n { id: 'cpq.arc.cancel.manage', title: 'Manage Cancel Quotes', module: 'cpq' },\n { id: 'cpq.arc.changelog.view', title: 'View Subscription Change History', module: 'cpq' },\n // Expiring-subscriptions feature retired \u2014 the dedicated /expiring page\n // was replaced by an \"Expiring within (days)\" filter + saved perspective\n // on the main subscriptions list, which uses `cpq.inventory.view`.\n]\n\nexport default features\n"],
|
|
5
|
+
"mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,EAAE,IAAI,2BAA2B,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EACjF,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA,EACrE,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,qBAAqB,OAAO,2BAA2B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,uBAAuB,OAAO,6BAA6B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,2BAA2B,OAAO,+BAA+B,QAAQ,MAAM;AAAA,EACrF,EAAE,IAAI,6BAA6B,OAAO,iCAAiC,QAAQ,MAAM;AAAA,EACzF,EAAE,IAAI,sBAAsB,OAAO,0BAA0B,QAAQ,MAAM;AAAA,EAC3E,EAAE,IAAI,wBAAwB,OAAO,4BAA4B,QAAQ,MAAM;AAAA,EAC/E,EAAE,IAAI,oBAAoB,OAAO,gBAAgB,QAAQ,MAAM;AAAA,EAC/D,EAAE,IAAI,sBAAsB,OAAO,kBAAkB,QAAQ,MAAM;AAAA,EACnE,EAAE,IAAI,mBAAmB,OAAO,mBAAmB,QAAQ,MAAM;AAAA,EACjE,EAAE,IAAI,qBAAqB,OAAO,qBAAqB,QAAQ,MAAM;AAAA;AAAA,EAErE,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,wBAAwB,OAAO,uBAAuB,QAAQ,MAAM;AAAA,EAC1E,EAAE,IAAI,yBAAyB,OAAO,wBAAwB,QAAQ,MAAM;AAAA,EAC5E,EAAE,IAAI,0BAA0B,OAAO,oCAAoC,QAAQ,MAAM;AAAA;AAAA;AAAA;AAI3F;AAEA,IAAO,cAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { serializeOperationMetadata } from "@open-mercato/shared/lib/commands/operationMetadata";
|
|
3
|
+
import { runCpqMutationGuards } from "./quoteMutationGuard.js";
|
|
4
|
+
async function adjustmentCommandRoute(req, ctx, quoteId, body, schema, operation, adjustmentId) {
|
|
5
|
+
const guard = await runCpqMutationGuards(ctx, req, {
|
|
6
|
+
resourceKind: "cpq.quote_price_adjustment",
|
|
7
|
+
resourceId: adjustmentId,
|
|
8
|
+
operation,
|
|
9
|
+
mutationPayload: { quoteId, ...body }
|
|
10
|
+
});
|
|
11
|
+
if (!guard.ok) return guard.response;
|
|
12
|
+
const modified = Object.fromEntries(Object.entries(guard.modifiedPayload ?? {}).filter(([key]) => key !== "quoteId" && key !== "adjustmentId"));
|
|
13
|
+
const effective = schema.parse({ ...body, ...modified });
|
|
14
|
+
const input = operation === "create" ? { quoteId, adjustment: effective } : operation === "update" ? { quoteId, adjustmentId, patch: effective } : { quoteId, adjustmentId, ...effective };
|
|
15
|
+
const commandCtx = {
|
|
16
|
+
container: ctx.container,
|
|
17
|
+
auth: ctx.auth,
|
|
18
|
+
organizationScope: ctx.organizationScope,
|
|
19
|
+
selectedOrganizationId: ctx.organizationId,
|
|
20
|
+
organizationIds: [ctx.organizationId],
|
|
21
|
+
request: req
|
|
22
|
+
};
|
|
23
|
+
const bus = ctx.container.resolve("commandBus");
|
|
24
|
+
const commandId = `cpq.quote_price_adjustment.${operation}`;
|
|
25
|
+
const { result, logEntry } = await bus.execute(commandId, { input, ctx: commandCtx });
|
|
26
|
+
if (!result) throw new Error("Adjustment command did not return a result");
|
|
27
|
+
await guard.runAfterSuccess();
|
|
28
|
+
const response = NextResponse.json(operation === "delete" ? { quote: result.quote } : { adjustment: result.adjustment, quote: result.quote }, { status: operation === "create" ? 201 : 200 });
|
|
29
|
+
if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {
|
|
30
|
+
response.headers.set("x-om-operation", serializeOperationMetadata({
|
|
31
|
+
id: logEntry.id,
|
|
32
|
+
undoToken: logEntry.undoToken,
|
|
33
|
+
commandId: logEntry.commandId,
|
|
34
|
+
actionLabel: logEntry.actionLabel ?? null,
|
|
35
|
+
resourceKind: "cpq.quote_price_adjustment",
|
|
36
|
+
resourceId: result.adjustment.id,
|
|
37
|
+
executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString() : typeof logEntry.createdAt === "string" ? logEntry.createdAt : (/* @__PURE__ */ new Date()).toISOString()
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
41
|
+
}
|
|
42
|
+
export {
|
|
43
|
+
adjustmentCommandRoute
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=adjustmentCommandRoute.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { z } from 'zod'\nimport type { CommandBus, CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { serializeOperationMetadata } from '@open-mercato/shared/lib/commands/operationMetadata'\nimport type { CpqRouteContext } from '../context'\nimport { runCpqMutationGuards } from './quoteMutationGuard'\nimport type { AdjustmentWriteResult } from '../../services/priceAdjustments/adjustmentService'\n\nexport async function adjustmentCommandRoute<T extends object>(\n req: Request, ctx: CpqRouteContext, quoteId: string, body: T,\n schema: z.ZodType<T>, operation: 'create' | 'update' | 'delete', adjustmentId?: string,\n): Promise<NextResponse | Response> {\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: 'cpq.quote_price_adjustment', resourceId: adjustmentId,\n operation, mutationPayload: { quoteId, ...body },\n })\n if (!guard.ok) return guard.response\n // Path identifiers belong to the server, even when a project guard returns\n // the full payload including the original quoteId metadata.\n const modified = Object.fromEntries(Object.entries(guard.modifiedPayload ?? {})\n .filter(([key]) => key !== 'quoteId' && key !== 'adjustmentId'))\n const effective = schema.parse({ ...body, ...modified })\n const input = operation === 'create' ? { quoteId, adjustment: effective }\n : operation === 'update' ? { quoteId, adjustmentId, patch: effective }\n : { quoteId, adjustmentId, ...effective }\n const commandCtx: CommandRuntimeContext = {\n container: ctx.container, auth: ctx.auth, organizationScope: ctx.organizationScope,\n selectedOrganizationId: ctx.organizationId, organizationIds: [ctx.organizationId], request: req,\n }\n const bus = ctx.container.resolve<CommandBus>('commandBus')\n const commandId = `cpq.quote_price_adjustment.${operation}`\n const { result, logEntry } = await bus.execute<typeof input, AdjustmentWriteResult>(commandId, { input, ctx: commandCtx })\n if (!result) throw new Error('Adjustment command did not return a result')\n await guard.runAfterSuccess()\n const response = NextResponse.json(operation === 'delete' ? { quote: result.quote }\n : { adjustment: result.adjustment, quote: result.quote }, { status: operation === 'create' ? 201 : 200 })\n if (logEntry?.undoToken && logEntry.id && logEntry.commandId) {\n response.headers.set('x-om-operation', serializeOperationMetadata({ id: logEntry.id, undoToken: logEntry.undoToken,\n commandId: logEntry.commandId, actionLabel: logEntry.actionLabel ?? null,\n resourceKind: 'cpq.quote_price_adjustment', resourceId: result.adjustment.id,\n executedAt: logEntry.createdAt instanceof Date ? logEntry.createdAt.toISOString()\n : typeof logEntry.createdAt === 'string' ? logEntry.createdAt : new Date().toISOString() }))\n }\n return response\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAG7B,SAAS,kCAAkC;AAE3C,SAAS,4BAA4B;AAGrC,eAAsB,uBACpB,KAAc,KAAsB,SAAiB,MACrD,QAAsB,WAA2C,cAC/B;AAClC,QAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,IACjD,cAAc;AAAA,IAA8B,YAAY;AAAA,IACxD;AAAA,IAAW,iBAAiB,EAAE,SAAS,GAAG,KAAK;AAAA,EACjD,CAAC;AACD,MAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAG5B,QAAM,WAAW,OAAO,YAAY,OAAO,QAAQ,MAAM,mBAAmB,CAAC,CAAC,EAC3E,OAAO,CAAC,CAAC,GAAG,MAAM,QAAQ,aAAa,QAAQ,cAAc,CAAC;AACjE,QAAM,YAAY,OAAO,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC;AACvD,QAAM,QAAQ,cAAc,WAAW,EAAE,SAAS,YAAY,UAAU,IACpE,cAAc,WAAW,EAAE,SAAS,cAAc,OAAO,UAAU,IACjE,EAAE,SAAS,cAAc,GAAG,UAAU;AAC5C,QAAM,aAAoC;AAAA,IACxC,WAAW,IAAI;AAAA,IAAW,MAAM,IAAI;AAAA,IAAM,mBAAmB,IAAI;AAAA,IACjE,wBAAwB,IAAI;AAAA,IAAgB,iBAAiB,CAAC,IAAI,cAAc;AAAA,IAAG,SAAS;AAAA,EAC9F;AACA,QAAM,MAAM,IAAI,UAAU,QAAoB,YAAY;AAC1D,QAAM,YAAY,8BAA8B,SAAS;AACzD,QAAM,EAAE,QAAQ,SAAS,IAAI,MAAM,IAAI,QAA6C,WAAW,EAAE,OAAO,KAAK,WAAW,CAAC;AACzH,MAAI,CAAC,OAAQ,OAAM,IAAI,MAAM,4CAA4C;AACzE,QAAM,MAAM,gBAAgB;AAC5B,QAAM,WAAW,aAAa,KAAK,cAAc,WAAW,EAAE,OAAO,OAAO,MAAM,IAC9E,EAAE,YAAY,OAAO,YAAY,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,cAAc,WAAW,MAAM,IAAI,CAAC;AAC1G,MAAI,UAAU,aAAa,SAAS,MAAM,SAAS,WAAW;AAC5D,aAAS,QAAQ,IAAI,kBAAkB,2BAA2B;AAAA,MAAE,IAAI,SAAS;AAAA,MAAI,WAAW,SAAS;AAAA,MACvG,WAAW,SAAS;AAAA,MAAW,aAAa,SAAS,eAAe;AAAA,MACpE,cAAc;AAAA,MAA8B,YAAY,OAAO,WAAW;AAAA,MAC1E,YAAY,SAAS,qBAAqB,OAAO,SAAS,UAAU,YAAY,IAC5E,OAAO,SAAS,cAAc,WAAW,SAAS,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IAAE,CAAC,CAAC;AAAA,EACjG;AACA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { OptimisticLockError } from "@mikro-orm/core";
|
|
4
|
-
import {
|
|
4
|
+
import { isQuotingError } from "../../services/cpqQuotingService.js";
|
|
5
5
|
function handleArcError(err, ctx) {
|
|
6
6
|
if (err instanceof z.ZodError) {
|
|
7
7
|
return NextResponse.json(
|
|
@@ -9,7 +9,7 @@ function handleArcError(err, ctx) {
|
|
|
9
9
|
{ status: 400 }
|
|
10
10
|
);
|
|
11
11
|
}
|
|
12
|
-
if (err
|
|
12
|
+
if (isQuotingError(err)) {
|
|
13
13
|
const body = { error: err.message };
|
|
14
14
|
if (err.cpqStatus) body.cpqStatus = err.cpqStatus;
|
|
15
15
|
if (err.details) Object.assign(body, err.details);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/cpq/api/_helpers/handleArcError.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,2BAA2B;AACpC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport { isQuotingError } from '../../services/cpqQuotingService'\n\n/**\n * Shared error \u2192 JSON-response converter for ARC routes (XD-250).\n *\n * Handles three families:\n * 1. Zod validation errors \u2192 400 with `details` array.\n * 2. Service-layer `QuotingError` (matched via its cross-realm brand,\n * `isQuotingError`, with a duck-typed fallback below) \u2192 propagates\n * `err.status`. `instanceof` is unreliable here: Next bundles this\n * package once per server layer, so the route and the DI-resolved\n * service can hold two distinct copies of the class \u2014 in production\n * too, not only under hot-reload.\n * 3. cpqInventoryService throws plain `{ status, error }` objects \u2192\n * propagate the embedded status.\n * 4. Anything else \u2192 500 + console.error with the route context.\n *\n * Returning the response keeps route handlers terse:\n * `} catch (err) { return handleArcError(err, 'cpq/quotes/from-subscription.POST') }`\n */\nexport function handleArcError(err: unknown, ctx: string): NextResponse {\n if (err instanceof z.ZodError) {\n return NextResponse.json(\n { error: 'Validation error', details: err.issues },\n { status: 400 },\n )\n }\n\n if (isQuotingError(err)) {\n const body: Record<string, unknown> = { error: err.message }\n if (err.cpqStatus) body.cpqStatus = err.cpqStatus\n if (err.details) Object.assign(body, err.details)\n return NextResponse.json(body, { status: err.status })\n }\n\n // MikroORM optimistic-lock collision on subscription.version (XD-250 spec\n // \u00A7 Risks \"Order activation racing with another mutation\"). Both\n // `instanceof OptimisticLockError` AND constructor-name fallback to\n // survive Turbopack class drift.\n if (\n err instanceof OptimisticLockError ||\n (err &&\n typeof err === 'object' &&\n typeof (err as { constructor?: { name?: string } }).constructor?.name === 'string' &&\n (err as { constructor: { name: string } }).constructor.name === 'OptimisticLockError')\n ) {\n return NextResponse.json(\n { error: 'Concurrent modification detected', details: (err as Error).message },\n { status: 409 },\n )\n }\n\n // cpqInventoryService's plain throw shape `{ status, error }` (QuotingError\n // from any bundle copy is already handled by `isQuotingError` above).\n // Propagates `details` (object) into the response body so structured\n // payloads survive here too.\n if (\n err &&\n typeof err === 'object' &&\n 'status' in err &&\n typeof (err as { status: unknown }).status === 'number'\n ) {\n const e = err as {\n status: number\n message?: string\n error?: string\n name?: string\n details?: Record<string, unknown>\n }\n const message = e.message ?? e.error ?? 'Conflict'\n const body: Record<string, unknown> = { error: message }\n if (e.details && typeof e.details === 'object') Object.assign(body, e.details)\n return NextResponse.json(body, { status: e.status })\n }\n\n console.error(`[${ctx}]`, err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAoBxB,SAAS,eAAe,KAAc,KAA2B;AACtE,MAAI,eAAe,EAAE,UAAU;AAC7B,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO;AAAA,MACjD,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAEA,MAAI,eAAe,GAAG,GAAG;AACvB,UAAM,OAAgC,EAAE,OAAO,IAAI,QAAQ;AAC3D,QAAI,IAAI,UAAW,MAAK,YAAY,IAAI;AACxC,QAAI,IAAI,QAAS,QAAO,OAAO,MAAM,IAAI,OAAO;AAChD,WAAO,aAAa,KAAK,MAAM,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,EACvD;AAMA,MACE,eAAe,uBACd,OACC,OAAO,QAAQ,YACf,OAAQ,IAA4C,aAAa,SAAS,YACzE,IAA0C,YAAY,SAAS,uBAClE;AACA,WAAO,aAAa;AAAA,MAClB,EAAE,OAAO,oCAAoC,SAAU,IAAc,QAAQ;AAAA,MAC7E,EAAE,QAAQ,IAAI;AAAA,IAChB;AAAA,EACF;AAMA,MACE,OACA,OAAO,QAAQ,YACf,YAAY,OACZ,OAAQ,IAA4B,WAAW,UAC/C;AACA,UAAM,IAAI;AAOV,UAAM,UAAU,EAAE,WAAW,EAAE,SAAS;AACxC,UAAM,OAAgC,EAAE,OAAO,QAAQ;AACvD,QAAI,EAAE,WAAW,OAAO,EAAE,YAAY,SAAU,QAAO,OAAO,MAAM,EAAE,OAAO;AAC7E,WAAO,aAAa,KAAK,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC;AAAA,EACrD;AAEA,UAAQ,MAAM,IAAI,GAAG,KAAK,GAAG;AAC7B,SAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AACvE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
runRouteMutationGuards
|
|
3
|
+
} from "@open-mercato/shared/lib/crud/route-mutation-guard";
|
|
4
|
+
const CPQ_QUOTE_RESOURCE_KIND = "cpq.quote";
|
|
5
|
+
const CPQ_QUOTE_LINE_RESOURCE_KIND = "cpq.quote_line";
|
|
6
|
+
function runCpqMutationGuards(ctx, req, input) {
|
|
7
|
+
return runRouteMutationGuards({
|
|
8
|
+
container: ctx.container,
|
|
9
|
+
req,
|
|
10
|
+
auth: {
|
|
11
|
+
userId: ctx.auth?.sub ?? "",
|
|
12
|
+
tenantId: ctx.tenantId,
|
|
13
|
+
organizationId: ctx.organizationId || null
|
|
14
|
+
},
|
|
15
|
+
input
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
CPQ_QUOTE_LINE_RESOURCE_KIND,
|
|
20
|
+
CPQ_QUOTE_RESOURCE_KIND,
|
|
21
|
+
runCpqMutationGuards
|
|
22
|
+
};
|
|
23
|
+
//# sourceMappingURL=quoteMutationGuard.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/api/_helpers/quoteMutationGuard.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n runRouteMutationGuards,\n type RouteMutationGuardInput,\n type RouteMutationGuardResult,\n} from '@open-mercato/shared/lib/crud/route-mutation-guard'\nimport type { CpqRouteContext } from '../context'\n\n/** Mutation-guard resource kinds for CPQ quote writes (`module.entity`). */\nexport const CPQ_QUOTE_RESOURCE_KIND = 'cpq.quote'\nexport const CPQ_QUOTE_LINE_RESOURCE_KIND = 'cpq.quote_line'\n\n/**\n * Run every registered mutation guard for a CPQ custom write route.\n *\n * Quote routes are hand-written (not `makeCrudRoute`), so they wire guards\n * explicitly through the framework's registry-based wrapper. Projects use\n * this hook to enforce rules CPQ does not own \u2014 e.g. blocking\n * `ready \u2192 with_customer` or order conversion until an approval workflow\n * signs a discounted quote off. With no guard registered the call passes.\n *\n * Call `runAfterSuccess()` on the passed result only after the write commits.\n */\nexport function runCpqMutationGuards(\n ctx: Pick<CpqRouteContext, 'container' | 'auth' | 'tenantId' | 'organizationId'>,\n req: Request,\n input: RouteMutationGuardInput,\n): Promise<RouteMutationGuardResult> {\n return runRouteMutationGuards({\n container: ctx.container,\n req,\n auth: {\n userId: ctx.auth?.sub ?? '',\n tenantId: ctx.tenantId,\n organizationId: ctx.organizationId || null,\n },\n input,\n })\n}\n"],
|
|
5
|
+
"mappings": "AAAA;AAAA,EACE;AAAA,OAGK;AAIA,MAAM,0BAA0B;AAChC,MAAM,+BAA+B;AAarC,SAAS,qBACd,KACA,KACA,OACmC;AACnC,SAAO,uBAAuB;AAAA,IAC5B,WAAW,IAAI;AAAA,IACf;AAAA,IACA,MAAM;AAAA,MACJ,QAAQ,IAAI,MAAM,OAAO;AAAA,MACzB,UAAU,IAAI;AAAA,MACd,gBAAgB,IAAI,kBAAkB;AAAA,IACxC;AAAA,IACA;AAAA,EACF,CAAC;AACH;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const cpqErrorResponseSchema = z.object({ error: z.string() }).passthrough();
|
|
3
|
+
function cpqErrorDoc(status, description) {
|
|
4
|
+
return { status, description, schema: cpqErrorResponseSchema };
|
|
5
|
+
}
|
|
6
|
+
const cpqQuoteIdPathParams = z.object({ quoteId: z.string().uuid() });
|
|
7
|
+
const CPQ_GUARDED_WRITE_ERRORS = [
|
|
8
|
+
cpqErrorDoc(400, "Validation error"),
|
|
9
|
+
cpqErrorDoc(404, "Quote or line not found"),
|
|
10
|
+
cpqErrorDoc(409, "Quote is in a terminal status"),
|
|
11
|
+
cpqErrorDoc(422, "Blocked by a mutation guard")
|
|
12
|
+
];
|
|
13
|
+
export {
|
|
14
|
+
CPQ_GUARDED_WRITE_ERRORS,
|
|
15
|
+
cpqErrorDoc,
|
|
16
|
+
cpqErrorResponseSchema,
|
|
17
|
+
cpqQuoteIdPathParams
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=quoteRouteOpenApi.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport type { OpenApiResponseDoc } from '@open-mercato/shared/lib/openapi'\n\n/** Error body every CPQ quote route returns (`{ error, ... }`). */\nexport const cpqErrorResponseSchema = z.object({ error: z.string() }).passthrough()\n\nexport function cpqErrorDoc(status: number, description: string): OpenApiResponseDoc {\n return { status, description, schema: cpqErrorResponseSchema }\n}\n\nexport const cpqQuoteIdPathParams = z.object({ quoteId: z.string().uuid() })\n\n/** Shared error docs for quote writes guarded by mutation guards. */\nexport const CPQ_GUARDED_WRITE_ERRORS: OpenApiResponseDoc[] = [\n cpqErrorDoc(400, 'Validation error'),\n cpqErrorDoc(404, 'Quote or line not found'),\n cpqErrorDoc(409, 'Quote is in a terminal status'),\n cpqErrorDoc(422, 'Blocked by a mutation guard'),\n]\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAIX,MAAM,yBAAyB,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY;AAE3E,SAAS,YAAY,QAAgB,aAAyC;AACnF,SAAO,EAAE,QAAQ,aAAa,QAAQ,uBAAuB;AAC/D;AAEO,MAAM,uBAAuB,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAGpE,MAAM,2BAAiD;AAAA,EAC5D,YAAY,KAAK,kBAAkB;AAAA,EACnC,YAAY,KAAK,yBAAyB;AAAA,EAC1C,YAAY,KAAK,+BAA+B;AAAA,EAChD,YAAY,KAAK,6BAA6B;AAChD;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { OptimisticLockError } from "@mikro-orm/core";
|
|
3
3
|
import { resolveCpqRouteContext } from "../../../context.js";
|
|
4
|
-
import {
|
|
4
|
+
import { isOrderError } from "../../../../services/cpqOrderService.js";
|
|
5
5
|
const metadata = {
|
|
6
6
|
POST: { requireAuth: true, requireFeatures: ["cpq.orders.manage"] }
|
|
7
7
|
};
|
|
@@ -15,7 +15,7 @@ async function POST(req, { params }) {
|
|
|
15
15
|
const result = await service.activateOrder(orderId, scope);
|
|
16
16
|
return NextResponse.json(result);
|
|
17
17
|
} catch (err) {
|
|
18
|
-
if (err
|
|
18
|
+
if (isOrderError(err)) {
|
|
19
19
|
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
20
20
|
}
|
|
21
21
|
if (err instanceof OptimisticLockError || err && typeof err === "object" && err.constructor?.name === "OptimisticLockError") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/cpq/api/orders/%5BorderId%5D/activate/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport { resolveCpqRouteContext } from '../../../context'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { OptimisticLockError } from '@mikro-orm/core'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { isOrderError } from '../../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.activateOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n // XD-250 ARC: optimistic-lock collision on subscription.version when two\n // ARC orders touching the same sub race to activate. Spec \u00A7 Risks:\n // \"second activation gets 409, order stays in pending_activation,\n // retryable\". Constructor-name fallback survives Turbopack class drift.\n if (\n err instanceof OptimisticLockError ||\n (err &&\n typeof err === 'object' &&\n (err as { constructor?: { name?: string } }).constructor?.name === 'OptimisticLockError')\n ) {\n return NextResponse.json(\n { error: 'Concurrent modification detected', details: (err as Error).message },\n { status: 409 },\n )\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error('[cpq/orders/[orderId]/activate.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,2BAA2B;AACpC,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAGtB,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,cAAc,SAAS,KAAK;AACzD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AAKA,QACE,eAAe,uBACd,OACC,OAAO,QAAQ,YACd,IAA4C,aAAa,SAAS,uBACrE;AACA,aAAO,aAAa;AAAA,QAClB,EAAE,OAAO,oCAAoC,SAAU,IAAc,QAAQ;AAAA,QAC7E,EAAE,QAAQ,IAAI;AAAA,MAChB;AAAA,IACF;AACA,UAAM,SAAS;AACf,QAAI,OAAO,QAAQ,WAAW,YAAY,OAAO,QAAQ,UAAU,UAAU;AAC3E,aAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,OAAO,OAAiB,CAAC;AAAA,IACvF;AACA,YAAQ,MAAM,wCAAwC,GAAG;AACzD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { cpqErrorDoc } from "../../_helpers/quoteRouteOpenApi.js";
|
|
2
4
|
import { resolveCpqRouteContext } from "../../context.js";
|
|
3
|
-
import {
|
|
5
|
+
import { isOrderError } from "../../../services/cpqOrderService.js";
|
|
4
6
|
const metadata = {
|
|
5
7
|
GET: { requireAuth: true, requireFeatures: ["cpq.orders.view"] }
|
|
6
8
|
};
|
|
@@ -14,15 +16,29 @@ async function GET(req, { params }) {
|
|
|
14
16
|
const result = await service.getOrder(orderId, scope);
|
|
15
17
|
return NextResponse.json(result);
|
|
16
18
|
} catch (err) {
|
|
17
|
-
if (err
|
|
19
|
+
if (isOrderError(err)) {
|
|
18
20
|
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
19
21
|
}
|
|
20
22
|
console.error("[cpq/orders/[orderId].GET]", err);
|
|
21
23
|
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
22
24
|
}
|
|
23
25
|
}
|
|
26
|
+
const openApi = {
|
|
27
|
+
tag: "CPQ Orders",
|
|
28
|
+
summary: "CPQ order",
|
|
29
|
+
pathParams: z.object({ orderId: z.string().uuid() }),
|
|
30
|
+
methods: {
|
|
31
|
+
GET: {
|
|
32
|
+
summary: "Get a CPQ order with its lines and pricing summary",
|
|
33
|
+
description: "`pricingSummary` carries NRC / MRC totals plus the source quote's waterfall figures (`listNrcTotal`, `listMrcTotal`, `standard*Total`, `ruleDiscountTotal`, `manualDiscountTotal`, `dealValue`, `discountDepth`), computed from the line charges copied at conversion. ARC lines carry `arcSource` (the subscription item before the change).",
|
|
34
|
+
responses: [{ status: 200, description: "The order" }],
|
|
35
|
+
errors: [cpqErrorDoc(401, "Unauthorized"), cpqErrorDoc(404, "Order not found")]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
24
39
|
export {
|
|
25
40
|
GET,
|
|
26
|
-
metadata
|
|
41
|
+
metadata,
|
|
42
|
+
openApi
|
|
27
43
|
};
|
|
28
44
|
//# sourceMappingURL=route.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/cpq/api/orders/%5BorderId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../context'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { cpqErrorDoc } from '../../_helpers/quoteRouteOpenApi'\nimport { resolveCpqRouteContext } from '../../context'\nimport { isOrderError } from '../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../services/cpqOrderService'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function GET(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.getOrder(orderId, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/orders/[orderId].GET]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Orders',\n summary: 'CPQ order',\n pathParams: z.object({ orderId: z.string().uuid() }),\n methods: {\n GET: {\n summary: 'Get a CPQ order with its lines and pricing summary',\n description:\n '`pricingSummary` carries NRC / MRC totals plus the source quote\\'s waterfall figures (`listNrcTotal`, `listMrcTotal`, `standard*Total`, `ruleDiscountTotal`, `manualDiscountTotal`, `dealValue`, `discountDepth`), computed from the line charges copied at conversion. ARC lines carry `arcSource` (the subscription item before the change).',\n responses: [{ status: 200, description: 'The order' }],\n errors: [cpqErrorDoc(401, 'Unauthorized'), cpqErrorDoc(404, 'Order not found')],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,mBAAmB;AAC5B,SAAS,8BAA8B;AACvC,SAAS,oBAAoB;AAGtB,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AACjE;AAEA,eAAsB,IAAI,KAAc,EAAE,OAAO,GAA6C;AAC5F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,SAAS,SAAS,KAAK;AACpD,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,8BAA8B,GAAG;AAC/C,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,EACnD,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,YAAY,CAAC;AAAA,MACrD,QAAQ,CAAC,YAAY,KAAK,cAAc,GAAG,YAAY,KAAK,iBAAiB,CAAC;AAAA,IAChF;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { resolveCpqRouteContext } from "../../../context.js";
|
|
4
4
|
import { cpqOrderStatusTransitionSchema } from "../../../../data/validators.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isOrderError } from "../../../../services/cpqOrderService.js";
|
|
6
6
|
const metadata = {
|
|
7
7
|
POST: { requireAuth: true, requireFeatures: ["cpq.orders.manage"] }
|
|
8
8
|
};
|
|
@@ -20,7 +20,7 @@ async function POST(req, { params }) {
|
|
|
20
20
|
if (err instanceof z.ZodError) {
|
|
21
21
|
return NextResponse.json({ error: "Validation error", details: err.issues }, { status: 400 });
|
|
22
22
|
}
|
|
23
|
-
if (err
|
|
23
|
+
if (isOrderError(err)) {
|
|
24
24
|
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
25
25
|
}
|
|
26
26
|
const errObj = err;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/cpq/api/orders/%5BorderId%5D/status/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqOrderStatusTransitionSchema } from '../../../../data/validators'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,sCAAsC;AAC/C,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqOrderStatusTransitionSchema } from '../../../../data/validators'\nimport { isOrderError } from '../../../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { orderId } = await params\n const body = cpqOrderStatusTransitionSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.transitionOrderStatus(orderId, body.targetStatus, scope)\n return NextResponse.json(result)\n } catch (err) {\n if (err instanceof z.ZodError) {\n return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })\n }\n if (isOrderError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n const errObj = err as Record<string, unknown>\n if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {\n return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })\n }\n console.error('[cpq/orders/[orderId]/status.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,sCAAsC;AAC/C,SAAS,oBAAoB;AAGtB,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6C;AAC7F,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAElF,UAAM,EAAE,QAAQ,IAAI,MAAM;AAC1B,UAAM,OAAO,+BAA+B,MAAM,MAAM,IAAI,KAAK,CAAC;AAClE,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,UAAU,IAAI,UAAU,QAAQ,iBAAiB;AACvD,UAAM,SAAS,MAAM,QAAQ,sBAAsB,SAAS,KAAK,cAAc,KAAK;AACpF,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,EAAE,UAAU;AAC7B,aAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,SAAS,IAAI,OAAO,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC9F;AACA,QAAI,aAAa,GAAG,GAAG;AACrB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,UAAM,SAAS;AACf,QAAI,OAAO,QAAQ,WAAW,YAAY,OAAO,QAAQ,UAAU,UAAU;AAC3E,aAAO,aAAa,KAAK,EAAE,OAAO,OAAO,MAAM,GAAG,EAAE,QAAQ,OAAO,OAAiB,CAAC;AAAA,IACvF;AACA,YAAQ,MAAM,sCAAsC,GAAG;AACvD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|