@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
|
@@ -2,7 +2,9 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { resolveCpqRouteContext } from "../context.js";
|
|
4
4
|
import { cpqConvertQuoteToOrderSchema } from "../../data/validators.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isOrderError } from "../../services/cpqOrderService.js";
|
|
6
|
+
import { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from "../_helpers/quoteMutationGuard.js";
|
|
7
|
+
import { cpqErrorDoc } from "../_helpers/quoteRouteOpenApi.js";
|
|
6
8
|
const metadata = {
|
|
7
9
|
POST: { requireAuth: true, requireFeatures: ["cpq.orders.manage"] },
|
|
8
10
|
GET: { requireAuth: true, requireFeatures: ["cpq.orders.view"] }
|
|
@@ -13,8 +15,16 @@ async function POST(req) {
|
|
|
13
15
|
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
14
16
|
const body = cpqConvertQuoteToOrderSchema.parse(await req.json());
|
|
15
17
|
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
|
|
18
|
+
const guard = await runCpqMutationGuards(ctx, req, {
|
|
19
|
+
resourceKind: CPQ_QUOTE_RESOURCE_KIND,
|
|
20
|
+
resourceId: body.quoteId,
|
|
21
|
+
operation: "custom",
|
|
22
|
+
mutationPayload: { action: "convert_to_order" }
|
|
23
|
+
});
|
|
24
|
+
if (!guard.ok) return guard.response;
|
|
16
25
|
const service = ctx.container.resolve("cpqOrderService");
|
|
17
26
|
const result = await service.convertQuoteToOrder(body.quoteId, scope);
|
|
27
|
+
await guard.runAfterSuccess();
|
|
18
28
|
return NextResponse.json(result, { status: 201 });
|
|
19
29
|
} catch (err) {
|
|
20
30
|
return handleError(err, "cpq/orders.POST");
|
|
@@ -53,8 +63,8 @@ function handleError(err, context) {
|
|
|
53
63
|
if (err instanceof z.ZodError) {
|
|
54
64
|
return NextResponse.json({ error: "Validation error", details: err.issues }, { status: 400 });
|
|
55
65
|
}
|
|
56
|
-
if (err
|
|
57
|
-
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
66
|
+
if (isOrderError(err)) {
|
|
67
|
+
return NextResponse.json({ error: err.message, ...err.details }, { status: err.status });
|
|
58
68
|
}
|
|
59
69
|
const errObj = err;
|
|
60
70
|
if (typeof errObj?.status === "number" && typeof errObj?.error === "string") {
|
|
@@ -63,9 +73,44 @@ function handleError(err, context) {
|
|
|
63
73
|
console.error(`[${context}]`, err);
|
|
64
74
|
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
65
75
|
}
|
|
76
|
+
const openApi = {
|
|
77
|
+
tag: "CPQ Orders",
|
|
78
|
+
summary: "CPQ orders",
|
|
79
|
+
methods: {
|
|
80
|
+
POST: {
|
|
81
|
+
summary: "Convert a CPQ quote into an order",
|
|
82
|
+
description: "Creates the sales order and CPQ order configuration from the quote. Refused for cancelled or rejected quotes; mutation guards (resource kind `cpq.quote`, action `convert_to_order`) run first.",
|
|
83
|
+
requestBody: { schema: cpqConvertQuoteToOrderSchema },
|
|
84
|
+
responses: [{ status: 201, description: "Created order" }],
|
|
85
|
+
errors: [
|
|
86
|
+
cpqErrorDoc(400, "Validation error"),
|
|
87
|
+
cpqErrorDoc(404, "Quote not found"),
|
|
88
|
+
cpqErrorDoc(409, "Quote cannot be converted in its status or is already converted"),
|
|
89
|
+
cpqErrorDoc(422, "Quote has no lines or has invalid stored adjustments, or blocked by a mutation guard")
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
GET: {
|
|
93
|
+
summary: "List CPQ orders",
|
|
94
|
+
query: z.object({
|
|
95
|
+
customerId: z.string().uuid().optional(),
|
|
96
|
+
sourceQuoteId: z.string().uuid().optional(),
|
|
97
|
+
salesOrderId: z.string().uuid().optional(),
|
|
98
|
+
cpqStatus: z.string().optional(),
|
|
99
|
+
currencyCode: z.string().optional(),
|
|
100
|
+
search: z.string().optional(),
|
|
101
|
+
sortField: z.enum(["createdAt", "updatedAt", "cpqStatus", "activatedAt", "currencyCode"]).optional(),
|
|
102
|
+
sortDir: z.enum(["asc", "desc"]).optional(),
|
|
103
|
+
page: z.coerce.number().int().min(1).optional(),
|
|
104
|
+
pageSize: z.coerce.number().int().min(1).max(100).optional()
|
|
105
|
+
}),
|
|
106
|
+
responses: [{ status: 200, description: "Paged list of CPQ orders" }]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
};
|
|
66
110
|
export {
|
|
67
111
|
GET,
|
|
68
112
|
POST,
|
|
69
|
-
metadata
|
|
113
|
+
metadata,
|
|
114
|
+
openApi
|
|
70
115
|
};
|
|
71
116
|
//# sourceMappingURL=route.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/cpq/api/orders/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { cpqConvertQuoteToOrderSchema } from '../../data/validators'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,oCAAoC;AAC7C,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../context'\nimport { cpqConvertQuoteToOrderSchema } from '../../data/validators'\nimport { isOrderError } from '../../services/cpqOrderService'\nimport type { DefaultCpqOrderService } from '../../services/cpqOrderService'\nimport { CPQ_QUOTE_RESOURCE_KIND, runCpqMutationGuards } from '../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { cpqErrorDoc } from '../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },\n GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },\n}\n\nexport async function POST(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const body = cpqConvertQuoteToOrderSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n // Conversion turns a quote into an order \u2014 projects can block it (e.g.\n // until an approval workflow has signed a discounted quote off).\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_RESOURCE_KIND,\n resourceId: body.quoteId,\n operation: 'custom',\n mutationPayload: { action: 'convert_to_order' },\n })\n if (!guard.ok) return guard.response\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.convertQuoteToOrder(body.quoteId, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n return handleError(err, 'cpq/orders.POST')\n }\n}\n\nexport async function GET(req: Request) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n const url = new URL(req.url)\n const ALLOWED_SORT_FIELDS = ['createdAt', 'updatedAt', 'cpqStatus', 'activatedAt', 'currencyCode'] as const\n const sortFieldParam = url.searchParams.get('sortField') ?? ''\n const sortField = (ALLOWED_SORT_FIELDS as readonly string[]).includes(sortFieldParam)\n ? (sortFieldParam as (typeof ALLOWED_SORT_FIELDS)[number])\n : undefined\n const sortDir = url.searchParams.get('sortDir') === 'asc' ? 'asc' : 'desc'\n\n const filters = {\n customerId: url.searchParams.get('customerId') ?? undefined,\n sourceQuoteId: url.searchParams.get('sourceQuoteId') ?? undefined,\n salesOrderId: url.searchParams.get('salesOrderId') ?? undefined,\n cpqStatus: url.searchParams.get('cpqStatus') ?? undefined,\n currencyCode: url.searchParams.get('currencyCode') ?? undefined,\n search: url.searchParams.get('search') ?? undefined,\n sortField,\n sortDir,\n page: url.searchParams.has('page') ? Number(url.searchParams.get('page')) : undefined,\n pageSize: url.searchParams.has('pageSize') ? Number(url.searchParams.get('pageSize')) : undefined,\n } as const\n\n const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService\n const result = await service.listOrders(filters, scope)\n return NextResponse.json(result)\n } catch (err) {\n return handleError(err, 'cpq/orders.GET')\n }\n}\n\nfunction handleError(err: unknown, context: string) {\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, ...err.details }, { 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(`[${context}]`, err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Orders',\n summary: 'CPQ orders',\n methods: {\n POST: {\n summary: 'Convert a CPQ quote into an order',\n description:\n 'Creates the sales order and CPQ order configuration from the quote. Refused for cancelled or rejected quotes; mutation guards (resource kind `cpq.quote`, action `convert_to_order`) run first.',\n requestBody: { schema: cpqConvertQuoteToOrderSchema },\n responses: [{ status: 201, description: 'Created order' }],\n errors: [\n cpqErrorDoc(400, 'Validation error'),\n cpqErrorDoc(404, 'Quote not found'),\n cpqErrorDoc(409, 'Quote cannot be converted in its status or is already converted'),\n cpqErrorDoc(422, 'Quote has no lines or has invalid stored adjustments, or blocked by a mutation guard'),\n ],\n },\n GET: {\n summary: 'List CPQ orders',\n query: z.object({\n customerId: z.string().uuid().optional(),\n sourceQuoteId: z.string().uuid().optional(),\n salesOrderId: z.string().uuid().optional(),\n cpqStatus: z.string().optional(),\n currencyCode: z.string().optional(),\n search: z.string().optional(),\n sortField: z.enum(['createdAt', 'updatedAt', 'cpqStatus', 'activatedAt', 'currencyCode']).optional(),\n sortDir: z.enum(['asc', 'desc']).optional(),\n page: z.coerce.number().int().min(1).optional(),\n pageSize: z.coerce.number().int().min(1).max(100).optional(),\n }),\n responses: [{ status: 200, description: 'Paged list of CPQ orders' }],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { resolveCpqRouteContext } from "../../../context.js";
|
|
3
|
-
import {
|
|
3
|
+
import { isQuotingError } from "../../../../services/cpqQuotingService.js";
|
|
4
4
|
import { resolveQuotingService } from "../../../resolveQuotingService.js";
|
|
5
5
|
const metadata = {
|
|
6
6
|
POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
|
|
@@ -15,7 +15,7 @@ async function POST(req, { params }) {
|
|
|
15
15
|
const result = await service.cloneQuote(quoteId, scope);
|
|
16
16
|
return NextResponse.json(result, { status: 201 });
|
|
17
17
|
} catch (err) {
|
|
18
|
-
if (err
|
|
18
|
+
if (isQuotingError(err)) {
|
|
19
19
|
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
20
20
|
}
|
|
21
21
|
console.error("[cpq/quotes/[quoteId]/clone.POST]", err);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/clone/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 { resolveCpqRouteContext } from '../../../context'\nimport { isQuotingError } from '../../../../services/cpqQuotingService'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = resolveQuotingService(ctx)\n const result = await service.cloneQuote(quoteId, scope)\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/clone.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AAE/B,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,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,WAAW,SAAS,KAAK;AACtD,WAAO,aAAa,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClD,SAAS,KAAK;AACZ,QAAI,eAAe,GAAG,GAAG;AACvB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,qCAAqC,GAAG;AACtD,WAAO,aAAa,KAAK,EAAE,OAAO,iBAAiB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvE;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { resolveCpqRouteContext } from "../../../../../context.js";
|
|
4
|
+
import { isQuotingError } from "../../../../../../services/cpqQuotingService.js";
|
|
5
|
+
import { resolveQuotingService } from "../../../../../resolveQuotingService.js";
|
|
6
|
+
import { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from "../../../../../_helpers/quoteMutationGuard.js";
|
|
7
|
+
import { CPQ_GUARDED_WRITE_ERRORS } from "../../../../../_helpers/quoteRouteOpenApi.js";
|
|
8
|
+
const metadata = {
|
|
9
|
+
POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
|
|
10
|
+
};
|
|
11
|
+
async function POST(req, { params }) {
|
|
12
|
+
try {
|
|
13
|
+
const ctx = await resolveCpqRouteContext(req);
|
|
14
|
+
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
15
|
+
const { quoteId, lineId } = await params;
|
|
16
|
+
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
|
|
17
|
+
const guard = await runCpqMutationGuards(ctx, req, {
|
|
18
|
+
resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,
|
|
19
|
+
resourceId: lineId,
|
|
20
|
+
operation: "update",
|
|
21
|
+
mutationPayload: { quoteId, restore: true }
|
|
22
|
+
});
|
|
23
|
+
if (!guard.ok) return guard.response;
|
|
24
|
+
const service = resolveQuotingService(ctx);
|
|
25
|
+
const result = await service.restoreQuoteItem(quoteId, lineId, scope);
|
|
26
|
+
await guard.runAfterSuccess();
|
|
27
|
+
return NextResponse.json(result);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
if (isQuotingError(err)) {
|
|
30
|
+
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
31
|
+
}
|
|
32
|
+
console.error("[cpq/quotes/[quoteId]/items/[lineId]/restore.POST]", err);
|
|
33
|
+
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const openApi = {
|
|
37
|
+
tag: "CPQ Quotes",
|
|
38
|
+
summary: "Restore a subscription item marked for removal",
|
|
39
|
+
pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),
|
|
40
|
+
methods: {
|
|
41
|
+
POST: {
|
|
42
|
+
summary: "Restore a removed subscription item on an Amend / Renew quote",
|
|
43
|
+
description: "Turns a `cancel` line that mirrors a live subscription item back into a `modify` line, rebuilt from the item (configuration, quantity, stored price). Its cancelled mirrored components are restored with it. 409 when the line is not a removed item, its parent is still removed, or the item is no longer active. Mutation guards run first (resource kind `cpq.quote_line`, update).",
|
|
44
|
+
responses: [{ status: 200, description: "Quote after the restore" }],
|
|
45
|
+
errors: CPQ_GUARDED_WRITE_ERRORS.filter((doc) => doc.status !== 400)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
export {
|
|
50
|
+
POST,
|
|
51
|
+
metadata,
|
|
52
|
+
openApi
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/items/%5BlineId%5D/restore/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../../../context'\nimport { isQuotingError } from '../../../../../../services/cpqQuotingService'\nimport { resolveQuotingService } from '../../../../../resolveQuotingService'\nimport { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from '../../../../../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CPQ_GUARDED_WRITE_ERRORS } from '../../../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string; lineId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId, lineId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,\n resourceId: lineId,\n operation: 'update',\n mutationPayload: { quoteId, restore: true },\n })\n if (!guard.ok) return guard.response\n\n const service = resolveQuotingService(ctx)\n const result = await service.restoreQuoteItem(quoteId, lineId, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result)\n } catch (err) {\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/items/[lineId]/restore.POST]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'Restore a subscription item marked for removal',\n pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),\n methods: {\n POST: {\n summary: 'Restore a removed subscription item on an Amend / Renew quote',\n description:\n 'Turns a `cancel` line that mirrors a live subscription item back into a `modify` line, rebuilt from the item (configuration, quantity, stored price). Its cancelled mirrored components are restored with it. 409 when the line is not a removed item, its parent is still removed, or the item is no longer active. Mutation guards run first (resource kind `cpq.quote_line`, update).',\n responses: [{ status: 200, description: 'Quote after the restore' }],\n errors: CPQ_GUARDED_WRITE_ERRORS.filter((doc) => doc.status !== 400),\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,6BAA6B;AACtC,SAAS,8BAA8B,4BAA4B;AAEnE,SAAS,gCAAgC;AAElC,MAAM,WAAW;AAAA,EACtB,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,mBAAmB,EAAE;AACpE;AAEA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAA6D;AAC7G,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,SAAS,OAAO,IAAI,MAAM;AAClC,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,MACjD,cAAc;AAAA,MACd,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,iBAAiB,EAAE,SAAS,SAAS,KAAK;AAAA,IAC5C,CAAC;AACD,QAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAE5B,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,iBAAiB,SAAS,QAAQ,KAAK;AACpE,UAAM,MAAM,gBAAgB;AAC5B,WAAO,aAAa,KAAK,MAAM;AAAA,EACjC,SAAS,KAAK;AACZ,QAAI,eAAe,GAAG,GAAG;AACvB,aAAO,aAAa,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,EAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,IACzE;AACA,YAAQ,MAAM,sDAAsD,GAAG;AACvE,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,GAAG,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAAA,EAC9E,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,0BAA0B,CAAC;AAAA,MACnE,QAAQ,yBAAyB,OAAO,CAAC,QAAQ,IAAI,WAAW,GAAG;AAAA,IACrE;AAAA,EACF;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -2,8 +2,10 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { resolveCpqRouteContext } from "../../../../context.js";
|
|
4
4
|
import { cpqUpdateQuoteItemSchema } from "../../../../../data/validators.js";
|
|
5
|
-
import {
|
|
5
|
+
import { isQuotingError } from "../../../../../services/cpqQuotingService.js";
|
|
6
6
|
import { resolveQuotingService } from "../../../../resolveQuotingService.js";
|
|
7
|
+
import { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from "../../../../_helpers/quoteMutationGuard.js";
|
|
8
|
+
import { CPQ_GUARDED_WRITE_ERRORS } from "../../../../_helpers/quoteRouteOpenApi.js";
|
|
7
9
|
const metadata = {
|
|
8
10
|
PUT: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] },
|
|
9
11
|
DELETE: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
|
|
@@ -15,14 +17,23 @@ async function PUT(req, { params }) {
|
|
|
15
17
|
const { quoteId, lineId } = await params;
|
|
16
18
|
const body = cpqUpdateQuoteItemSchema.parse(await req.json());
|
|
17
19
|
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
|
|
20
|
+
const guard = await runCpqMutationGuards(ctx, req, {
|
|
21
|
+
resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,
|
|
22
|
+
resourceId: lineId,
|
|
23
|
+
operation: "update",
|
|
24
|
+
mutationPayload: { quoteId, ...body }
|
|
25
|
+
});
|
|
26
|
+
if (!guard.ok) return guard.response;
|
|
27
|
+
const effective = guard.modifiedPayload ? cpqUpdateQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload }) : body;
|
|
18
28
|
const service = resolveQuotingService(ctx);
|
|
19
|
-
const result = await service.updateQuoteItem(quoteId, lineId,
|
|
29
|
+
const result = await service.updateQuoteItem(quoteId, lineId, effective, scope);
|
|
30
|
+
await guard.runAfterSuccess();
|
|
20
31
|
return NextResponse.json(result);
|
|
21
32
|
} catch (err) {
|
|
22
33
|
if (err instanceof z.ZodError) {
|
|
23
34
|
return NextResponse.json({ error: "Validation error", details: err.issues }, { status: 400 });
|
|
24
35
|
}
|
|
25
|
-
if (err
|
|
36
|
+
if (isQuotingError(err)) {
|
|
26
37
|
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
27
38
|
}
|
|
28
39
|
console.error("[cpq/quotes/[quoteId]/items/[lineId].PUT]", err);
|
|
@@ -35,20 +46,49 @@ async function DELETE(req, { params }) {
|
|
|
35
46
|
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
36
47
|
const { quoteId, lineId } = await params;
|
|
37
48
|
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
|
|
49
|
+
const guard = await runCpqMutationGuards(ctx, req, {
|
|
50
|
+
resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,
|
|
51
|
+
resourceId: lineId,
|
|
52
|
+
operation: "delete",
|
|
53
|
+
mutationPayload: { quoteId }
|
|
54
|
+
});
|
|
55
|
+
if (!guard.ok) return guard.response;
|
|
38
56
|
const service = resolveQuotingService(ctx);
|
|
39
57
|
const result = await service.removeQuoteItem(quoteId, lineId, scope);
|
|
58
|
+
await guard.runAfterSuccess();
|
|
40
59
|
return NextResponse.json(result);
|
|
41
60
|
} catch (err) {
|
|
42
|
-
if (err
|
|
61
|
+
if (isQuotingError(err)) {
|
|
43
62
|
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
44
63
|
}
|
|
45
64
|
console.error("[cpq/quotes/[quoteId]/items/[lineId].DELETE]", err);
|
|
46
65
|
return NextResponse.json({ error: "Internal error" }, { status: 500 });
|
|
47
66
|
}
|
|
48
67
|
}
|
|
68
|
+
const openApi = {
|
|
69
|
+
tag: "CPQ Quotes",
|
|
70
|
+
summary: "CPQ quote line",
|
|
71
|
+
pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),
|
|
72
|
+
methods: {
|
|
73
|
+
PUT: {
|
|
74
|
+
summary: "Update a quote line (configuration, quantity, term)",
|
|
75
|
+
description: "Re-prices the line unless it is a frozen Amend mirror line whose pricing inputs are unchanged. Mutation guards run first (resource kind `cpq.quote_line`, update). `_arc*` configuration keys are server-owned and ignored.",
|
|
76
|
+
requestBody: { schema: cpqUpdateQuoteItemSchema },
|
|
77
|
+
responses: [{ status: 200, description: "Quote after the update" }],
|
|
78
|
+
errors: CPQ_GUARDED_WRITE_ERRORS
|
|
79
|
+
},
|
|
80
|
+
DELETE: {
|
|
81
|
+
summary: "Remove a quote line",
|
|
82
|
+
description: "On an Amend / Renew quote, a line mirroring a live subscription item is not deleted: it (and its mirrored components) becomes a `cancel` line with zero totals, and activation terminates the item; lines this quote added under it are deleted. 409 when the item is already marked for removal \u2014 use `POST .../restore` to undo. Other lines are deleted with their descendants. Mutation guards run first (resource kind `cpq.quote_line`, delete).",
|
|
83
|
+
responses: [{ status: 200, description: "Quote after the removal" }],
|
|
84
|
+
errors: CPQ_GUARDED_WRITE_ERRORS.filter((doc) => doc.status !== 400)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
49
88
|
export {
|
|
50
89
|
DELETE,
|
|
51
90
|
PUT,
|
|
52
|
-
metadata
|
|
91
|
+
metadata,
|
|
92
|
+
openApi
|
|
53
93
|
};
|
|
54
94
|
//# sourceMappingURL=route.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/items/%5BlineId%5D/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../../context'\nimport { cpqUpdateQuoteItemSchema } from '../../../../../data/validators'\nimport {
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,8BAA8B;AACvC,SAAS,gCAAgC;AACzC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { resolveCpqRouteContext } from '../../../../context'\nimport { cpqUpdateQuoteItemSchema } from '../../../../../data/validators'\nimport { isQuotingError } from '../../../../../services/cpqQuotingService'\nimport { resolveQuotingService } from '../../../../resolveQuotingService'\nimport { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from '../../../../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CPQ_GUARDED_WRITE_ERRORS } from '../../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n PUT: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function PUT(req: Request, { params }: { params: Promise<{ quoteId: string; lineId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId, lineId } = await params\n const body = cpqUpdateQuoteItemSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,\n resourceId: lineId,\n operation: 'update',\n mutationPayload: { quoteId, ...body },\n })\n if (!guard.ok) return guard.response\n const effective = guard.modifiedPayload\n ? cpqUpdateQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload })\n : body\n\n const service = resolveQuotingService(ctx)\n const result = await service.updateQuoteItem(quoteId, lineId, effective, scope)\n await guard.runAfterSuccess()\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 (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/items/[lineId].PUT]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport async function DELETE(req: Request, { params }: { params: Promise<{ quoteId: string; lineId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId, lineId } = await params\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,\n resourceId: lineId,\n operation: 'delete',\n mutationPayload: { quoteId },\n })\n if (!guard.ok) return guard.response\n\n const service = resolveQuotingService(ctx)\n const result = await service.removeQuoteItem(quoteId, lineId, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result)\n } catch (err) {\n if (isQuotingError(err)) {\n return NextResponse.json({ error: err.message }, { status: err.status })\n }\n console.error('[cpq/quotes/[quoteId]/items/[lineId].DELETE]', err)\n return NextResponse.json({ error: 'Internal error' }, { status: 500 })\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'CPQ quote line',\n pathParams: z.object({ quoteId: z.string().uuid(), lineId: z.string().uuid() }),\n methods: {\n PUT: {\n summary: 'Update a quote line (configuration, quantity, term)',\n description:\n 'Re-prices the line unless it is a frozen Amend mirror line whose pricing inputs are unchanged. Mutation guards run first (resource kind `cpq.quote_line`, update). `_arc*` configuration keys are server-owned and ignored.',\n requestBody: { schema: cpqUpdateQuoteItemSchema },\n responses: [{ status: 200, description: 'Quote after the update' }],\n errors: CPQ_GUARDED_WRITE_ERRORS,\n },\n DELETE: {\n summary: 'Remove a quote line',\n description:\n 'On an Amend / Renew quote, a line mirroring a live subscription item is not deleted: it (and its mirrored components) becomes a `cancel` line with zero totals, and activation terminates the item; lines this quote added under it are deleted. 409 when the item is already marked for removal \u2014 use `POST .../restore` to undo. Other lines are deleted with their descendants. Mutation guards run first (resource kind `cpq.quote_line`, delete).',\n responses: [{ status: 200, description: 'Quote after the removal' }],\n errors: CPQ_GUARDED_WRITE_ERRORS.filter((doc) => doc.status !== 400),\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,6 +3,8 @@ import { resolveCpqRouteContext } from "../../../context.js";
|
|
|
3
3
|
import { cpqAddQuoteItemSchema } from "../../../../data/validators.js";
|
|
4
4
|
import { resolveQuotingService } from "../../../resolveQuotingService.js";
|
|
5
5
|
import { handleArcError } from "../../../_helpers/handleArcError.js";
|
|
6
|
+
import { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from "../../../_helpers/quoteMutationGuard.js";
|
|
7
|
+
import { CPQ_GUARDED_WRITE_ERRORS, cpqQuoteIdPathParams } from "../../../_helpers/quoteRouteOpenApi.js";
|
|
6
8
|
const metadata = {
|
|
7
9
|
POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage"] }
|
|
8
10
|
};
|
|
@@ -13,15 +15,38 @@ async function POST(req, { params }) {
|
|
|
13
15
|
const { quoteId } = await params;
|
|
14
16
|
const body = cpqAddQuoteItemSchema.parse(await req.json());
|
|
15
17
|
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId };
|
|
18
|
+
const guard = await runCpqMutationGuards(ctx, req, {
|
|
19
|
+
resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,
|
|
20
|
+
operation: "create",
|
|
21
|
+
mutationPayload: { quoteId, ...body }
|
|
22
|
+
});
|
|
23
|
+
if (!guard.ok) return guard.response;
|
|
24
|
+
const effective = guard.modifiedPayload ? cpqAddQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload }) : body;
|
|
16
25
|
const service = resolveQuotingService(ctx);
|
|
17
|
-
const result = await service.addQuoteItem(quoteId,
|
|
26
|
+
const result = await service.addQuoteItem(quoteId, effective, scope);
|
|
27
|
+
await guard.runAfterSuccess();
|
|
18
28
|
return NextResponse.json(result, { status: 201 });
|
|
19
29
|
} catch (err) {
|
|
20
30
|
return handleArcError(err, "cpq/quotes/[quoteId]/items.POST");
|
|
21
31
|
}
|
|
22
32
|
}
|
|
33
|
+
const openApi = {
|
|
34
|
+
tag: "CPQ Quotes",
|
|
35
|
+
summary: "CPQ quote lines",
|
|
36
|
+
pathParams: cpqQuoteIdPathParams,
|
|
37
|
+
methods: {
|
|
38
|
+
POST: {
|
|
39
|
+
summary: "Add a configured line to a CPQ quote",
|
|
40
|
+
description: "Prices the line and recalculates the quote. Mutation guards run first (resource kind `cpq.quote_line`, create). `_arc*` configuration keys are server-owned and ignored.",
|
|
41
|
+
requestBody: { schema: cpqAddQuoteItemSchema },
|
|
42
|
+
responses: [{ status: 201, description: "Quote after the line was added" }],
|
|
43
|
+
errors: CPQ_GUARDED_WRITE_ERRORS
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
23
47
|
export {
|
|
24
48
|
POST,
|
|
25
|
-
metadata
|
|
49
|
+
metadata,
|
|
50
|
+
openApi
|
|
26
51
|
};
|
|
27
52
|
//# sourceMappingURL=route.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/items/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqAddQuoteItemSchema } from '../../../../data/validators'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\nimport { handleArcError } from '../../../_helpers/handleArcError'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqAddQuoteItemSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const service = resolveQuotingService(ctx)\n const result = await service.addQuoteItem(quoteId,
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqAddQuoteItemSchema } from '../../../../data/validators'\nimport { resolveQuotingService } from '../../../resolveQuotingService'\nimport { handleArcError } from '../../../_helpers/handleArcError'\nimport { CPQ_QUOTE_LINE_RESOURCE_KIND, runCpqMutationGuards } from '../../../_helpers/quoteMutationGuard'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { CPQ_GUARDED_WRITE_ERRORS, cpqQuoteIdPathParams } from '../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage'] },\n}\n\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const { quoteId } = await params\n const body = cpqAddQuoteItemSchema.parse(await req.json())\n const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }\n\n const guard = await runCpqMutationGuards(ctx, req, {\n resourceKind: CPQ_QUOTE_LINE_RESOURCE_KIND,\n operation: 'create',\n mutationPayload: { quoteId, ...body },\n })\n if (!guard.ok) return guard.response\n const effective = guard.modifiedPayload\n ? cpqAddQuoteItemSchema.parse({ ...body, ...guard.modifiedPayload })\n : body\n\n const service = resolveQuotingService(ctx)\n const result = await service.addQuoteItem(quoteId, effective, scope)\n await guard.runAfterSuccess()\n return NextResponse.json(result, { status: 201 })\n } catch (err) {\n return handleArcError(err, 'cpq/quotes/[quoteId]/items.POST')\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'CPQ Quotes',\n summary: 'CPQ quote lines',\n pathParams: cpqQuoteIdPathParams,\n methods: {\n POST: {\n summary: 'Add a configured line to a CPQ quote',\n description:\n 'Prices the line and recalculates the quote. Mutation guards run first (resource kind `cpq.quote_line`, create). `_arc*` configuration keys are server-owned and ignored.',\n requestBody: { schema: cpqAddQuoteItemSchema },\n responses: [{ status: 201, description: 'Quote after the line was added' }],\n errors: CPQ_GUARDED_WRITE_ERRORS,\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,8BAA8B;AACvC,SAAS,6BAA6B;AACtC,SAAS,6BAA6B;AACtC,SAAS,sBAAsB;AAC/B,SAAS,8BAA8B,4BAA4B;AAEnE,SAAS,0BAA0B,4BAA4B;AAExD,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,sBAAsB,MAAM,MAAM,IAAI,KAAK,CAAC;AACzD,UAAM,QAAQ,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS;AAE3E,UAAM,QAAQ,MAAM,qBAAqB,KAAK,KAAK;AAAA,MACjD,cAAc;AAAA,MACd,WAAW;AAAA,MACX,iBAAiB,EAAE,SAAS,GAAG,KAAK;AAAA,IACtC,CAAC;AACD,QAAI,CAAC,MAAM,GAAI,QAAO,MAAM;AAC5B,UAAM,YAAY,MAAM,kBACpB,sBAAsB,MAAM,EAAE,GAAG,MAAM,GAAG,MAAM,gBAAgB,CAAC,IACjE;AAEJ,UAAM,UAAU,sBAAsB,GAAG;AACzC,UAAM,SAAS,MAAM,QAAQ,aAAa,SAAS,WAAW,KAAK;AACnE,UAAM,MAAM,gBAAgB;AAC5B,WAAO,aAAa,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;AAAA,EAClD,SAAS,KAAK;AACZ,WAAO,eAAe,KAAK,iCAAiC;AAAA,EAC9D;AACF;AAEO,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,SAAS;AAAA,IACP,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,aACE;AAAA,MACF,aAAa,EAAE,QAAQ,sBAAsB;AAAA,MAC7C,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,iCAAiC,CAAC;AAAA,MAC1E,QAAQ;AAAA,IACV;AAAA,EACF;AACF;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { resolveCpqRouteContext } from "../../../../context.js";
|
|
4
|
+
import { cpqPriceAdjustmentPatchSchema } from "../../../../../data/price-adjustments.js";
|
|
5
|
+
import { adjustmentCommandRoute } from "../../../../_helpers/adjustmentCommandRoute.js";
|
|
6
|
+
import { handleArcError } from "../../../../_helpers/handleArcError.js";
|
|
7
|
+
import { cpqErrorDoc } from "../../../../_helpers/quoteRouteOpenApi.js";
|
|
8
|
+
const metadata = {
|
|
9
|
+
PATCH: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] },
|
|
10
|
+
DELETE: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] }
|
|
11
|
+
};
|
|
12
|
+
const pathParams = z.object({ quoteId: z.string().uuid(), adjustmentId: z.string().uuid() });
|
|
13
|
+
const deletionSchema = z.object({ expectedRevision: z.coerce.number().int().min(0).optional() }).strict();
|
|
14
|
+
async function PATCH(req, { params }) {
|
|
15
|
+
try {
|
|
16
|
+
const ctx = await resolveCpqRouteContext(req);
|
|
17
|
+
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
18
|
+
const { quoteId, adjustmentId } = pathParams.parse(await params);
|
|
19
|
+
const body = cpqPriceAdjustmentPatchSchema.parse(await req.json());
|
|
20
|
+
return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentPatchSchema, "update", adjustmentId);
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return handleArcError(error, "cpq/price-adjustments.PATCH");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function DELETE(req, { params }) {
|
|
26
|
+
try {
|
|
27
|
+
const ctx = await resolveCpqRouteContext(req);
|
|
28
|
+
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
29
|
+
const { quoteId, adjustmentId } = pathParams.parse(await params);
|
|
30
|
+
const body = deletionSchema.parse(Object.fromEntries(new URL(req.url).searchParams));
|
|
31
|
+
return await adjustmentCommandRoute(req, ctx, quoteId, body, deletionSchema, "delete", adjustmentId);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return handleArcError(error, "cpq/price-adjustments.DELETE");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const errors = [cpqErrorDoc(400, "Invalid input"), cpqErrorDoc(403, "Missing permission or disabled feature"), cpqErrorDoc(404, "Quote or adjustment not found"), cpqErrorDoc(409, "Adjustment locked, stale revision or changed undo target"), cpqErrorDoc(422, "Invalid adjustment or guard rejection")];
|
|
37
|
+
const openApi = { tag: "CPQ Quotes", pathParams, methods: {
|
|
38
|
+
PATCH: { summary: "Update a negotiated quote price adjustment", requestBody: { schema: cpqPriceAdjustmentPatchSchema }, responses: [{ status: 200, description: "Adjustment and updated quote" }], errors },
|
|
39
|
+
DELETE: { summary: "Remove a negotiated quote price adjustment", query: deletionSchema, responses: [{ status: 200, description: "Updated quote" }], errors }
|
|
40
|
+
} };
|
|
41
|
+
export {
|
|
42
|
+
DELETE,
|
|
43
|
+
PATCH,
|
|
44
|
+
metadata,
|
|
45
|
+
openApi
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/price-adjustments/%5BadjustmentId%5D/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveCpqRouteContext } from '../../../../context'\nimport { cpqPriceAdjustmentPatchSchema } from '../../../../../data/price-adjustments'\nimport { adjustmentCommandRoute } from '../../../../_helpers/adjustmentCommandRoute'\nimport { handleArcError } from '../../../../_helpers/handleArcError'\nimport { cpqErrorDoc } from '../../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n PATCH: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] },\n DELETE: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] },\n}\nconst pathParams = z.object({ quoteId: z.string().uuid(), adjustmentId: z.string().uuid() })\nconst deletionSchema = z.object({ expectedRevision: z.coerce.number().int().min(0).optional() }).strict()\ntype Params = { params: Promise<{ quoteId: string; adjustmentId: string }> }\nexport async function PATCH(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId, adjustmentId } = pathParams.parse(await params)\n const body = cpqPriceAdjustmentPatchSchema.parse(await req.json())\n return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentPatchSchema, 'update', adjustmentId)\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.PATCH') }\n}\nexport async function DELETE(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId, adjustmentId } = pathParams.parse(await params)\n const body = deletionSchema.parse(Object.fromEntries(new URL(req.url).searchParams))\n return await adjustmentCommandRoute(req, ctx, quoteId, body, deletionSchema, 'delete', adjustmentId)\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.DELETE') }\n}\nconst errors = [cpqErrorDoc(400, 'Invalid input'), cpqErrorDoc(403, 'Missing permission or disabled feature'), cpqErrorDoc(404, 'Quote or adjustment not found'), cpqErrorDoc(409, 'Adjustment locked, stale revision or changed undo target'), cpqErrorDoc(422, 'Invalid adjustment or guard rejection')]\nexport const openApi: OpenApiRouteDoc = { tag: 'CPQ Quotes', pathParams, methods: {\n PATCH: { summary: 'Update a negotiated quote price adjustment', requestBody: { schema: cpqPriceAdjustmentPatchSchema }, responses: [{ status: 200, description: 'Adjustment and updated quote' }], errors },\n DELETE: { summary: 'Remove a negotiated quote price adjustment', query: deletionSchema, responses: [{ status: 200, description: 'Updated quote' }], errors },\n} }\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,qCAAqC;AAC9C,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,mBAAmB;AAErB,MAAM,WAAW;AAAA,EACtB,OAAO,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE;AAAA,EAC9F,QAAQ,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE;AACjG;AACA,MAAM,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,GAAG,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AAC3F,MAAM,iBAAiB,EAAE,OAAO,EAAE,kBAAkB,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO;AAExG,eAAsB,MAAM,KAAc,EAAE,OAAO,GAAW;AAC5D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,SAAS,aAAa,IAAI,WAAW,MAAM,MAAM,MAAM;AAC/D,UAAM,OAAO,8BAA8B,MAAM,MAAM,IAAI,KAAK,CAAC;AACjE,WAAO,MAAM,uBAAuB,KAAK,KAAK,SAAS,MAAM,+BAA+B,UAAU,YAAY;AAAA,EACpH,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,6BAA6B;AAAA,EAAE;AAChF;AACA,eAAsB,OAAO,KAAc,EAAE,OAAO,GAAW;AAC7D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,SAAS,aAAa,IAAI,WAAW,MAAM,MAAM,MAAM;AAC/D,UAAM,OAAO,eAAe,MAAM,OAAO,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,YAAY,CAAC;AACnF,WAAO,MAAM,uBAAuB,KAAK,KAAK,SAAS,MAAM,gBAAgB,UAAU,YAAY;AAAA,EACrG,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,8BAA8B;AAAA,EAAE;AACjF;AACA,MAAM,SAAS,CAAC,YAAY,KAAK,eAAe,GAAG,YAAY,KAAK,wCAAwC,GAAG,YAAY,KAAK,+BAA+B,GAAG,YAAY,KAAK,0DAA0D,GAAG,YAAY,KAAK,uCAAuC,CAAC;AAClS,MAAM,UAA2B,EAAE,KAAK,cAAc,YAAY,SAAS;AAAA,EAChF,OAAO,EAAE,SAAS,8CAA8C,aAAa,EAAE,QAAQ,8BAA8B,GAAG,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,+BAA+B,CAAC,GAAG,OAAO;AAAA,EAC1M,QAAQ,EAAE,SAAS,8CAA8C,OAAO,gBAAgB,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,gBAAgB,CAAC,GAAG,OAAO;AAC7J,EAAE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { resolveCpqRouteContext } from "../../../../context.js";
|
|
3
|
+
import { cpqPriceAdjustmentPreviewSchema } from "../../../../../data/price-adjustments.js";
|
|
4
|
+
import { handleArcError } from "../../../../_helpers/handleArcError.js";
|
|
5
|
+
import { cpqErrorDoc, cpqQuoteIdPathParams } from "../../../../_helpers/quoteRouteOpenApi.js";
|
|
6
|
+
const metadata = { POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] } };
|
|
7
|
+
async function POST(req, { params }) {
|
|
8
|
+
try {
|
|
9
|
+
const ctx = await resolveCpqRouteContext(req);
|
|
10
|
+
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
11
|
+
const { quoteId } = cpqQuoteIdPathParams.parse(await params);
|
|
12
|
+
const body = cpqPriceAdjustmentPreviewSchema.parse(await req.json());
|
|
13
|
+
const service = ctx.container.resolve("cpqAdjustmentService");
|
|
14
|
+
return NextResponse.json(await service.preview(quoteId, body.draft, body.replaceAdjustmentId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId }));
|
|
15
|
+
} catch (error) {
|
|
16
|
+
return handleArcError(error, "cpq/price-adjustments/preview.POST");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const openApi = { tag: "CPQ Quotes", pathParams: cpqQuoteIdPathParams, methods: { POST: {
|
|
20
|
+
summary: "Preview a draft adjustment without saving or emitting events",
|
|
21
|
+
requestBody: { schema: cpqPriceAdjustmentPreviewSchema },
|
|
22
|
+
responses: [{ status: 200, description: "Draft quote and adjustment errors; nothing persisted" }],
|
|
23
|
+
errors: [cpqErrorDoc(400, "Invalid draft"), cpqErrorDoc(403, "Missing permission or disabled feature"), cpqErrorDoc(404, "Quote, line or replaced adjustment not found"), cpqErrorDoc(409, "Adjustment locked or stale revision"), cpqErrorDoc(422, "Ineligible target or invalid reason")]
|
|
24
|
+
} } };
|
|
25
|
+
export {
|
|
26
|
+
POST,
|
|
27
|
+
metadata,
|
|
28
|
+
openApi
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/price-adjustments/preview/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveCpqRouteContext } from '../../../../context'\nimport { cpqPriceAdjustmentPreviewSchema } from '../../../../../data/price-adjustments'\nimport type { CpqAdjustmentService } from '../../../../../services/priceAdjustments/adjustmentService'\nimport { handleArcError } from '../../../../_helpers/handleArcError'\nimport { cpqErrorDoc, cpqQuoteIdPathParams } from '../../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = { POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] } }\nexport async function POST(req: Request, { params }: { params: Promise<{ quoteId: string }> }) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId } = cpqQuoteIdPathParams.parse(await params)\n const body = cpqPriceAdjustmentPreviewSchema.parse(await req.json())\n const service = ctx.container.resolve<CpqAdjustmentService>('cpqAdjustmentService')\n return NextResponse.json(await service.preview(quoteId, body.draft, body.replaceAdjustmentId, { tenantId: ctx.tenantId, organizationId: ctx.organizationId }))\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments/preview.POST') }\n}\nexport const openApi: OpenApiRouteDoc = { tag: 'CPQ Quotes', pathParams: cpqQuoteIdPathParams, methods: { POST: {\n summary: 'Preview a draft adjustment without saving or emitting events', requestBody: { schema: cpqPriceAdjustmentPreviewSchema },\n responses: [{ status: 200, description: 'Draft quote and adjustment errors; nothing persisted' }],\n errors: [cpqErrorDoc(400, 'Invalid draft'), cpqErrorDoc(403, 'Missing permission or disabled feature'), cpqErrorDoc(404, 'Quote, line or replaced adjustment not found'), cpqErrorDoc(409, 'Adjustment locked or stale revision'), cpqErrorDoc(422, 'Ineligible target or invalid reason')],\n} } }\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAE7B,SAAS,8BAA8B;AACvC,SAAS,uCAAuC;AAEhD,SAAS,sBAAsB;AAC/B,SAAS,aAAa,4BAA4B;AAE3C,MAAM,WAAW,EAAE,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE,EAAE;AACzH,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;AAClF,UAAM,EAAE,QAAQ,IAAI,qBAAqB,MAAM,MAAM,MAAM;AAC3D,UAAM,OAAO,gCAAgC,MAAM,MAAM,IAAI,KAAK,CAAC;AACnE,UAAM,UAAU,IAAI,UAAU,QAA8B,sBAAsB;AAClF,WAAO,aAAa,KAAK,MAAM,QAAQ,QAAQ,SAAS,KAAK,OAAO,KAAK,qBAAqB,EAAE,UAAU,IAAI,UAAU,gBAAgB,IAAI,eAAe,CAAC,CAAC;AAAA,EAC/J,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,oCAAoC;AAAA,EAAE;AACvF;AACO,MAAM,UAA2B,EAAE,KAAK,cAAc,YAAY,sBAAsB,SAAS,EAAE,MAAM;AAAA,EAC9G,SAAS;AAAA,EAAgE,aAAa,EAAE,QAAQ,gCAAgC;AAAA,EAChI,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,uDAAuD,CAAC;AAAA,EAChG,QAAQ,CAAC,YAAY,KAAK,eAAe,GAAG,YAAY,KAAK,wCAAwC,GAAG,YAAY,KAAK,8CAA8C,GAAG,YAAY,KAAK,qCAAqC,GAAG,YAAY,KAAK,qCAAqC,CAAC;AAC5R,EAAE,EAAE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { resolveCpqRouteContext } from "../../../context.js";
|
|
4
|
+
import { cpqPriceAdjustmentInputSchema } from "../../../../data/price-adjustments.js";
|
|
5
|
+
import { adjustmentCommandRoute } from "../../../_helpers/adjustmentCommandRoute.js";
|
|
6
|
+
import { handleArcError } from "../../../_helpers/handleArcError.js";
|
|
7
|
+
import { cpqErrorDoc, cpqQuoteIdPathParams } from "../../../_helpers/quoteRouteOpenApi.js";
|
|
8
|
+
const metadata = {
|
|
9
|
+
GET: { requireAuth: true, requireFeatures: ["cpq.quotes.view"] },
|
|
10
|
+
POST: { requireAuth: true, requireFeatures: ["cpq.quotes.manage", "cpq.quotes.adjust_price"] }
|
|
11
|
+
};
|
|
12
|
+
const listQuery = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(25) });
|
|
13
|
+
async function GET(req, { params }) {
|
|
14
|
+
try {
|
|
15
|
+
const ctx = await resolveCpqRouteContext(req);
|
|
16
|
+
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
17
|
+
const { quoteId } = cpqQuoteIdPathParams.parse(await params);
|
|
18
|
+
const { page, pageSize } = listQuery.parse(Object.fromEntries(new URL(req.url).searchParams));
|
|
19
|
+
const service = ctx.container.resolve("cpqAdjustmentService");
|
|
20
|
+
return NextResponse.json(await service.list(quoteId, { organizationId: ctx.organizationId, tenantId: ctx.tenantId }, page, pageSize));
|
|
21
|
+
} catch (error) {
|
|
22
|
+
return handleArcError(error, "cpq/price-adjustments.GET");
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function POST(req, { params }) {
|
|
26
|
+
try {
|
|
27
|
+
const ctx = await resolveCpqRouteContext(req);
|
|
28
|
+
if (!ctx.auth) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
29
|
+
const { quoteId } = cpqQuoteIdPathParams.parse(await params);
|
|
30
|
+
const body = cpqPriceAdjustmentInputSchema.parse(await req.json());
|
|
31
|
+
return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentInputSchema, "create");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
return handleArcError(error, "cpq/price-adjustments.POST");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const errors = [cpqErrorDoc(400, "Invalid input"), cpqErrorDoc(403, "Missing permission or disabled feature"), cpqErrorDoc(404, "Quote not found"), cpqErrorDoc(409, "Adjustment locked or stale revision"), cpqErrorDoc(422, "Invalid adjustment or guard rejection")];
|
|
37
|
+
const openApi = { tag: "CPQ Quotes", pathParams: cpqQuoteIdPathParams, methods: {
|
|
38
|
+
GET: { summary: "List quote price adjustments", query: listQuery, responses: [{ status: 200, description: "Paged adjustments, pricing revision, feature state and the reason options (`reasons: [{ value, label }]`)" }], errors },
|
|
39
|
+
POST: { summary: "Create a negotiated quote price adjustment", requestBody: { schema: cpqPriceAdjustmentInputSchema }, responses: [{ status: 201, description: "Adjustment and updated quote" }], errors }
|
|
40
|
+
} };
|
|
41
|
+
export {
|
|
42
|
+
GET,
|
|
43
|
+
POST,
|
|
44
|
+
metadata,
|
|
45
|
+
openApi
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../src/modules/cpq/api/quotes/%5BquoteId%5D/price-adjustments/route.ts"],
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveCpqRouteContext } from '../../../context'\nimport { cpqPriceAdjustmentInputSchema } from '../../../../data/price-adjustments'\nimport type { CpqAdjustmentService } from '../../../../services/priceAdjustments/adjustmentService'\nimport { adjustmentCommandRoute } from '../../../_helpers/adjustmentCommandRoute'\nimport { handleArcError } from '../../../_helpers/handleArcError'\nimport { cpqErrorDoc, cpqQuoteIdPathParams } from '../../../_helpers/quoteRouteOpenApi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['cpq.quotes.view'] },\n POST: { requireAuth: true, requireFeatures: ['cpq.quotes.manage', 'cpq.quotes.adjust_price'] },\n}\nconst listQuery = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(25) })\ntype Params = { params: Promise<{ quoteId: string }> }\nexport async function GET(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId } = cpqQuoteIdPathParams.parse(await params)\n const { page, pageSize } = listQuery.parse(Object.fromEntries(new URL(req.url).searchParams))\n const service = ctx.container.resolve<CpqAdjustmentService>('cpqAdjustmentService')\n return NextResponse.json(await service.list(quoteId, { organizationId: ctx.organizationId, tenantId: ctx.tenantId }, page, pageSize))\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.GET') }\n}\nexport async function POST(req: Request, { params }: Params) {\n try {\n const ctx = await resolveCpqRouteContext(req)\n if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n const { quoteId } = cpqQuoteIdPathParams.parse(await params)\n const body = cpqPriceAdjustmentInputSchema.parse(await req.json())\n return await adjustmentCommandRoute(req, ctx, quoteId, body, cpqPriceAdjustmentInputSchema, 'create')\n } catch (error) { return handleArcError(error, 'cpq/price-adjustments.POST') }\n}\nconst errors = [cpqErrorDoc(400, 'Invalid input'), cpqErrorDoc(403, 'Missing permission or disabled feature'), cpqErrorDoc(404, 'Quote not found'), cpqErrorDoc(409, 'Adjustment locked or stale revision'), cpqErrorDoc(422, 'Invalid adjustment or guard rejection')]\nexport const openApi: OpenApiRouteDoc = { tag: 'CPQ Quotes', pathParams: cpqQuoteIdPathParams, methods: {\n GET: { summary: 'List quote price adjustments', query: listQuery, responses: [{ status: 200, description: 'Paged adjustments, pricing revision, feature state and the reason options (`reasons: [{ value, label }]`)' }], errors },\n POST: { summary: 'Create a negotiated quote price adjustment', requestBody: { schema: cpqPriceAdjustmentInputSchema }, responses: [{ status: 201, description: 'Adjustment and updated quote' }], errors },\n} }\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,8BAA8B;AACvC,SAAS,qCAAqC;AAE9C,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,aAAa,4BAA4B;AAE3C,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,iBAAiB,EAAE;AAAA,EAC/D,MAAM,EAAE,aAAa,MAAM,iBAAiB,CAAC,qBAAqB,yBAAyB,EAAE;AAC/F;AACA,MAAM,YAAY,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,QAAQ,CAAC,GAAG,UAAU,EAAE,OAAO,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,EAAE,EAAE,CAAC;AAE7I,eAAsB,IAAI,KAAc,EAAE,OAAO,GAAW;AAC1D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,QAAQ,IAAI,qBAAqB,MAAM,MAAM,MAAM;AAC3D,UAAM,EAAE,MAAM,SAAS,IAAI,UAAU,MAAM,OAAO,YAAY,IAAI,IAAI,IAAI,GAAG,EAAE,YAAY,CAAC;AAC5F,UAAM,UAAU,IAAI,UAAU,QAA8B,sBAAsB;AAClF,WAAO,aAAa,KAAK,MAAM,QAAQ,KAAK,SAAS,EAAE,gBAAgB,IAAI,gBAAgB,UAAU,IAAI,SAAS,GAAG,MAAM,QAAQ,CAAC;AAAA,EACtI,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,2BAA2B;AAAA,EAAE;AAC9E;AACA,eAAsB,KAAK,KAAc,EAAE,OAAO,GAAW;AAC3D,MAAI;AACF,UAAM,MAAM,MAAM,uBAAuB,GAAG;AAC5C,QAAI,CAAC,IAAI,KAAM,QAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAClF,UAAM,EAAE,QAAQ,IAAI,qBAAqB,MAAM,MAAM,MAAM;AAC3D,UAAM,OAAO,8BAA8B,MAAM,MAAM,IAAI,KAAK,CAAC;AACjE,WAAO,MAAM,uBAAuB,KAAK,KAAK,SAAS,MAAM,+BAA+B,QAAQ;AAAA,EACtG,SAAS,OAAO;AAAE,WAAO,eAAe,OAAO,4BAA4B;AAAA,EAAE;AAC/E;AACA,MAAM,SAAS,CAAC,YAAY,KAAK,eAAe,GAAG,YAAY,KAAK,wCAAwC,GAAG,YAAY,KAAK,iBAAiB,GAAG,YAAY,KAAK,qCAAqC,GAAG,YAAY,KAAK,uCAAuC,CAAC;AAC/P,MAAM,UAA2B,EAAE,KAAK,cAAc,YAAY,sBAAsB,SAAS;AAAA,EACtG,KAAK,EAAE,SAAS,gCAAgC,OAAO,WAAW,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,4GAA4G,CAAC,GAAG,OAAO;AAAA,EACjO,MAAM,EAAE,SAAS,8CAA8C,aAAa,EAAE,QAAQ,8BAA8B,GAAG,WAAW,CAAC,EAAE,QAAQ,KAAK,aAAa,+BAA+B,CAAC,GAAG,OAAO;AAC3M,EAAE;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|