@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
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
|
+
import { Percent, Pencil, Trash2 } from 'lucide-react'
|
|
6
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
7
|
+
import { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'
|
|
8
|
+
import type { AdjustmentAccess, LineEligibilityInput, WaterfallChargeInput } from '../../../../../lib/priceAdjustmentView'
|
|
9
|
+
import { lineAdjustmentBlock } from '../../../../../lib/priceAdjustmentView'
|
|
10
|
+
|
|
11
|
+
export type AdjustableLine = LineEligibilityInput & { lineId: string; offeringName: string }
|
|
12
|
+
export type AdjustableCharge = WaterfallChargeInput & { chargeCode: string; chargeName: string }
|
|
13
|
+
|
|
14
|
+
export type PriceAdjustmentUi = {
|
|
15
|
+
access: AdjustmentAccess
|
|
16
|
+
quoteType: string
|
|
17
|
+
cpqStatus: string
|
|
18
|
+
reasonLabels: Record<string, string>
|
|
19
|
+
/** Ids of adjustments currently flagged invalid (they keep Delete while locked). */
|
|
20
|
+
invalidIds: Set<string>
|
|
21
|
+
canDeleteInvalid: boolean
|
|
22
|
+
openCharge: (line: AdjustableLine, charge: AdjustableCharge) => void
|
|
23
|
+
openLine: (line: AdjustableLine) => void
|
|
24
|
+
edit: (adjustmentId: string) => void
|
|
25
|
+
remove: (adjustmentId: string) => void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const Context = React.createContext<PriceAdjustmentUi | null>(null)
|
|
29
|
+
export const PriceAdjustmentProvider = Context.Provider
|
|
30
|
+
export const usePriceAdjustmentUi = () => React.useContext(Context)
|
|
31
|
+
|
|
32
|
+
/** Quote-wide explanation for why adjustments are unavailable; null when editable. */
|
|
33
|
+
export function useAccessReasonText(): string | null {
|
|
34
|
+
const t = useT()
|
|
35
|
+
const ui = usePriceAdjustmentUi()
|
|
36
|
+
if (!ui) return null
|
|
37
|
+
switch (ui.access.reason) {
|
|
38
|
+
case null: return null
|
|
39
|
+
case 'locked':
|
|
40
|
+
return t('cpq.priceAdjustments.blocked.locked', 'Adjustments are locked in status {status} — move the quote back to Ready to change them')
|
|
41
|
+
.replace('{status}', ui.cpqStatus)
|
|
42
|
+
case 'converted': return t('cpq.priceAdjustments.blocked.converted', 'This quote has been converted to an order')
|
|
43
|
+
case 'no_permission': return t('cpq.priceAdjustments.blocked.permission', 'You do not have permission to adjust prices')
|
|
44
|
+
case 'disabled': return t('cpq.priceAdjustments.blocked.disabled', 'Manual price adjustments are turned off')
|
|
45
|
+
default: return t('cpq.priceAdjustments.blocked.loading', 'Loading…')
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Disabled-state explanation for one line; null when the action is available. */
|
|
50
|
+
export function useAdjustmentBlockReason(line: AdjustableLine): string | null {
|
|
51
|
+
const t = useT()
|
|
52
|
+
const ui = usePriceAdjustmentUi()
|
|
53
|
+
const accessText = useAccessReasonText()
|
|
54
|
+
if (!ui) return null
|
|
55
|
+
const block = lineAdjustmentBlock(line, ui.quoteType)
|
|
56
|
+
if (block === 'mirror') return t('cpq.priceAdjustments.blocked.mirror', 'Price changes on existing subscription items arrive with a later release')
|
|
57
|
+
if (block === 'free_form') return t('cpq.priceAdjustments.blocked.freeForm', 'Free-form lines cannot be adjusted')
|
|
58
|
+
return accessText
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Disabled buttons swallow no pointer events, so a native `title` never shows.
|
|
63
|
+
* Wrap a blocked action in a focusable span that carries the explanation.
|
|
64
|
+
*/
|
|
65
|
+
function BlockedReason({ reason, children }: { reason: string | null; children: React.ReactNode }) {
|
|
66
|
+
if (!reason) return <>{children}</>
|
|
67
|
+
return (
|
|
68
|
+
<SimpleTooltip content={reason} size="sm">
|
|
69
|
+
<span className="inline-flex cursor-not-allowed" tabIndex={0} aria-label={reason}>{children}</span>
|
|
70
|
+
</SimpleTooltip>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Per-charge "Adjust price" action. Hidden entirely when the feature is off or not permitted. */
|
|
75
|
+
export function AdjustChargeAction({ line, charge }: { line: AdjustableLine; charge: AdjustableCharge }) {
|
|
76
|
+
const t = useT()
|
|
77
|
+
const ui = usePriceAdjustmentUi()
|
|
78
|
+
const reason = useAdjustmentBlockReason(line)
|
|
79
|
+
if (!ui || charge.chargeType === 'usage') return null
|
|
80
|
+
if (ui.access.reason === 'disabled' || ui.access.reason === 'no_permission' || ui.access.reason === 'loading') return null
|
|
81
|
+
const label = t('cpq.priceAdjustments.adjustPrice', 'Adjust price')
|
|
82
|
+
return (
|
|
83
|
+
<BlockedReason reason={reason}>
|
|
84
|
+
<IconButton
|
|
85
|
+
variant="ghost" size="xs" aria-label={label} title={reason ? undefined : label} disabled={!!reason}
|
|
86
|
+
onClick={() => ui.openCharge(line, charge)} data-testid="cpq-adjust-charge"
|
|
87
|
+
>
|
|
88
|
+
<Pencil className="h-3.5 w-3.5" />
|
|
89
|
+
</IconButton>
|
|
90
|
+
</BlockedReason>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Line-level "Discount line" action. */
|
|
95
|
+
export function DiscountLineAction({ line }: { line: AdjustableLine }) {
|
|
96
|
+
const t = useT()
|
|
97
|
+
const ui = usePriceAdjustmentUi()
|
|
98
|
+
const reason = useAdjustmentBlockReason(line)
|
|
99
|
+
if (!ui || ui.access.reason === 'disabled' || ui.access.reason === 'no_permission' || ui.access.reason === 'loading') return null
|
|
100
|
+
const label = t('cpq.priceAdjustments.discountLine', 'Discount line')
|
|
101
|
+
return (
|
|
102
|
+
<BlockedReason reason={reason}>
|
|
103
|
+
<IconButton
|
|
104
|
+
variant="ghost" size="sm" aria-label={label} title={reason ? undefined : label} disabled={!!reason}
|
|
105
|
+
onClick={() => ui.openLine(line)} data-testid="cpq-discount-line"
|
|
106
|
+
>
|
|
107
|
+
<Percent className="h-4 w-4" />
|
|
108
|
+
</IconButton>
|
|
109
|
+
</BlockedReason>
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Edit / delete actions on a manual waterfall row or panel row. */
|
|
114
|
+
export function ManualAdjustmentActions({ adjustmentId }: { adjustmentId: string }) {
|
|
115
|
+
const t = useT()
|
|
116
|
+
const ui = usePriceAdjustmentUi()
|
|
117
|
+
if (!ui) return null
|
|
118
|
+
const editable = ui.access.editable
|
|
119
|
+
const deletable = editable || (ui.canDeleteInvalid && ui.invalidIds.has(adjustmentId))
|
|
120
|
+
if (!editable && !deletable) return null
|
|
121
|
+
return (
|
|
122
|
+
<span className="inline-flex items-center gap-0.5">
|
|
123
|
+
{editable && (
|
|
124
|
+
<IconButton variant="ghost" size="xs" aria-label={t('cpq.priceAdjustments.edit', 'Edit adjustment')} onClick={() => ui.edit(adjustmentId)}>
|
|
125
|
+
<Pencil className="h-3.5 w-3.5" />
|
|
126
|
+
</IconButton>
|
|
127
|
+
)}
|
|
128
|
+
<IconButton variant="ghost" size="xs" aria-label={t('cpq.priceAdjustments.delete', 'Delete adjustment')} onClick={() => ui.remove(adjustmentId)}>
|
|
129
|
+
<Trash2 className="h-3.5 w-3.5" />
|
|
130
|
+
</IconButton>
|
|
131
|
+
</span>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
|
+
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
6
|
+
import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
|
|
7
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
8
|
+
import {
|
|
9
|
+
ADJUSTMENT_ERROR_FALLBACKS,
|
|
10
|
+
adjustmentAccess,
|
|
11
|
+
adjustmentErrorCode,
|
|
12
|
+
canDeleteInvalidAdjustment,
|
|
13
|
+
collectAdjustmentErrors,
|
|
14
|
+
type AdjustmentErrorSourceQuote,
|
|
15
|
+
type AdjustmentTarget,
|
|
16
|
+
} from '../../../../../lib/priceAdjustmentView'
|
|
17
|
+
import { PriceAdjustmentDialog, type AdjustmentReason, type ExistingAdjustment } from './PriceAdjustmentDialog'
|
|
18
|
+
import type { AdjustableCharge, AdjustableLine, PriceAdjustmentUi } from './priceAdjustmentContext'
|
|
19
|
+
import type { StoredAdjustment } from './PriceAdjustmentsPanel'
|
|
20
|
+
|
|
21
|
+
type ControllerQuote = {
|
|
22
|
+
id: string
|
|
23
|
+
cpqStatus: string
|
|
24
|
+
currencyCode: string
|
|
25
|
+
convertedOrderId: string | null
|
|
26
|
+
pricingRevision?: number
|
|
27
|
+
adjustmentErrors?: Array<{ adjustmentId: string; code: string }>
|
|
28
|
+
lines: Array<AdjustableLine & { mrcTotal: number; nrcTotal: number; charges: AdjustableCharge[] }>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type ListResponse = {
|
|
32
|
+
items: StoredAdjustment[]
|
|
33
|
+
total: number
|
|
34
|
+
pricingRevision: number
|
|
35
|
+
enabled: boolean
|
|
36
|
+
reasons?: AdjustmentReason[]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type DialogState = {
|
|
40
|
+
target: AdjustmentTarget
|
|
41
|
+
targetLabel: string
|
|
42
|
+
charge?: AdjustableCharge
|
|
43
|
+
existing?: ExistingAdjustment | null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Owns negotiated-price state for the quote page: the adjustment list,
|
|
48
|
+
* reason options, the dialog and delete confirmation. Every write returns the
|
|
49
|
+
* repriced quote, which is handed back through `onQuote`.
|
|
50
|
+
*/
|
|
51
|
+
export function usePriceAdjustments<Q extends ControllerQuote>({ quote, quoteType, canAdjust, onQuote, reloadQuote }: {
|
|
52
|
+
quote: Q | null
|
|
53
|
+
quoteType: string
|
|
54
|
+
canAdjust: boolean
|
|
55
|
+
onQuote: (quote: Q) => void
|
|
56
|
+
reloadQuote: () => Promise<void>
|
|
57
|
+
}) {
|
|
58
|
+
const t = useT()
|
|
59
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog()
|
|
60
|
+
const [list, setList] = React.useState<ListResponse | null>(null)
|
|
61
|
+
const [dialog, setDialog] = React.useState<DialogState | null>(null)
|
|
62
|
+
const quoteId = quote?.id ?? null
|
|
63
|
+
const revision = quote?.pricingRevision ?? 0
|
|
64
|
+
|
|
65
|
+
const loadList = React.useCallback(async () => {
|
|
66
|
+
if (!quoteId) return
|
|
67
|
+
const call = await apiCall<ListResponse>(`/api/cpq/quotes/${quoteId}/price-adjustments?pageSize=100`)
|
|
68
|
+
if (call.ok && call.result) setList(call.result)
|
|
69
|
+
else setList({ items: [], total: 0, pricingRevision: 0, enabled: false })
|
|
70
|
+
}, [quoteId])
|
|
71
|
+
|
|
72
|
+
// Refresh whenever the quote's pricing revision moves (own writes, line edits, recalculation).
|
|
73
|
+
React.useEffect(() => { void loadList() }, [loadList, revision])
|
|
74
|
+
|
|
75
|
+
const reasons = React.useMemo(() => list?.reasons ?? [], [list])
|
|
76
|
+
const reasonLabels = React.useMemo(() => Object.fromEntries(reasons.map((reason) => [reason.value, reason.label])), [reasons])
|
|
77
|
+
const errorsById = React.useMemo(() => collectAdjustmentErrors(quote as AdjustmentErrorSourceQuote | null), [quote])
|
|
78
|
+
|
|
79
|
+
const accessInput = {
|
|
80
|
+
enabled: list ? list.enabled : null,
|
|
81
|
+
canAdjust,
|
|
82
|
+
cpqStatus: quote?.cpqStatus ?? 'new',
|
|
83
|
+
convertedOrderId: quote?.convertedOrderId ?? null,
|
|
84
|
+
}
|
|
85
|
+
const access = adjustmentAccess(accessInput)
|
|
86
|
+
|
|
87
|
+
const remove = React.useCallback(async (adjustmentId: string) => {
|
|
88
|
+
if (!quoteId) return
|
|
89
|
+
const ok = await confirm({
|
|
90
|
+
title: t('cpq.priceAdjustments.deleteConfirm.title', 'Remove this price adjustment?'),
|
|
91
|
+
text: t('cpq.priceAdjustments.deleteConfirm.text', 'The charge goes back to its standard price.'),
|
|
92
|
+
confirmText: t('cpq.priceAdjustments.deleteConfirm.confirm', 'Remove'),
|
|
93
|
+
variant: 'destructive',
|
|
94
|
+
})
|
|
95
|
+
if (!ok) return
|
|
96
|
+
const call = await apiCall<{ quote: Q }>(`/api/cpq/quotes/${quoteId}/price-adjustments/${adjustmentId}?expectedRevision=${revision}`, { method: 'DELETE' })
|
|
97
|
+
if (call.ok && call.result?.quote) { onQuote(call.result.quote); return }
|
|
98
|
+
const code = adjustmentErrorCode(call.status, call.result)
|
|
99
|
+
flash(t(`cpq.priceAdjustments.errors.${code}`, ADJUSTMENT_ERROR_FALLBACKS[code] ?? code), 'error')
|
|
100
|
+
if (code === 'STALE_REVISION') await reloadQuote()
|
|
101
|
+
}, [quoteId, revision, confirm, t, onQuote, reloadQuote])
|
|
102
|
+
|
|
103
|
+
const ui: PriceAdjustmentUi = {
|
|
104
|
+
access,
|
|
105
|
+
quoteType,
|
|
106
|
+
cpqStatus: accessInput.cpqStatus,
|
|
107
|
+
reasonLabels,
|
|
108
|
+
invalidIds: new Set(errorsById.keys()),
|
|
109
|
+
canDeleteInvalid: canDeleteInvalidAdjustment(accessInput),
|
|
110
|
+
openCharge: (line, charge) => {
|
|
111
|
+
const existing = list?.items.find((item) => item.level === 'charge' && item.quoteLineId === line.lineId && item.chargeCode === charge.chargeCode)
|
|
112
|
+
setDialog({
|
|
113
|
+
target: { level: 'charge', quoteLineId: line.lineId, chargeCode: charge.chargeCode },
|
|
114
|
+
targetLabel: `${line.offeringName} · ${charge.chargeName}`,
|
|
115
|
+
charge,
|
|
116
|
+
existing: existing ?? null,
|
|
117
|
+
})
|
|
118
|
+
},
|
|
119
|
+
openLine: (line) => {
|
|
120
|
+
const existing = list?.items.find((item) => item.level === 'line' && item.quoteLineId === line.lineId)
|
|
121
|
+
setDialog({ target: { level: 'line', quoteLineId: line.lineId }, targetLabel: line.offeringName, existing: existing ?? null })
|
|
122
|
+
},
|
|
123
|
+
edit: (adjustmentId) => {
|
|
124
|
+
const item = list?.items.find((entry) => entry.id === adjustmentId)
|
|
125
|
+
const line = quote?.lines.find((entry) => entry.lineId === item?.quoteLineId)
|
|
126
|
+
if (!item || !line) return
|
|
127
|
+
const charge = item.chargeCode ? line.charges.find((entry) => entry.chargeCode === item.chargeCode) : undefined
|
|
128
|
+
setDialog({
|
|
129
|
+
target: item.level === 'charge' && item.chargeCode
|
|
130
|
+
? { level: 'charge', quoteLineId: item.quoteLineId, chargeCode: item.chargeCode }
|
|
131
|
+
: { level: 'line', quoteLineId: item.quoteLineId },
|
|
132
|
+
targetLabel: charge ? `${line.offeringName} · ${charge.chargeName}` : line.offeringName,
|
|
133
|
+
charge,
|
|
134
|
+
existing: item,
|
|
135
|
+
})
|
|
136
|
+
},
|
|
137
|
+
remove: (adjustmentId) => { void remove(adjustmentId) },
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const dialogElement = quote && dialog ? (
|
|
141
|
+
<PriceAdjustmentDialog<Q>
|
|
142
|
+
open
|
|
143
|
+
quoteId={quote.id}
|
|
144
|
+
currency={quote.currencyCode}
|
|
145
|
+
pricingRevision={revision}
|
|
146
|
+
target={dialog.target}
|
|
147
|
+
targetLabel={dialog.targetLabel}
|
|
148
|
+
charge={dialog.charge}
|
|
149
|
+
existing={dialog.existing}
|
|
150
|
+
storedAdjustmentIds={list?.items.map((item) => item.id) ?? []}
|
|
151
|
+
reasons={reasons}
|
|
152
|
+
onClose={() => setDialog(null)}
|
|
153
|
+
onSaved={(next) => onQuote(next)}
|
|
154
|
+
onStale={() => { void reloadQuote() }}
|
|
155
|
+
/>
|
|
156
|
+
) : null
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
ui,
|
|
160
|
+
items: list?.items ?? [],
|
|
161
|
+
total: list?.total ?? 0,
|
|
162
|
+
errorsById,
|
|
163
|
+
elements: <>{dialogElement}{ConfirmDialogElement}</>,
|
|
164
|
+
}
|
|
165
|
+
}
|