@dainamite/cpq 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/MIGRATION.md +74 -0
- package/dist/modules/cpq/acl.js +1 -0
- package/dist/modules/cpq/acl.js.map +2 -2
- package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js +45 -0
- package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js.map +7 -0
- package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js +23 -0
- package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js.map +7 -0
- package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js +19 -0
- package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js.map +7 -0
- package/dist/modules/cpq/api/orders/[orderId]/route.js +17 -1
- package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/route.js +47 -2
- package/dist/modules/cpq/api/orders/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js +54 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +42 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js +47 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js +30 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js +47 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +32 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +33 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/price/route.js +43 -3
- package/dist/modules/cpq/api/quotes/price/route.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +22 -8
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +89 -232
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +205 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +144 -221
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
- package/dist/modules/cpq/ce.js +8 -0
- package/dist/modules/cpq/ce.js.map +2 -2
- package/dist/modules/cpq/cli.js +34 -1
- package/dist/modules/cpq/cli.js.map +2 -2
- package/dist/modules/cpq/commands/price-adjustments.js +107 -0
- package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
- package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
- package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js +154 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
- package/dist/modules/cpq/components/PriceWaterfall.js +66 -0
- package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js +117 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
- package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
- package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
- package/dist/modules/cpq/data/entities.js +97 -0
- package/dist/modules/cpq/data/entities.js.map +2 -2
- package/dist/modules/cpq/data/price-adjustments.js +70 -0
- package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
- package/dist/modules/cpq/data/validators.js +4 -0
- package/dist/modules/cpq/data/validators.js.map +2 -2
- package/dist/modules/cpq/di.js +6 -0
- package/dist/modules/cpq/di.js.map +2 -2
- package/dist/modules/cpq/events.js +47 -0
- package/dist/modules/cpq/events.js.map +2 -2
- package/dist/modules/cpq/index.js +1 -1
- package/dist/modules/cpq/index.js.map +2 -2
- package/dist/modules/cpq/lib/auditActor.js +10 -0
- package/dist/modules/cpq/lib/auditActor.js.map +7 -0
- package/dist/modules/cpq/lib/formatMoney.js +7 -0
- package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
- package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
- package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
- package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
- package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
- package/dist/modules/cpq/lib/quotePermissions.js +13 -0
- package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
- package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
- package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
- package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
- package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
- package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
- package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
- package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
- package/dist/modules/cpq/services/cpqOrderService.js +25 -28
- package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
- package/dist/modules/cpq/services/cpqPricingService.js +7 -0
- package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
- package/dist/modules/cpq/services/cpqQuotingService.js +330 -109
- package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
- package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
- package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
- package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
- package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
- package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
- package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
- package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
- package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
- package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
- package/dist/modules/cpq/services/types.js.map +2 -2
- package/dist/modules/cpq/setup.js +6 -0
- package/dist/modules/cpq/setup.js.map +2 -2
- package/package.json +1 -1
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
- package/src/modules/cpq/acl.ts +1 -0
- package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
- package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
- package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
- package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
- package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
- package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
- package/src/modules/cpq/api/orders/[orderId]/route.ts +18 -0
- package/src/modules/cpq/api/orders/route.ts +51 -1
- package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
- package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +48 -1
- package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
- package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
- package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +40 -1
- package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
- package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
- package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
- package/src/modules/cpq/api/quotes/price/route.ts +28 -2
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +60 -269
- package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +174 -263
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
- package/src/modules/cpq/ce.ts +8 -0
- package/src/modules/cpq/cli.ts +48 -1
- package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
- package/src/modules/cpq/commands/price-adjustments.ts +90 -0
- package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
- package/src/modules/cpq/components/ChargeBreakdownTable.tsx +201 -0
- package/src/modules/cpq/components/PriceWaterfall.tsx +94 -0
- package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
- package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
- package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
- package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
- package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
- package/src/modules/cpq/data/entities.ts +83 -0
- package/src/modules/cpq/data/price-adjustments.ts +67 -0
- package/src/modules/cpq/data/validators.ts +3 -0
- package/src/modules/cpq/di.ts +6 -0
- package/src/modules/cpq/events.ts +53 -2
- package/src/modules/cpq/index.ts +1 -1
- package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
- package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
- package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
- package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
- package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
- package/src/modules/cpq/lib/auditActor.ts +13 -0
- package/src/modules/cpq/lib/formatMoney.ts +4 -0
- package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
- package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
- package/src/modules/cpq/lib/quotePermissions.ts +22 -0
- package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
- package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
- package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
- package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
- package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
- package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
- package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
- package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
- package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
- package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
- package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
- package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
- package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
- package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
- package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
- package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
- package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
- package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
- package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
- package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
- package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
- package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
- package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
- package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
- package/src/modules/cpq/services/cpqOrderService.ts +22 -28
- package/src/modules/cpq/services/cpqPricingService.ts +8 -0
- package/src/modules/cpq/services/cpqQuotingService.ts +374 -125
- package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
- package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
- package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
- package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
- package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
- package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
- package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
- package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
- package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
- package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
- package/src/modules/cpq/services/types.ts +34 -1
- package/src/modules/cpq/setup.ts +7 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
5
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
6
|
+
import { InjectionSpot } from "@open-mercato/ui/backend/injection/InjectionSpot";
|
|
7
|
+
import { ADJUSTMENT_ERROR_FALLBACKS } from "../../../../../lib/priceAdjustmentView.js";
|
|
8
|
+
import { ManualAdjustmentActions, useAccessReasonText, usePriceAdjustmentUi } from "./priceAdjustmentContext.js";
|
|
9
|
+
const PRICE_ADJUSTMENTS_SPOT_ID = "data-table:cpq.quote_price_adjustments";
|
|
10
|
+
const TYPE_FALLBACKS = {
|
|
11
|
+
price_override: "Price override",
|
|
12
|
+
discount_percent: "% discount",
|
|
13
|
+
discount_absolute: "Amount off"
|
|
14
|
+
};
|
|
15
|
+
function money(amount, currency) {
|
|
16
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
17
|
+
}
|
|
18
|
+
function PriceAdjustmentsPanel({ quoteId, items, total, lines, currency, errorsById }) {
|
|
19
|
+
const t = useT();
|
|
20
|
+
const ui = usePriceAdjustmentUi();
|
|
21
|
+
const lineById = React.useMemo(() => new Map(lines.map((line) => [line.lineId, line])), [lines]);
|
|
22
|
+
const accessText = useAccessReasonText();
|
|
23
|
+
if (!ui || ui.access.reason === "disabled" && items.length === 0) return null;
|
|
24
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card", "data-testid": "cpq-price-adjustments-panel", children: [
|
|
25
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
|
|
26
|
+
/* @__PURE__ */ jsxs("h3", { className: "text-sm font-medium", children: [
|
|
27
|
+
t("cpq.priceAdjustments.panel.title", "Price adjustments"),
|
|
28
|
+
/* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: total })
|
|
29
|
+
] }),
|
|
30
|
+
accessText && ui.access.reason !== "loading" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", "data-testid": "cpq-price-adjustments-locked", children: accessText })
|
|
31
|
+
] }),
|
|
32
|
+
items.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-3 text-xs text-muted-foreground", children: t("cpq.priceAdjustments.panel.empty", "No negotiated prices yet. Use \u201CAdjust price\u201D on a charge or \u201CDiscount line\u201D on a line.") }) : /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
33
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
34
|
+
/* @__PURE__ */ jsx("th", { className: "px-4 py-1.5 text-left font-medium", children: t("cpq.priceAdjustments.panel.target", "Applies to") }),
|
|
35
|
+
/* @__PURE__ */ jsx("th", { className: "py-1.5 text-left font-medium", children: t("cpq.priceAdjustments.panel.type", "Type") }),
|
|
36
|
+
/* @__PURE__ */ jsx("th", { className: "py-1.5 text-right font-medium", children: t("cpq.priceAdjustments.panel.value", "Value") }),
|
|
37
|
+
/* @__PURE__ */ jsx("th", { className: "py-1.5 pl-4 text-left font-medium", children: t("cpq.priceAdjustments.panel.reason", "Reason") }),
|
|
38
|
+
/* @__PURE__ */ jsx("th", { className: "py-1.5 text-left font-medium", children: t("cpq.priceAdjustments.panel.author", "Author") }),
|
|
39
|
+
/* @__PURE__ */ jsx("th", { className: "py-1.5 text-left font-medium", children: t("cpq.priceAdjustments.panel.date", "Date") }),
|
|
40
|
+
/* @__PURE__ */ jsx("th", { className: "w-16" })
|
|
41
|
+
] }) }),
|
|
42
|
+
/* @__PURE__ */ jsx("tbody", { children: items.map((item) => {
|
|
43
|
+
const line = lineById.get(item.quoteLineId);
|
|
44
|
+
const chargeName = item.chargeCode ? line?.charges.find((charge) => charge.chargeCode === item.chargeCode)?.chargeName ?? item.chargeCode : null;
|
|
45
|
+
const scope = item.level === "line" ? item.chargeTypeFilter === "mrc" ? t("cpq.priceAdjustments.panel.lineMrc", "whole line, recurring") : item.chargeTypeFilter === "nrc" ? t("cpq.priceAdjustments.panel.lineNrc", "whole line, one-time") : t("cpq.priceAdjustments.panel.line", "whole line") : chargeName;
|
|
46
|
+
const error = errorsById.get(item.id);
|
|
47
|
+
return /* @__PURE__ */ jsxs("tr", { className: "border-t", "data-testid": "cpq-price-adjustment-row", children: [
|
|
48
|
+
/* @__PURE__ */ jsxs("td", { className: "px-4 py-1.5", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: line?.offeringName ?? t("cpq.priceAdjustments.panel.removedLine", "Removed line") }),
|
|
50
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
51
|
+
" \xB7 ",
|
|
52
|
+
scope
|
|
53
|
+
] }),
|
|
54
|
+
error && /* @__PURE__ */ jsxs(Tag, { variant: "warning", className: "ml-1.5 px-1.5 text-xs", children: [
|
|
55
|
+
t("cpq.priceAdjustments.invalid", "Invalid"),
|
|
56
|
+
": ",
|
|
57
|
+
t(`cpq.priceAdjustments.errors.${error}`, ADJUSTMENT_ERROR_FALLBACKS[error] ?? error)
|
|
58
|
+
] })
|
|
59
|
+
] }),
|
|
60
|
+
/* @__PURE__ */ jsx("td", { className: "py-1.5", children: t(`cpq.priceAdjustments.panel.types.${item.adjustmentType}`, TYPE_FALLBACKS[item.adjustmentType]) }),
|
|
61
|
+
/* @__PURE__ */ jsx("td", { className: "py-1.5 text-right font-mono", children: item.adjustmentType === "discount_percent" ? `${item.value}%` : money(item.value, item.currencyCode ?? currency) }),
|
|
62
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1.5 pl-4", children: [
|
|
63
|
+
ui.reasonLabels[item.reasonCode] ?? item.reasonCode,
|
|
64
|
+
item.reasonNote && /* @__PURE__ */ jsx("span", { className: "block text-muted-foreground", children: item.reasonNote })
|
|
65
|
+
] }),
|
|
66
|
+
/* @__PURE__ */ jsx("td", { className: "py-1.5 font-mono text-muted-foreground", title: item.createdByUserId, children: item.createdByUserId.slice(0, 8) }),
|
|
67
|
+
/* @__PURE__ */ jsx("td", { className: "py-1.5 text-muted-foreground", children: new Date(item.createdAt).toLocaleDateString() }),
|
|
68
|
+
/* @__PURE__ */ jsx("td", { className: "py-1.5 pr-2 text-right", children: /* @__PURE__ */ jsx(ManualAdjustmentActions, { adjustmentId: item.id }) })
|
|
69
|
+
] }, item.id);
|
|
70
|
+
}) })
|
|
71
|
+
] }),
|
|
72
|
+
/* @__PURE__ */ jsx(InjectionSpot, { spotId: PRICE_ADJUSTMENTS_SPOT_ID, context: { quoteId, adjustments: items } })
|
|
73
|
+
] });
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
PRICE_ADJUSTMENTS_SPOT_ID,
|
|
77
|
+
PriceAdjustmentsPanel
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=PriceAdjustmentsPanel.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/cpq/backend/cpq/quotes/%5Bid%5D/_components/PriceAdjustmentsPanel.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { ADJUSTMENT_ERROR_FALLBACKS } from '../../../../../lib/priceAdjustmentView'\nimport { ManualAdjustmentActions, useAccessReasonText, usePriceAdjustmentUi } from './priceAdjustmentContext'\n\nexport const PRICE_ADJUSTMENTS_SPOT_ID = 'data-table:cpq.quote_price_adjustments'\n\nexport type StoredAdjustment = {\n id: string\n level: 'charge' | 'line'\n quoteLineId: string\n chargeCode?: string | null\n chargeTypeFilter?: 'nrc' | 'mrc' | null\n adjustmentType: 'discount_percent' | 'discount_absolute' | 'price_override'\n value: number\n currencyCode?: string | null\n reasonCode: string\n reasonNote?: string | null\n createdByUserId: string\n createdAt: string\n}\n\ntype LineRef = { lineId: string; offeringName: string; offeringId: string | null; configuration?: Record<string, unknown> | null; charges: Array<{ chargeCode: string; chargeName: string }> }\n\nconst TYPE_FALLBACKS: Record<StoredAdjustment['adjustmentType'], string> = {\n price_override: 'Price override',\n discount_percent: '% discount',\n discount_absolute: 'Amount off',\n}\n\nfunction money(amount: number, currency: string) {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nexport function PriceAdjustmentsPanel({ quoteId, items, total, lines, currency, errorsById }: {\n quoteId: string\n items: StoredAdjustment[]\n total: number\n lines: LineRef[]\n currency: string\n /** adjustmentId \u2192 first error code reported by the latest pricing. */\n errorsById: Map<string, string>\n}) {\n const t = useT()\n const ui = usePriceAdjustmentUi()\n const lineById = React.useMemo(() => new Map(lines.map((line) => [line.lineId, line])), [lines])\n const accessText = useAccessReasonText()\n // Toggle off: stored adjustments still price the quote, so keep them visible (read-only).\n if (!ui || (ui.access.reason === 'disabled' && items.length === 0)) return null\n\n return (\n <div className=\"rounded-lg border bg-card\" data-testid=\"cpq-price-adjustments-panel\">\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <h3 className=\"text-sm font-medium\">\n {t('cpq.priceAdjustments.panel.title', 'Price adjustments')}\n <span className=\"ml-2 text-xs text-muted-foreground\">{total}</span>\n </h3>\n {accessText && ui.access.reason !== 'loading' && (\n <span className=\"text-xs text-muted-foreground\" data-testid=\"cpq-price-adjustments-locked\">{accessText}</span>\n )}\n </div>\n {items.length === 0 ? (\n <p className=\"px-4 py-3 text-xs text-muted-foreground\">\n {t('cpq.priceAdjustments.panel.empty', 'No negotiated prices yet. Use \u201CAdjust price\u201D on a charge or \u201CDiscount line\u201D on a line.')}\n </p>\n ) : (\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"px-4 py-1.5 text-left font-medium\">{t('cpq.priceAdjustments.panel.target', 'Applies to')}</th>\n <th className=\"py-1.5 text-left font-medium\">{t('cpq.priceAdjustments.panel.type', 'Type')}</th>\n <th className=\"py-1.5 text-right font-medium\">{t('cpq.priceAdjustments.panel.value', 'Value')}</th>\n <th className=\"py-1.5 pl-4 text-left font-medium\">{t('cpq.priceAdjustments.panel.reason', 'Reason')}</th>\n <th className=\"py-1.5 text-left font-medium\">{t('cpq.priceAdjustments.panel.author', 'Author')}</th>\n <th className=\"py-1.5 text-left font-medium\">{t('cpq.priceAdjustments.panel.date', 'Date')}</th>\n <th className=\"w-16\" />\n </tr>\n </thead>\n <tbody>\n {items.map((item) => {\n const line = lineById.get(item.quoteLineId)\n const chargeName = item.chargeCode ? line?.charges.find((charge) => charge.chargeCode === item.chargeCode)?.chargeName ?? item.chargeCode : null\n const scope = item.level === 'line'\n ? item.chargeTypeFilter === 'mrc' ? t('cpq.priceAdjustments.panel.lineMrc', 'whole line, recurring')\n : item.chargeTypeFilter === 'nrc' ? t('cpq.priceAdjustments.panel.lineNrc', 'whole line, one-time')\n : t('cpq.priceAdjustments.panel.line', 'whole line')\n : chargeName\n const error = errorsById.get(item.id)\n return (\n <tr key={item.id} className=\"border-t\" data-testid=\"cpq-price-adjustment-row\">\n <td className=\"px-4 py-1.5\">\n <span className=\"font-medium\">{line?.offeringName ?? t('cpq.priceAdjustments.panel.removedLine', 'Removed line')}</span>\n <span className=\"text-muted-foreground\"> \u00B7 {scope}</span>\n {error && (\n <Tag variant=\"warning\" className=\"ml-1.5 px-1.5 text-xs\">\n {t('cpq.priceAdjustments.invalid', 'Invalid')}: {t(`cpq.priceAdjustments.errors.${error}`, ADJUSTMENT_ERROR_FALLBACKS[error] ?? error)}\n </Tag>\n )}\n </td>\n <td className=\"py-1.5\">{t(`cpq.priceAdjustments.panel.types.${item.adjustmentType}`, TYPE_FALLBACKS[item.adjustmentType])}</td>\n <td className=\"py-1.5 text-right font-mono\">\n {item.adjustmentType === 'discount_percent' ? `${item.value}%` : money(item.value, item.currencyCode ?? currency)}\n </td>\n <td className=\"py-1.5 pl-4\">\n {ui.reasonLabels[item.reasonCode] ?? item.reasonCode}\n {item.reasonNote && <span className=\"block text-muted-foreground\">{item.reasonNote}</span>}\n </td>\n <td className=\"py-1.5 font-mono text-muted-foreground\" title={item.createdByUserId}>{item.createdByUserId.slice(0, 8)}</td>\n <td className=\"py-1.5 text-muted-foreground\">{new Date(item.createdAt).toLocaleDateString()}</td>\n <td className=\"py-1.5 pr-2 text-right\"><ManualAdjustmentActions adjustmentId={item.id} /></td>\n </tr>\n )\n })}\n </tbody>\n </table>\n )}\n <InjectionSpot spotId={PRICE_ADJUSTMENTS_SPOT_ID} context={{ quoteId, adjustments: items }} />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAyDQ,SAEE,KAFF;AAvDR,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,WAAW;AACpB,SAAS,qBAAqB;AAC9B,SAAS,kCAAkC;AAC3C,SAAS,yBAAyB,qBAAqB,4BAA4B;AAE5E,MAAM,4BAA4B;AAmBzC,MAAM,iBAAqE;AAAA,EACzE,gBAAgB;AAAA,EAChB,kBAAkB;AAAA,EAClB,mBAAmB;AACrB;AAEA,SAAS,MAAM,QAAgB,UAAkB;AAC/C,SAAO,IAAI,KAAK,aAAa,SAAS,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,MAAM;AACtF;AAEO,SAAS,sBAAsB,EAAE,SAAS,OAAO,OAAO,OAAO,UAAU,WAAW,GAQxF;AACD,QAAM,IAAI,KAAK;AACf,QAAM,KAAK,qBAAqB;AAChC,QAAM,WAAW,MAAM,QAAQ,MAAM,IAAI,IAAI,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC;AAC/F,QAAM,aAAa,oBAAoB;AAEvC,MAAI,CAAC,MAAO,GAAG,OAAO,WAAW,cAAc,MAAM,WAAW,EAAI,QAAO;AAE3E,SACE,qBAAC,SAAI,WAAU,6BAA4B,eAAY,+BACrD;AAAA,yBAAC,SAAI,WAAU,wDACb;AAAA,2BAAC,QAAG,WAAU,uBACX;AAAA,UAAE,oCAAoC,mBAAmB;AAAA,QAC1D,oBAAC,UAAK,WAAU,sCAAsC,iBAAM;AAAA,SAC9D;AAAA,MACC,cAAc,GAAG,OAAO,WAAW,aAClC,oBAAC,UAAK,WAAU,iCAAgC,eAAY,gCAAgC,sBAAW;AAAA,OAE3G;AAAA,IACC,MAAM,WAAW,IAChB,oBAAC,OAAE,WAAU,2CACV,YAAE,oCAAoC,4GAAwF,GACjI,IAEA,qBAAC,WAAM,WAAU,kBACf;AAAA,0BAAC,WACC,+BAAC,QAAG,WAAU,yBACZ;AAAA,4BAAC,QAAG,WAAU,qCAAqC,YAAE,qCAAqC,YAAY,GAAE;AAAA,QACxG,oBAAC,QAAG,WAAU,gCAAgC,YAAE,mCAAmC,MAAM,GAAE;AAAA,QAC3F,oBAAC,QAAG,WAAU,iCAAiC,YAAE,oCAAoC,OAAO,GAAE;AAAA,QAC9F,oBAAC,QAAG,WAAU,qCAAqC,YAAE,qCAAqC,QAAQ,GAAE;AAAA,QACpG,oBAAC,QAAG,WAAU,gCAAgC,YAAE,qCAAqC,QAAQ,GAAE;AAAA,QAC/F,oBAAC,QAAG,WAAU,gCAAgC,YAAE,mCAAmC,MAAM,GAAE;AAAA,QAC3F,oBAAC,QAAG,WAAU,QAAO;AAAA,SACvB,GACF;AAAA,MACA,oBAAC,WACE,gBAAM,IAAI,CAAC,SAAS;AACnB,cAAM,OAAO,SAAS,IAAI,KAAK,WAAW;AAC1C,cAAM,aAAa,KAAK,aAAa,MAAM,QAAQ,KAAK,CAAC,WAAW,OAAO,eAAe,KAAK,UAAU,GAAG,cAAc,KAAK,aAAa;AAC5I,cAAM,QAAQ,KAAK,UAAU,SACzB,KAAK,qBAAqB,QAAQ,EAAE,sCAAsC,uBAAuB,IAC/F,KAAK,qBAAqB,QAAQ,EAAE,sCAAsC,sBAAsB,IAC9F,EAAE,mCAAmC,YAAY,IACrD;AACJ,cAAM,QAAQ,WAAW,IAAI,KAAK,EAAE;AACpC,eACE,qBAAC,QAAiB,WAAU,YAAW,eAAY,4BACjD;AAAA,+BAAC,QAAG,WAAU,eACZ;AAAA,gCAAC,UAAK,WAAU,eAAe,gBAAM,gBAAgB,EAAE,0CAA0C,cAAc,GAAE;AAAA,YACjH,qBAAC,UAAK,WAAU,yBAAwB;AAAA;AAAA,cAAI;AAAA,eAAM;AAAA,YACjD,SACC,qBAAC,OAAI,SAAQ,WAAU,WAAU,yBAC9B;AAAA,gBAAE,gCAAgC,SAAS;AAAA,cAAE;AAAA,cAAG,EAAE,+BAA+B,KAAK,IAAI,2BAA2B,KAAK,KAAK,KAAK;AAAA,eACvI;AAAA,aAEJ;AAAA,UACA,oBAAC,QAAG,WAAU,UAAU,YAAE,oCAAoC,KAAK,cAAc,IAAI,eAAe,KAAK,cAAc,CAAC,GAAE;AAAA,UAC1H,oBAAC,QAAG,WAAU,+BACX,eAAK,mBAAmB,qBAAqB,GAAG,KAAK,KAAK,MAAM,MAAM,KAAK,OAAO,KAAK,gBAAgB,QAAQ,GAClH;AAAA,UACA,qBAAC,QAAG,WAAU,eACX;AAAA,eAAG,aAAa,KAAK,UAAU,KAAK,KAAK;AAAA,YACzC,KAAK,cAAc,oBAAC,UAAK,WAAU,+BAA+B,eAAK,YAAW;AAAA,aACrF;AAAA,UACA,oBAAC,QAAG,WAAU,0CAAyC,OAAO,KAAK,iBAAkB,eAAK,gBAAgB,MAAM,GAAG,CAAC,GAAE;AAAA,UACtH,oBAAC,QAAG,WAAU,gCAAgC,cAAI,KAAK,KAAK,SAAS,EAAE,mBAAmB,GAAE;AAAA,UAC5F,oBAAC,QAAG,WAAU,0BAAyB,8BAAC,2BAAwB,cAAc,KAAK,IAAI,GAAE;AAAA,aApBlF,KAAK,EAqBd;AAAA,MAEJ,CAAC,GACH;AAAA,OACF;AAAA,IAEF,oBAAC,iBAAc,QAAQ,2BAA2B,SAAS,EAAE,SAAS,aAAa,MAAM,GAAG;AAAA,KAC9F;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
|
|
5
|
+
import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
|
|
6
|
+
import { isArcCancelledLine, removalCancelsItem } from "../../../../../services/arcLineRemoval.js";
|
|
7
|
+
const ArcLineRemovalContext = React.createContext(null);
|
|
8
|
+
const ArcLineRemovalProvider = ArcLineRemovalContext.Provider;
|
|
9
|
+
function useArcLineRemovalUi() {
|
|
10
|
+
return React.useContext(ArcLineRemovalContext);
|
|
11
|
+
}
|
|
12
|
+
function useArcLineRemoval({ quote, quoteType, onQuote, onError }) {
|
|
13
|
+
const t = useT();
|
|
14
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
15
|
+
const send = React.useCallback(async (url, method) => {
|
|
16
|
+
onError(null);
|
|
17
|
+
const call = await apiCall(url, { method });
|
|
18
|
+
if (call.ok && call.result) onQuote(call.result);
|
|
19
|
+
else onError(call.result?.error ?? t("cpq.quotes.lineUpdateFailed", "Could not update the line"));
|
|
20
|
+
}, [onError, onQuote, t]);
|
|
21
|
+
const remove = React.useCallback(async (lineId) => {
|
|
22
|
+
if (!quote) return;
|
|
23
|
+
const line = quote.lines.find((candidate) => candidate.lineId === lineId);
|
|
24
|
+
if (line && removalCancelsItem(line, quoteType)) {
|
|
25
|
+
const ok = await confirm({
|
|
26
|
+
title: t("cpq.quotes.arc.removeConfirm.title", "Remove {name} from the subscription?").replace("{name}", line.offeringName),
|
|
27
|
+
text: t(
|
|
28
|
+
"cpq.quotes.arc.removeConfirm.text",
|
|
29
|
+
'This item already exists on the subscription. Removing it here does not delete it from the quote \u2014 it stays as a "remove" line, and when the order is activated the item (and its components) is cancelled on the subscription and its recurring charge stops. You can restore it until then.'
|
|
30
|
+
),
|
|
31
|
+
confirmText: t("cpq.quotes.arc.removeConfirm.confirm", "Remove item"),
|
|
32
|
+
variant: "destructive"
|
|
33
|
+
});
|
|
34
|
+
if (!ok) return;
|
|
35
|
+
}
|
|
36
|
+
await send(`/api/cpq/quotes/${quote.id}/items/${lineId}`, "DELETE");
|
|
37
|
+
}, [confirm, quote, quoteType, send, t]);
|
|
38
|
+
const restore = React.useCallback((lineId) => {
|
|
39
|
+
if (!quote) return;
|
|
40
|
+
void send(`/api/cpq/quotes/${quote.id}/items/${lineId}/restore`, "POST");
|
|
41
|
+
}, [quote, send]);
|
|
42
|
+
const ui = React.useMemo(() => ({
|
|
43
|
+
isRemoved: (line) => isArcCancelledLine(line, quoteType),
|
|
44
|
+
restore
|
|
45
|
+
}), [quoteType, restore]);
|
|
46
|
+
return { remove, ui, ConfirmDialogElement };
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
ArcLineRemovalProvider,
|
|
50
|
+
useArcLineRemoval,
|
|
51
|
+
useArcLineRemovalUi
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=arcLineRemoval.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/cpq/backend/cpq/quotes/%5Bid%5D/_components/arcLineRemoval.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { isArcCancelledLine, removalCancelsItem, type ArcRemovalLine } from '../../../../../services/arcLineRemoval'\n\ntype RemovableLine = Pick<ArcRemovalLine, 'action' | 'configuration'> & { lineId: string; offeringName: string }\n\nexport type ArcLineRemovalUi = {\n /** The line is an existing subscription item marked for removal. */\n isRemoved: (line: Pick<RemovableLine, 'action'>) => boolean\n restore: (lineId: string) => void\n}\n\nconst ArcLineRemovalContext = React.createContext<ArcLineRemovalUi | null>(null)\n\nexport const ArcLineRemovalProvider = ArcLineRemovalContext.Provider\n\nexport function useArcLineRemovalUi(): ArcLineRemovalUi | null {\n return React.useContext(ArcLineRemovalContext)\n}\n\n/**\n * Line removal on the quote page. On an Amend / Renew quote, removing a line\n * that mirrors a live subscription item asks for confirmation and marks the\n * item for removal (server turns it into a `cancel` line); it can be restored.\n * Other lines are deleted as before.\n */\nexport function useArcLineRemoval<Q extends { id: string; lines: RemovableLine[] }>({ quote, quoteType, onQuote, onError }: {\n quote: Q | null\n quoteType: string\n onQuote: (quote: Q) => void\n onError: (message: string | null) => void\n}) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n const send = React.useCallback(async (url: string, method: 'DELETE' | 'POST') => {\n onError(null)\n const call = await apiCall<Q & { error?: string }>(url, { method })\n if (call.ok && call.result) onQuote(call.result)\n else onError(call.result?.error ?? t('cpq.quotes.lineUpdateFailed', 'Could not update the line'))\n }, [onError, onQuote, t])\n\n const remove = React.useCallback(async (lineId: string) => {\n if (!quote) return\n const line = quote.lines.find((candidate) => candidate.lineId === lineId)\n if (line && removalCancelsItem(line, quoteType)) {\n const ok = await confirm({\n title: t('cpq.quotes.arc.removeConfirm.title', 'Remove {name} from the subscription?').replace('{name}', line.offeringName),\n text: t(\n 'cpq.quotes.arc.removeConfirm.text',\n 'This item already exists on the subscription. Removing it here does not delete it from the quote \u2014 it stays as a \"remove\" line, and when the order is activated the item (and its components) is cancelled on the subscription and its recurring charge stops. You can restore it until then.',\n ),\n confirmText: t('cpq.quotes.arc.removeConfirm.confirm', 'Remove item'),\n variant: 'destructive',\n })\n if (!ok) return\n }\n await send(`/api/cpq/quotes/${quote.id}/items/${lineId}`, 'DELETE')\n }, [confirm, quote, quoteType, send, t])\n\n const restore = React.useCallback((lineId: string) => {\n if (!quote) return\n void send(`/api/cpq/quotes/${quote.id}/items/${lineId}/restore`, 'POST')\n }, [quote, send])\n\n const ui = React.useMemo<ArcLineRemovalUi>(() => ({\n isRemoved: (line) => isArcCancelledLine(line, quoteType),\n restore,\n }), [quoteType, restore])\n\n return { remove, ui, ConfirmDialogElement }\n}\n"],
|
|
5
|
+
"mappings": ";AAEA,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,eAAe;AACxB,SAAS,wBAAwB;AACjC,SAAS,oBAAoB,0BAA+C;AAU5E,MAAM,wBAAwB,MAAM,cAAuC,IAAI;AAExE,MAAM,yBAAyB,sBAAsB;AAErD,SAAS,sBAA+C;AAC7D,SAAO,MAAM,WAAW,qBAAqB;AAC/C;AAQO,SAAS,kBAAoE,EAAE,OAAO,WAAW,SAAS,QAAQ,GAKtH;AACD,QAAM,IAAI,KAAK;AACf,QAAM,EAAE,SAAS,qBAAqB,IAAI,iBAAiB;AAE3D,QAAM,OAAO,MAAM,YAAY,OAAO,KAAa,WAA8B;AAC/E,YAAQ,IAAI;AACZ,UAAM,OAAO,MAAM,QAAgC,KAAK,EAAE,OAAO,CAAC;AAClE,QAAI,KAAK,MAAM,KAAK,OAAQ,SAAQ,KAAK,MAAM;AAAA,QAC1C,SAAQ,KAAK,QAAQ,SAAS,EAAE,+BAA+B,2BAA2B,CAAC;AAAA,EAClG,GAAG,CAAC,SAAS,SAAS,CAAC,CAAC;AAExB,QAAM,SAAS,MAAM,YAAY,OAAO,WAAmB;AACzD,QAAI,CAAC,MAAO;AACZ,UAAM,OAAO,MAAM,MAAM,KAAK,CAAC,cAAc,UAAU,WAAW,MAAM;AACxE,QAAI,QAAQ,mBAAmB,MAAM,SAAS,GAAG;AAC/C,YAAM,KAAK,MAAM,QAAQ;AAAA,QACvB,OAAO,EAAE,sCAAsC,sCAAsC,EAAE,QAAQ,UAAU,KAAK,YAAY;AAAA,QAC1H,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,QACF;AAAA,QACA,aAAa,EAAE,wCAAwC,aAAa;AAAA,QACpE,SAAS;AAAA,MACX,CAAC;AACD,UAAI,CAAC,GAAI;AAAA,IACX;AACA,UAAM,KAAK,mBAAmB,MAAM,EAAE,UAAU,MAAM,IAAI,QAAQ;AAAA,EACpE,GAAG,CAAC,SAAS,OAAO,WAAW,MAAM,CAAC,CAAC;AAEvC,QAAM,UAAU,MAAM,YAAY,CAAC,WAAmB;AACpD,QAAI,CAAC,MAAO;AACZ,SAAK,KAAK,mBAAmB,MAAM,EAAE,UAAU,MAAM,YAAY,MAAM;AAAA,EACzE,GAAG,CAAC,OAAO,IAAI,CAAC;AAEhB,QAAM,KAAK,MAAM,QAA0B,OAAO;AAAA,IAChD,WAAW,CAAC,SAAS,mBAAmB,MAAM,SAAS;AAAA,IACvD;AAAA,EACF,IAAI,CAAC,WAAW,OAAO,CAAC;AAExB,SAAO,EAAE,QAAQ,IAAI,qBAAqB;AAC5C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
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 { lineAdjustmentBlock } from "../../../../../lib/priceAdjustmentView.js";
|
|
9
|
+
const Context = React.createContext(null);
|
|
10
|
+
const PriceAdjustmentProvider = Context.Provider;
|
|
11
|
+
const usePriceAdjustmentUi = () => React.useContext(Context);
|
|
12
|
+
function useAccessReasonText() {
|
|
13
|
+
const t = useT();
|
|
14
|
+
const ui = usePriceAdjustmentUi();
|
|
15
|
+
if (!ui) return null;
|
|
16
|
+
switch (ui.access.reason) {
|
|
17
|
+
case null:
|
|
18
|
+
return null;
|
|
19
|
+
case "locked":
|
|
20
|
+
return t("cpq.priceAdjustments.blocked.locked", "Adjustments are locked in status {status} \u2014 move the quote back to Ready to change them").replace("{status}", ui.cpqStatus);
|
|
21
|
+
case "converted":
|
|
22
|
+
return t("cpq.priceAdjustments.blocked.converted", "This quote has been converted to an order");
|
|
23
|
+
case "no_permission":
|
|
24
|
+
return t("cpq.priceAdjustments.blocked.permission", "You do not have permission to adjust prices");
|
|
25
|
+
case "disabled":
|
|
26
|
+
return t("cpq.priceAdjustments.blocked.disabled", "Manual price adjustments are turned off");
|
|
27
|
+
default:
|
|
28
|
+
return t("cpq.priceAdjustments.blocked.loading", "Loading\u2026");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function useAdjustmentBlockReason(line) {
|
|
32
|
+
const t = useT();
|
|
33
|
+
const ui = usePriceAdjustmentUi();
|
|
34
|
+
const accessText = useAccessReasonText();
|
|
35
|
+
if (!ui) return null;
|
|
36
|
+
const block = lineAdjustmentBlock(line, ui.quoteType);
|
|
37
|
+
if (block === "mirror") return t("cpq.priceAdjustments.blocked.mirror", "Price changes on existing subscription items arrive with a later release");
|
|
38
|
+
if (block === "free_form") return t("cpq.priceAdjustments.blocked.freeForm", "Free-form lines cannot be adjusted");
|
|
39
|
+
return accessText;
|
|
40
|
+
}
|
|
41
|
+
function BlockedReason({ reason, children }) {
|
|
42
|
+
if (!reason) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
43
|
+
return /* @__PURE__ */ jsx(SimpleTooltip, { content: reason, size: "sm", children: /* @__PURE__ */ jsx("span", { className: "inline-flex cursor-not-allowed", tabIndex: 0, "aria-label": reason, children }) });
|
|
44
|
+
}
|
|
45
|
+
function AdjustChargeAction({ line, charge }) {
|
|
46
|
+
const t = useT();
|
|
47
|
+
const ui = usePriceAdjustmentUi();
|
|
48
|
+
const reason = useAdjustmentBlockReason(line);
|
|
49
|
+
if (!ui || charge.chargeType === "usage") return null;
|
|
50
|
+
if (ui.access.reason === "disabled" || ui.access.reason === "no_permission" || ui.access.reason === "loading") return null;
|
|
51
|
+
const label = t("cpq.priceAdjustments.adjustPrice", "Adjust price");
|
|
52
|
+
return /* @__PURE__ */ jsx(BlockedReason, { reason, children: /* @__PURE__ */ jsx(
|
|
53
|
+
IconButton,
|
|
54
|
+
{
|
|
55
|
+
variant: "ghost",
|
|
56
|
+
size: "xs",
|
|
57
|
+
"aria-label": label,
|
|
58
|
+
title: reason ? void 0 : label,
|
|
59
|
+
disabled: !!reason,
|
|
60
|
+
onClick: () => ui.openCharge(line, charge),
|
|
61
|
+
"data-testid": "cpq-adjust-charge",
|
|
62
|
+
children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" })
|
|
63
|
+
}
|
|
64
|
+
) });
|
|
65
|
+
}
|
|
66
|
+
function DiscountLineAction({ line }) {
|
|
67
|
+
const t = useT();
|
|
68
|
+
const ui = usePriceAdjustmentUi();
|
|
69
|
+
const reason = useAdjustmentBlockReason(line);
|
|
70
|
+
if (!ui || ui.access.reason === "disabled" || ui.access.reason === "no_permission" || ui.access.reason === "loading") return null;
|
|
71
|
+
const label = t("cpq.priceAdjustments.discountLine", "Discount line");
|
|
72
|
+
return /* @__PURE__ */ jsx(BlockedReason, { reason, children: /* @__PURE__ */ jsx(
|
|
73
|
+
IconButton,
|
|
74
|
+
{
|
|
75
|
+
variant: "ghost",
|
|
76
|
+
size: "sm",
|
|
77
|
+
"aria-label": label,
|
|
78
|
+
title: reason ? void 0 : label,
|
|
79
|
+
disabled: !!reason,
|
|
80
|
+
onClick: () => ui.openLine(line),
|
|
81
|
+
"data-testid": "cpq-discount-line",
|
|
82
|
+
children: /* @__PURE__ */ jsx(Percent, { className: "h-4 w-4" })
|
|
83
|
+
}
|
|
84
|
+
) });
|
|
85
|
+
}
|
|
86
|
+
function ManualAdjustmentActions({ adjustmentId }) {
|
|
87
|
+
const t = useT();
|
|
88
|
+
const ui = usePriceAdjustmentUi();
|
|
89
|
+
if (!ui) return null;
|
|
90
|
+
const editable = ui.access.editable;
|
|
91
|
+
const deletable = editable || ui.canDeleteInvalid && ui.invalidIds.has(adjustmentId);
|
|
92
|
+
if (!editable && !deletable) return null;
|
|
93
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-0.5", children: [
|
|
94
|
+
editable && /* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "xs", "aria-label": t("cpq.priceAdjustments.edit", "Edit adjustment"), onClick: () => ui.edit(adjustmentId), children: /* @__PURE__ */ jsx(Pencil, { className: "h-3.5 w-3.5" }) }),
|
|
95
|
+
/* @__PURE__ */ jsx(IconButton, { variant: "ghost", size: "xs", "aria-label": t("cpq.priceAdjustments.delete", "Delete adjustment"), onClick: () => ui.remove(adjustmentId), children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" }) })
|
|
96
|
+
] });
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
AdjustChargeAction,
|
|
100
|
+
DiscountLineAction,
|
|
101
|
+
ManualAdjustmentActions,
|
|
102
|
+
PriceAdjustmentProvider,
|
|
103
|
+
useAccessReasonText,
|
|
104
|
+
useAdjustmentBlockReason,
|
|
105
|
+
usePriceAdjustmentUi
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=priceAdjustmentContext.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/cpq/backend/cpq/quotes/%5Bid%5D/_components/priceAdjustmentContext.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Percent, Pencil, Trash2 } from 'lucide-react'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport type { AdjustmentAccess, LineEligibilityInput, WaterfallChargeInput } from '../../../../../lib/priceAdjustmentView'\nimport { lineAdjustmentBlock } from '../../../../../lib/priceAdjustmentView'\n\nexport type AdjustableLine = LineEligibilityInput & { lineId: string; offeringName: string }\nexport type AdjustableCharge = WaterfallChargeInput & { chargeCode: string; chargeName: string }\n\nexport type PriceAdjustmentUi = {\n access: AdjustmentAccess\n quoteType: string\n cpqStatus: string\n reasonLabels: Record<string, string>\n /** Ids of adjustments currently flagged invalid (they keep Delete while locked). */\n invalidIds: Set<string>\n canDeleteInvalid: boolean\n openCharge: (line: AdjustableLine, charge: AdjustableCharge) => void\n openLine: (line: AdjustableLine) => void\n edit: (adjustmentId: string) => void\n remove: (adjustmentId: string) => void\n}\n\nconst Context = React.createContext<PriceAdjustmentUi | null>(null)\nexport const PriceAdjustmentProvider = Context.Provider\nexport const usePriceAdjustmentUi = () => React.useContext(Context)\n\n/** Quote-wide explanation for why adjustments are unavailable; null when editable. */\nexport function useAccessReasonText(): string | null {\n const t = useT()\n const ui = usePriceAdjustmentUi()\n if (!ui) return null\n switch (ui.access.reason) {\n case null: return null\n case 'locked':\n return t('cpq.priceAdjustments.blocked.locked', 'Adjustments are locked in status {status} \u2014 move the quote back to Ready to change them')\n .replace('{status}', ui.cpqStatus)\n case 'converted': return t('cpq.priceAdjustments.blocked.converted', 'This quote has been converted to an order')\n case 'no_permission': return t('cpq.priceAdjustments.blocked.permission', 'You do not have permission to adjust prices')\n case 'disabled': return t('cpq.priceAdjustments.blocked.disabled', 'Manual price adjustments are turned off')\n default: return t('cpq.priceAdjustments.blocked.loading', 'Loading\u2026')\n }\n}\n\n/** Disabled-state explanation for one line; null when the action is available. */\nexport function useAdjustmentBlockReason(line: AdjustableLine): string | null {\n const t = useT()\n const ui = usePriceAdjustmentUi()\n const accessText = useAccessReasonText()\n if (!ui) return null\n const block = lineAdjustmentBlock(line, ui.quoteType)\n if (block === 'mirror') return t('cpq.priceAdjustments.blocked.mirror', 'Price changes on existing subscription items arrive with a later release')\n if (block === 'free_form') return t('cpq.priceAdjustments.blocked.freeForm', 'Free-form lines cannot be adjusted')\n return accessText\n}\n\n/**\n * Disabled buttons swallow no pointer events, so a native `title` never shows.\n * Wrap a blocked action in a focusable span that carries the explanation.\n */\nfunction BlockedReason({ reason, children }: { reason: string | null; children: React.ReactNode }) {\n if (!reason) return <>{children}</>\n return (\n <SimpleTooltip content={reason} size=\"sm\">\n <span className=\"inline-flex cursor-not-allowed\" tabIndex={0} aria-label={reason}>{children}</span>\n </SimpleTooltip>\n )\n}\n\n/** Per-charge \"Adjust price\" action. Hidden entirely when the feature is off or not permitted. */\nexport function AdjustChargeAction({ line, charge }: { line: AdjustableLine; charge: AdjustableCharge }) {\n const t = useT()\n const ui = usePriceAdjustmentUi()\n const reason = useAdjustmentBlockReason(line)\n if (!ui || charge.chargeType === 'usage') return null\n if (ui.access.reason === 'disabled' || ui.access.reason === 'no_permission' || ui.access.reason === 'loading') return null\n const label = t('cpq.priceAdjustments.adjustPrice', 'Adjust price')\n return (\n <BlockedReason reason={reason}>\n <IconButton\n variant=\"ghost\" size=\"xs\" aria-label={label} title={reason ? undefined : label} disabled={!!reason}\n onClick={() => ui.openCharge(line, charge)} data-testid=\"cpq-adjust-charge\"\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </IconButton>\n </BlockedReason>\n )\n}\n\n/** Line-level \"Discount line\" action. */\nexport function DiscountLineAction({ line }: { line: AdjustableLine }) {\n const t = useT()\n const ui = usePriceAdjustmentUi()\n const reason = useAdjustmentBlockReason(line)\n if (!ui || ui.access.reason === 'disabled' || ui.access.reason === 'no_permission' || ui.access.reason === 'loading') return null\n const label = t('cpq.priceAdjustments.discountLine', 'Discount line')\n return (\n <BlockedReason reason={reason}>\n <IconButton\n variant=\"ghost\" size=\"sm\" aria-label={label} title={reason ? undefined : label} disabled={!!reason}\n onClick={() => ui.openLine(line)} data-testid=\"cpq-discount-line\"\n >\n <Percent className=\"h-4 w-4\" />\n </IconButton>\n </BlockedReason>\n )\n}\n\n/** Edit / delete actions on a manual waterfall row or panel row. */\nexport function ManualAdjustmentActions({ adjustmentId }: { adjustmentId: string }) {\n const t = useT()\n const ui = usePriceAdjustmentUi()\n if (!ui) return null\n const editable = ui.access.editable\n const deletable = editable || (ui.canDeleteInvalid && ui.invalidIds.has(adjustmentId))\n if (!editable && !deletable) return null\n return (\n <span className=\"inline-flex items-center gap-0.5\">\n {editable && (\n <IconButton variant=\"ghost\" size=\"xs\" aria-label={t('cpq.priceAdjustments.edit', 'Edit adjustment')} onClick={() => ui.edit(adjustmentId)}>\n <Pencil className=\"h-3.5 w-3.5\" />\n </IconButton>\n )}\n <IconButton variant=\"ghost\" size=\"xs\" aria-label={t('cpq.priceAdjustments.delete', 'Delete adjustment')} onClick={() => ui.remove(adjustmentId)}>\n <Trash2 className=\"h-3.5 w-3.5\" />\n </IconButton>\n </span>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
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
|
+
} from "../../../../../lib/priceAdjustmentView.js";
|
|
15
|
+
import { PriceAdjustmentDialog } from "./PriceAdjustmentDialog.js";
|
|
16
|
+
function usePriceAdjustments({ quote, quoteType, canAdjust, onQuote, reloadQuote }) {
|
|
17
|
+
const t = useT();
|
|
18
|
+
const { confirm, ConfirmDialogElement } = useConfirmDialog();
|
|
19
|
+
const [list, setList] = React.useState(null);
|
|
20
|
+
const [dialog, setDialog] = React.useState(null);
|
|
21
|
+
const quoteId = quote?.id ?? null;
|
|
22
|
+
const revision = quote?.pricingRevision ?? 0;
|
|
23
|
+
const loadList = React.useCallback(async () => {
|
|
24
|
+
if (!quoteId) return;
|
|
25
|
+
const call = await apiCall(`/api/cpq/quotes/${quoteId}/price-adjustments?pageSize=100`);
|
|
26
|
+
if (call.ok && call.result) setList(call.result);
|
|
27
|
+
else setList({ items: [], total: 0, pricingRevision: 0, enabled: false });
|
|
28
|
+
}, [quoteId]);
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
void loadList();
|
|
31
|
+
}, [loadList, revision]);
|
|
32
|
+
const reasons = React.useMemo(() => list?.reasons ?? [], [list]);
|
|
33
|
+
const reasonLabels = React.useMemo(() => Object.fromEntries(reasons.map((reason) => [reason.value, reason.label])), [reasons]);
|
|
34
|
+
const errorsById = React.useMemo(() => collectAdjustmentErrors(quote), [quote]);
|
|
35
|
+
const accessInput = {
|
|
36
|
+
enabled: list ? list.enabled : null,
|
|
37
|
+
canAdjust,
|
|
38
|
+
cpqStatus: quote?.cpqStatus ?? "new",
|
|
39
|
+
convertedOrderId: quote?.convertedOrderId ?? null
|
|
40
|
+
};
|
|
41
|
+
const access = adjustmentAccess(accessInput);
|
|
42
|
+
const remove = React.useCallback(async (adjustmentId) => {
|
|
43
|
+
if (!quoteId) return;
|
|
44
|
+
const ok = await confirm({
|
|
45
|
+
title: t("cpq.priceAdjustments.deleteConfirm.title", "Remove this price adjustment?"),
|
|
46
|
+
text: t("cpq.priceAdjustments.deleteConfirm.text", "The charge goes back to its standard price."),
|
|
47
|
+
confirmText: t("cpq.priceAdjustments.deleteConfirm.confirm", "Remove"),
|
|
48
|
+
variant: "destructive"
|
|
49
|
+
});
|
|
50
|
+
if (!ok) return;
|
|
51
|
+
const call = await apiCall(`/api/cpq/quotes/${quoteId}/price-adjustments/${adjustmentId}?expectedRevision=${revision}`, { method: "DELETE" });
|
|
52
|
+
if (call.ok && call.result?.quote) {
|
|
53
|
+
onQuote(call.result.quote);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const code = adjustmentErrorCode(call.status, call.result);
|
|
57
|
+
flash(t(`cpq.priceAdjustments.errors.${code}`, ADJUSTMENT_ERROR_FALLBACKS[code] ?? code), "error");
|
|
58
|
+
if (code === "STALE_REVISION") await reloadQuote();
|
|
59
|
+
}, [quoteId, revision, confirm, t, onQuote, reloadQuote]);
|
|
60
|
+
const ui = {
|
|
61
|
+
access,
|
|
62
|
+
quoteType,
|
|
63
|
+
cpqStatus: accessInput.cpqStatus,
|
|
64
|
+
reasonLabels,
|
|
65
|
+
invalidIds: new Set(errorsById.keys()),
|
|
66
|
+
canDeleteInvalid: canDeleteInvalidAdjustment(accessInput),
|
|
67
|
+
openCharge: (line, charge) => {
|
|
68
|
+
const existing = list?.items.find((item) => item.level === "charge" && item.quoteLineId === line.lineId && item.chargeCode === charge.chargeCode);
|
|
69
|
+
setDialog({
|
|
70
|
+
target: { level: "charge", quoteLineId: line.lineId, chargeCode: charge.chargeCode },
|
|
71
|
+
targetLabel: `${line.offeringName} \xB7 ${charge.chargeName}`,
|
|
72
|
+
charge,
|
|
73
|
+
existing: existing ?? null
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
openLine: (line) => {
|
|
77
|
+
const existing = list?.items.find((item) => item.level === "line" && item.quoteLineId === line.lineId);
|
|
78
|
+
setDialog({ target: { level: "line", quoteLineId: line.lineId }, targetLabel: line.offeringName, existing: existing ?? null });
|
|
79
|
+
},
|
|
80
|
+
edit: (adjustmentId) => {
|
|
81
|
+
const item = list?.items.find((entry) => entry.id === adjustmentId);
|
|
82
|
+
const line = quote?.lines.find((entry) => entry.lineId === item?.quoteLineId);
|
|
83
|
+
if (!item || !line) return;
|
|
84
|
+
const charge = item.chargeCode ? line.charges.find((entry) => entry.chargeCode === item.chargeCode) : void 0;
|
|
85
|
+
setDialog({
|
|
86
|
+
target: item.level === "charge" && item.chargeCode ? { level: "charge", quoteLineId: item.quoteLineId, chargeCode: item.chargeCode } : { level: "line", quoteLineId: item.quoteLineId },
|
|
87
|
+
targetLabel: charge ? `${line.offeringName} \xB7 ${charge.chargeName}` : line.offeringName,
|
|
88
|
+
charge,
|
|
89
|
+
existing: item
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
remove: (adjustmentId) => {
|
|
93
|
+
void remove(adjustmentId);
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
const dialogElement = quote && dialog ? /* @__PURE__ */ jsx(
|
|
97
|
+
PriceAdjustmentDialog,
|
|
98
|
+
{
|
|
99
|
+
open: true,
|
|
100
|
+
quoteId: quote.id,
|
|
101
|
+
currency: quote.currencyCode,
|
|
102
|
+
pricingRevision: revision,
|
|
103
|
+
target: dialog.target,
|
|
104
|
+
targetLabel: dialog.targetLabel,
|
|
105
|
+
charge: dialog.charge,
|
|
106
|
+
existing: dialog.existing,
|
|
107
|
+
storedAdjustmentIds: list?.items.map((item) => item.id) ?? [],
|
|
108
|
+
reasons,
|
|
109
|
+
onClose: () => setDialog(null),
|
|
110
|
+
onSaved: (next) => onQuote(next),
|
|
111
|
+
onStale: () => {
|
|
112
|
+
void reloadQuote();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
) : null;
|
|
116
|
+
return {
|
|
117
|
+
ui,
|
|
118
|
+
items: list?.items ?? [],
|
|
119
|
+
total: list?.total ?? 0,
|
|
120
|
+
errorsById,
|
|
121
|
+
elements: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
122
|
+
dialogElement,
|
|
123
|
+
ConfirmDialogElement
|
|
124
|
+
] })
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
usePriceAdjustments
|
|
129
|
+
};
|
|
130
|
+
//# sourceMappingURL=usePriceAdjustments.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/cpq/backend/cpq/quotes/%5Bid%5D/_components/usePriceAdjustments.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport {\n ADJUSTMENT_ERROR_FALLBACKS,\n adjustmentAccess,\n adjustmentErrorCode,\n canDeleteInvalidAdjustment,\n collectAdjustmentErrors,\n type AdjustmentErrorSourceQuote,\n type AdjustmentTarget,\n} from '../../../../../lib/priceAdjustmentView'\nimport { PriceAdjustmentDialog, type AdjustmentReason, type ExistingAdjustment } from './PriceAdjustmentDialog'\nimport type { AdjustableCharge, AdjustableLine, PriceAdjustmentUi } from './priceAdjustmentContext'\nimport type { StoredAdjustment } from './PriceAdjustmentsPanel'\n\ntype ControllerQuote = {\n id: string\n cpqStatus: string\n currencyCode: string\n convertedOrderId: string | null\n pricingRevision?: number\n adjustmentErrors?: Array<{ adjustmentId: string; code: string }>\n lines: Array<AdjustableLine & { mrcTotal: number; nrcTotal: number; charges: AdjustableCharge[] }>\n}\n\ntype ListResponse = {\n items: StoredAdjustment[]\n total: number\n pricingRevision: number\n enabled: boolean\n reasons?: AdjustmentReason[]\n}\n\ntype DialogState = {\n target: AdjustmentTarget\n targetLabel: string\n charge?: AdjustableCharge\n existing?: ExistingAdjustment | null\n}\n\n/**\n * Owns negotiated-price state for the quote page: the adjustment list,\n * reason options, the dialog and delete confirmation. Every write returns the\n * repriced quote, which is handed back through `onQuote`.\n */\nexport function usePriceAdjustments<Q extends ControllerQuote>({ quote, quoteType, canAdjust, onQuote, reloadQuote }: {\n quote: Q | null\n quoteType: string\n canAdjust: boolean\n onQuote: (quote: Q) => void\n reloadQuote: () => Promise<void>\n}) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [list, setList] = React.useState<ListResponse | null>(null)\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const quoteId = quote?.id ?? null\n const revision = quote?.pricingRevision ?? 0\n\n const loadList = React.useCallback(async () => {\n if (!quoteId) return\n const call = await apiCall<ListResponse>(`/api/cpq/quotes/${quoteId}/price-adjustments?pageSize=100`)\n if (call.ok && call.result) setList(call.result)\n else setList({ items: [], total: 0, pricingRevision: 0, enabled: false })\n }, [quoteId])\n\n // Refresh whenever the quote's pricing revision moves (own writes, line edits, recalculation).\n React.useEffect(() => { void loadList() }, [loadList, revision])\n\n const reasons = React.useMemo(() => list?.reasons ?? [], [list])\n const reasonLabels = React.useMemo(() => Object.fromEntries(reasons.map((reason) => [reason.value, reason.label])), [reasons])\n const errorsById = React.useMemo(() => collectAdjustmentErrors(quote as AdjustmentErrorSourceQuote | null), [quote])\n\n const accessInput = {\n enabled: list ? list.enabled : null,\n canAdjust,\n cpqStatus: quote?.cpqStatus ?? 'new',\n convertedOrderId: quote?.convertedOrderId ?? null,\n }\n const access = adjustmentAccess(accessInput)\n\n const remove = React.useCallback(async (adjustmentId: string) => {\n if (!quoteId) return\n const ok = await confirm({\n title: t('cpq.priceAdjustments.deleteConfirm.title', 'Remove this price adjustment?'),\n text: t('cpq.priceAdjustments.deleteConfirm.text', 'The charge goes back to its standard price.'),\n confirmText: t('cpq.priceAdjustments.deleteConfirm.confirm', 'Remove'),\n variant: 'destructive',\n })\n if (!ok) return\n const call = await apiCall<{ quote: Q }>(`/api/cpq/quotes/${quoteId}/price-adjustments/${adjustmentId}?expectedRevision=${revision}`, { method: 'DELETE' })\n if (call.ok && call.result?.quote) { onQuote(call.result.quote); return }\n const code = adjustmentErrorCode(call.status, call.result)\n flash(t(`cpq.priceAdjustments.errors.${code}`, ADJUSTMENT_ERROR_FALLBACKS[code] ?? code), 'error')\n if (code === 'STALE_REVISION') await reloadQuote()\n }, [quoteId, revision, confirm, t, onQuote, reloadQuote])\n\n const ui: PriceAdjustmentUi = {\n access,\n quoteType,\n cpqStatus: accessInput.cpqStatus,\n reasonLabels,\n invalidIds: new Set(errorsById.keys()),\n canDeleteInvalid: canDeleteInvalidAdjustment(accessInput),\n openCharge: (line, charge) => {\n const existing = list?.items.find((item) => item.level === 'charge' && item.quoteLineId === line.lineId && item.chargeCode === charge.chargeCode)\n setDialog({\n target: { level: 'charge', quoteLineId: line.lineId, chargeCode: charge.chargeCode },\n targetLabel: `${line.offeringName} \u00B7 ${charge.chargeName}`,\n charge,\n existing: existing ?? null,\n })\n },\n openLine: (line) => {\n const existing = list?.items.find((item) => item.level === 'line' && item.quoteLineId === line.lineId)\n setDialog({ target: { level: 'line', quoteLineId: line.lineId }, targetLabel: line.offeringName, existing: existing ?? null })\n },\n edit: (adjustmentId) => {\n const item = list?.items.find((entry) => entry.id === adjustmentId)\n const line = quote?.lines.find((entry) => entry.lineId === item?.quoteLineId)\n if (!item || !line) return\n const charge = item.chargeCode ? line.charges.find((entry) => entry.chargeCode === item.chargeCode) : undefined\n setDialog({\n target: item.level === 'charge' && item.chargeCode\n ? { level: 'charge', quoteLineId: item.quoteLineId, chargeCode: item.chargeCode }\n : { level: 'line', quoteLineId: item.quoteLineId },\n targetLabel: charge ? `${line.offeringName} \u00B7 ${charge.chargeName}` : line.offeringName,\n charge,\n existing: item,\n })\n },\n remove: (adjustmentId) => { void remove(adjustmentId) },\n }\n\n const dialogElement = quote && dialog ? (\n <PriceAdjustmentDialog<Q>\n open\n quoteId={quote.id}\n currency={quote.currencyCode}\n pricingRevision={revision}\n target={dialog.target}\n targetLabel={dialog.targetLabel}\n charge={dialog.charge}\n existing={dialog.existing}\n storedAdjustmentIds={list?.items.map((item) => item.id) ?? []}\n reasons={reasons}\n onClose={() => setDialog(null)}\n onSaved={(next) => onQuote(next)}\n onStale={() => { void reloadQuote() }}\n />\n ) : null\n\n return {\n ui,\n items: list?.items ?? [],\n total: list?.total ?? 0,\n errorsById,\n elements: <>{dialogElement}{ConfirmDialogElement}</>,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|