@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
package/dist/modules/cpq/ce.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
const entities = [
|
|
2
|
+
{
|
|
3
|
+
id: "cpq:cpq_quote_price_adjustment",
|
|
4
|
+
label: "Quote Price Adjustment",
|
|
5
|
+
description: "Negotiated charge or line discount on a quote.",
|
|
6
|
+
labelField: "reasonCode",
|
|
7
|
+
showInSidebar: false,
|
|
8
|
+
fields: []
|
|
9
|
+
},
|
|
2
10
|
{
|
|
3
11
|
id: "cpq:cpq_product_specification",
|
|
4
12
|
label: "Product Specification",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/cpq/ce.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CustomEntitySpec } from '@open-mercato/shared/modules/entities'\n\nconst entities: CustomEntitySpec[] = [\n {\n id: 'cpq:cpq_product_specification',\n label: 'Product Specification',\n description: 'CPQ product blueprint \u2014 defines attributes, dependencies, and relationships.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_offering',\n label: 'Product Offering',\n description: 'Sellable instance of a product specification with design-time values and charges.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_relationship',\n label: 'Product Relationship',\n description: 'Structural relationship between product specifications (parent_child, requires, excludes).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_attribute',\n label: 'Product Attribute',\n description: 'Configurable attribute definition for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_charge',\n label: 'Product Charge',\n description: 'Charge definition (NRC/MRC/usage) for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table',\n label: 'Pricing Table',\n description: 'Multi-dimensional pricing table with named price columns.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table_entry',\n label: 'Pricing Table Entry',\n description: 'Individual row in a pricing table with dimension values and prices.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_price_rule',\n label: 'Price Rule',\n description: 'Post-calculation price adjustment rule (discount, surcharge, override).',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_bundle_slot',\n label: 'Bundle Slot',\n description: 'Structural slot within a bundle specification \u2014 defines what component specs can fill it.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_offering_component',\n label: 'Offering Component',\n description: 'Links a bundle offering to a specific component offering within a slot.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_configuration',\n label: 'Quote Configuration',\n description: 'CPQ-specific configuration data for a sales quote.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_line_configuration',\n label: 'Quote Line Configuration',\n description: 'CPQ-specific configuration for a sales quote line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_wizard_definition',\n label: 'Wizard Definition',\n description: 'CPQ wizard definition \u2014 multi-step guided quoting flow.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription',\n label: 'Inventory Subscription',\n description: 'Commercial wrapper grouping subscription items under shared contractual terms.',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription_item',\n label: 'Inventory Subscription Item',\n description: 'Concrete product/service record within an inventory subscription.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_asset',\n label: 'Inventory Asset',\n description: 'Tangible or intangible thing the customer owns \u2014 hardware, licenses, fees.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_configuration',\n label: 'Order Configuration',\n description: 'CPQ-specific configuration data for a sales order.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_line_configuration',\n label: 'Order Line Configuration',\n description: 'CPQ-specific configuration for a sales order line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n]\n\nexport { entities }\nexport default entities\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { CustomEntitySpec } from '@open-mercato/shared/modules/entities'\n\nconst entities: CustomEntitySpec[] = [\n {\n id: 'cpq:cpq_quote_price_adjustment',\n label: 'Quote Price Adjustment',\n description: 'Negotiated charge or line discount on a quote.',\n labelField: 'reasonCode',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_specification',\n label: 'Product Specification',\n description: 'CPQ product blueprint \u2014 defines attributes, dependencies, and relationships.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_offering',\n label: 'Product Offering',\n description: 'Sellable instance of a product specification with design-time values and charges.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_relationship',\n label: 'Product Relationship',\n description: 'Structural relationship between product specifications (parent_child, requires, excludes).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_attribute',\n label: 'Product Attribute',\n description: 'Configurable attribute definition for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_charge',\n label: 'Product Charge',\n description: 'Charge definition (NRC/MRC/usage) for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table',\n label: 'Pricing Table',\n description: 'Multi-dimensional pricing table with named price columns.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table_entry',\n label: 'Pricing Table Entry',\n description: 'Individual row in a pricing table with dimension values and prices.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_price_rule',\n label: 'Price Rule',\n description: 'Post-calculation price adjustment rule (discount, surcharge, override).',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_bundle_slot',\n label: 'Bundle Slot',\n description: 'Structural slot within a bundle specification \u2014 defines what component specs can fill it.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_offering_component',\n label: 'Offering Component',\n description: 'Links a bundle offering to a specific component offering within a slot.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_configuration',\n label: 'Quote Configuration',\n description: 'CPQ-specific configuration data for a sales quote.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_line_configuration',\n label: 'Quote Line Configuration',\n description: 'CPQ-specific configuration for a sales quote line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_wizard_definition',\n label: 'Wizard Definition',\n description: 'CPQ wizard definition \u2014 multi-step guided quoting flow.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription',\n label: 'Inventory Subscription',\n description: 'Commercial wrapper grouping subscription items under shared contractual terms.',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription_item',\n label: 'Inventory Subscription Item',\n description: 'Concrete product/service record within an inventory subscription.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_asset',\n label: 'Inventory Asset',\n description: 'Tangible or intangible thing the customer owns \u2014 hardware, licenses, fees.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_configuration',\n label: 'Order Configuration',\n description: 'CPQ-specific configuration data for a sales order.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_line_configuration',\n label: 'Order Line Configuration',\n description: 'CPQ-specific configuration for a sales order line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n]\n\nexport { entities }\nexport default entities\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/modules/cpq/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
2
2
|
import { seedCpqDemo } from "./lib/seeds/demo.js";
|
|
3
|
+
import { seedPriceAdjustmentDefaults } from "./lib/priceAdjustmentDefaults.js";
|
|
3
4
|
function parseArgs(rest) {
|
|
4
5
|
const out = {};
|
|
5
6
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -58,7 +59,39 @@ const seed = {
|
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
|
-
|
|
62
|
+
const seedPriceAdjustments = {
|
|
63
|
+
command: "seed-price-adjustments",
|
|
64
|
+
async run(rest) {
|
|
65
|
+
const args = parseArgs(rest);
|
|
66
|
+
const container = await createRequestContainer();
|
|
67
|
+
const em = container.resolve("em");
|
|
68
|
+
const scopes = await resolveAllScopes(em, args);
|
|
69
|
+
if (scopes.length === 0) {
|
|
70
|
+
console.error("[cpq seed-price-adjustments] No matching organization found.");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let failed = 0;
|
|
74
|
+
for (const scope of scopes) {
|
|
75
|
+
try {
|
|
76
|
+
await seedPriceAdjustmentDefaults(em.fork(), scope);
|
|
77
|
+
console.log(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} \u2713`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
failed++;
|
|
80
|
+
console.error(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} failed`, error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (failed) process.exitCode = 1;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
async function resolveAllScopes(em, args) {
|
|
87
|
+
const { Organization } = await import("@open-mercato/core/modules/directory/data/entities");
|
|
88
|
+
const where = { isActive: true, deletedAt: null };
|
|
89
|
+
if (typeof args.org === "string") where.id = args.org;
|
|
90
|
+
if (typeof args.tenant === "string") where.tenant = args.tenant;
|
|
91
|
+
const orgs = await em.find(Organization, where, { orderBy: { createdAt: "asc" } });
|
|
92
|
+
return orgs.map((org) => ({ organizationId: org.id, tenantId: org.tenant.id }));
|
|
93
|
+
}
|
|
94
|
+
var cli_default = [seed, seedPriceAdjustments];
|
|
62
95
|
export {
|
|
63
96
|
cli_default as default
|
|
64
97
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/cpq/cli.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { seedCpqDemo } from './lib/seeds/demo'\n\n/**\n * `mercato cpq seed [--reset] [--tenant <id>] [--org <id>]`\n *\n * Loads a minimal, neutral CPQ test catalog (product + specification +\n * offering + charge) into a single tenant. With no `--tenant`/`--org` it\n * resolves the first (primary) organization created by `mercato init`.\n * `--reset` clears the seed's own rows first.\n *\n * For rich, vertical demo catalogs use `mercato demo_tenants seed --all`.\n */\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const token = rest[i]\n if (!token || !token.startsWith('--')) continue\n const key = token.slice(2)\n const next = rest[i + 1]\n if (next === undefined || next.startsWith('--')) out[key] = true\n else {\n out[key] = next\n i++\n }\n }\n return out\n}\n\nasync function resolveScope(\n em: EntityManager,\n args: Record<string, string | boolean>,\n): Promise<{ tenantId: string; organizationId: string } | null> {\n const orgArg = typeof args.org === 'string' ? args.org : undefined\n const tenantArg = typeof args.tenant === 'string' ? args.tenant : undefined\n\n const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')\n\n if (orgArg) {\n const org = await em.findOne(Organization, { id: orgArg } as never)\n if (!org) return null\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n }\n\n const orgs = await em.find(\n Organization,\n { isActive: true } as never,\n { orderBy: { createdAt: 'asc' } as never, limit: 5 },\n )\n if (orgs.length === 0) return null\n const org = orgs[0]\n if (orgs.length > 1) {\n console.warn(\n `[cpq seed] Multiple organizations found \u2014 defaulting to \"${org.name}\" (${org.id}). ` +\n `Pass --org <id> to target another.`,\n )\n }\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n}\n\nconst seed: ModuleCli = {\n command: 'seed',\n async run(rest) {\n const args = parseArgs(rest)\n const reset = Boolean(args.reset)\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n const scope = await resolveScope(em, args)\n if (!scope) {\n console.error('[cpq seed] No organization found. Run \"yarn initialize\" first, or pass --org <id>.')\n return\n }\n\n const res = await seedCpqDemo(em, scope, { reset })\n console.log(\n `[cpq seed] tenant=${scope.tenantId} org=${scope.organizationId}${reset ? ' (reset)' : ''}\\n` +\n ` \u2713 products +${res.productsCreated}, specs +${res.specsCreated}, ` +\n `offerings +${res.offeringsCreated}, charges +${res.chargesCreated} (existing rows left untouched)\\n` +\n ` \u2192 Backend \u00B7 CPQ \u00B7 Product Specifications / Product Offerings.`,\n )\n },\n}\n\nexport default [seed]\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,8BAA8B;AAEvC,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { seedCpqDemo } from './lib/seeds/demo'\nimport { seedPriceAdjustmentDefaults } from './lib/priceAdjustmentDefaults'\n\n/**\n * `mercato cpq seed [--reset] [--tenant <id>] [--org <id>]`\n *\n * Loads a minimal, neutral CPQ test catalog (product + specification +\n * offering + charge) into a single tenant. With no `--tenant`/`--org` it\n * resolves the first (primary) organization created by `mercato init`.\n * `--reset` clears the seed's own rows first.\n *\n * For rich, vertical demo catalogs use `mercato demo_tenants seed --all`.\n */\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const token = rest[i]\n if (!token || !token.startsWith('--')) continue\n const key = token.slice(2)\n const next = rest[i + 1]\n if (next === undefined || next.startsWith('--')) out[key] = true\n else {\n out[key] = next\n i++\n }\n }\n return out\n}\n\nasync function resolveScope(\n em: EntityManager,\n args: Record<string, string | boolean>,\n): Promise<{ tenantId: string; organizationId: string } | null> {\n const orgArg = typeof args.org === 'string' ? args.org : undefined\n const tenantArg = typeof args.tenant === 'string' ? args.tenant : undefined\n\n const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')\n\n if (orgArg) {\n const org = await em.findOne(Organization, { id: orgArg } as never)\n if (!org) return null\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n }\n\n const orgs = await em.find(\n Organization,\n { isActive: true } as never,\n { orderBy: { createdAt: 'asc' } as never, limit: 5 },\n )\n if (orgs.length === 0) return null\n const org = orgs[0]\n if (orgs.length > 1) {\n console.warn(\n `[cpq seed] Multiple organizations found \u2014 defaulting to \"${org.name}\" (${org.id}). ` +\n `Pass --org <id> to target another.`,\n )\n }\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n}\n\nconst seed: ModuleCli = {\n command: 'seed',\n async run(rest) {\n const args = parseArgs(rest)\n const reset = Boolean(args.reset)\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n const scope = await resolveScope(em, args)\n if (!scope) {\n console.error('[cpq seed] No organization found. Run \"yarn initialize\" first, or pass --org <id>.')\n return\n }\n\n const res = await seedCpqDemo(em, scope, { reset })\n console.log(\n `[cpq seed] tenant=${scope.tenantId} org=${scope.organizationId}${reset ? ' (reset)' : ''}\\n` +\n ` \u2713 products +${res.productsCreated}, specs +${res.specsCreated}, ` +\n `offerings +${res.offeringsCreated}, charges +${res.chargesCreated} (existing rows left untouched)\\n` +\n ` \u2192 Backend \u00B7 CPQ \u00B7 Product Specifications / Product Offerings.`,\n )\n },\n}\n\n/**\n * `mercato cpq seed-price-adjustments [--tenant <id>] [--org <id>]`\n *\n * Upgrade step for tenants created before negotiated prices shipped: adds the\n * `cpq.manual_price_adjustments` toggle and the `cpq.price_adjustment_reason`\n * dictionary for every active organization (or one, with `--org` /\n * `--tenant`). Idempotent \u2014 existing labels and toggle values are kept.\n * Pair with `mercato auth sync-role-acls` for the `cpq.quotes.adjust_price` grant.\n */\nconst seedPriceAdjustments: ModuleCli = {\n command: 'seed-price-adjustments',\n async run(rest) {\n const args = parseArgs(rest)\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n const scopes = await resolveAllScopes(em, args)\n if (scopes.length === 0) {\n console.error('[cpq seed-price-adjustments] No matching organization found.')\n return\n }\n let failed = 0\n for (const scope of scopes) {\n try {\n await seedPriceAdjustmentDefaults(em.fork(), scope)\n console.log(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} \u2713`)\n } catch (error) {\n failed++\n console.error(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} failed`, error)\n }\n }\n if (failed) process.exitCode = 1\n },\n}\n\nasync function resolveAllScopes(\n em: EntityManager,\n args: Record<string, string | boolean>,\n): Promise<Array<{ tenantId: string; organizationId: string }>> {\n const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')\n const where: Record<string, unknown> = { isActive: true, deletedAt: null }\n if (typeof args.org === 'string') where.id = args.org\n if (typeof args.tenant === 'string') where.tenant = args.tenant\n const orgs = await em.find(Organization, where as never, { orderBy: { createdAt: 'asc' } as never })\n return orgs.map((org) => ({ organizationId: org.id, tenantId: org.tenant.id }))\n}\n\nexport default [seed, seedPriceAdjustments]\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["org"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveAuditActor } from "../lib/auditActor.js";
|
|
3
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
4
|
+
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
|
|
5
|
+
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
6
|
+
import { cpqPriceAdjustmentInputSchema, cpqPriceAdjustmentPatchSchema } from "../data/price-adjustments.js";
|
|
7
|
+
import { QuotingError } from "../services/cpqQuotingService.js";
|
|
8
|
+
const id = z.string().uuid();
|
|
9
|
+
const createSchema = z.object({ quoteId: id, adjustment: cpqPriceAdjustmentInputSchema }).strict();
|
|
10
|
+
const updateSchema = z.object({ quoteId: id, adjustmentId: id, patch: cpqPriceAdjustmentPatchSchema }).strict();
|
|
11
|
+
const deleteSchema = z.object({ quoteId: id, adjustmentId: id, expectedRevision: z.number().int().min(0).optional() }).strict();
|
|
12
|
+
const snapshotSchema = z.object({
|
|
13
|
+
id,
|
|
14
|
+
quoteConfigurationId: id,
|
|
15
|
+
quoteId: id,
|
|
16
|
+
tenantId: id,
|
|
17
|
+
organizationId: id,
|
|
18
|
+
createdByUserId: id,
|
|
19
|
+
createdAt: z.string().datetime(),
|
|
20
|
+
updatedAt: z.string().datetime(),
|
|
21
|
+
deletedAt: z.string().datetime().nullable(),
|
|
22
|
+
isActive: z.boolean(),
|
|
23
|
+
level: z.enum(["charge", "line"]),
|
|
24
|
+
quoteLineId: id,
|
|
25
|
+
chargeCode: z.string().nullish(),
|
|
26
|
+
chargeTypeFilter: z.enum(["nrc", "mrc"]).nullish(),
|
|
27
|
+
adjustmentType: z.enum(["discount_percent", "discount_absolute", "price_override"]),
|
|
28
|
+
value: z.number(),
|
|
29
|
+
currencyCode: z.string().nullish(),
|
|
30
|
+
reasonCode: z.string(),
|
|
31
|
+
reasonNote: z.string().nullish()
|
|
32
|
+
});
|
|
33
|
+
const undoSchema = z.object({ quoteId: id, before: snapshotSchema.nullable(), after: snapshotSchema });
|
|
34
|
+
async function context(ctx) {
|
|
35
|
+
const subject = ctx.auth?.sub;
|
|
36
|
+
const actor = resolveAuditActor(ctx.auth);
|
|
37
|
+
const tenantId = ctx.organizationScope?.tenantId ?? ctx.auth?.tenantId;
|
|
38
|
+
const organizationId = ctx.selectedOrganizationId;
|
|
39
|
+
if (!subject || !tenantId || !organizationId) throw new QuotingError(403, "An authenticated tenant and organization are required");
|
|
40
|
+
if (!actor) throw new QuotingError(403, "Price adjustments require a user or a user-bound API key");
|
|
41
|
+
const scope = { tenantId, organizationId };
|
|
42
|
+
const rbac = ctx.container.resolve("rbacService");
|
|
43
|
+
if (!await rbac.userHasAllFeatures(subject, ["cpq.quotes.manage", "cpq.quotes.adjust_price"], scope)) throw new QuotingError(403, "Price adjustment permission required");
|
|
44
|
+
return { actor, scope, service: ctx.container.resolve("cpqAdjustmentService") };
|
|
45
|
+
}
|
|
46
|
+
async function buildLog(quoteId, result) {
|
|
47
|
+
const { translate } = await resolveTranslations();
|
|
48
|
+
return {
|
|
49
|
+
actionLabel: translate("cpq.priceAdjustments.audit.adjust", "Adjust quote price"),
|
|
50
|
+
resourceKind: "cpq.quote_price_adjustment",
|
|
51
|
+
resourceId: result.adjustment.id,
|
|
52
|
+
tenantId: result.adjustment.tenantId,
|
|
53
|
+
organizationId: result.adjustment.organizationId,
|
|
54
|
+
snapshotBefore: result.before,
|
|
55
|
+
snapshotAfter: result.adjustment,
|
|
56
|
+
payload: { undo: { quoteId, before: result.before, after: result.adjustment } }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function undo({ logEntry, ctx }) {
|
|
60
|
+
const envelope = undoSchema.safeParse(extractUndoPayload(logEntry));
|
|
61
|
+
if (!envelope.success) throw new QuotingError(422, "Invalid adjustment undo data");
|
|
62
|
+
const { actor, scope, service } = await context(ctx);
|
|
63
|
+
const { quoteId, before, after } = envelope.data;
|
|
64
|
+
await service.undo(quoteId, after.id, before, after, actor, scope);
|
|
65
|
+
}
|
|
66
|
+
const createAdjustmentCommand = {
|
|
67
|
+
id: "cpq.quote_price_adjustment.create",
|
|
68
|
+
isUndoable: true,
|
|
69
|
+
async execute(input, ctx) {
|
|
70
|
+
const parsed = createSchema.parse(input);
|
|
71
|
+
const { actor, scope, service } = await context(ctx);
|
|
72
|
+
return service.create(parsed.quoteId, parsed.adjustment, actor, scope);
|
|
73
|
+
},
|
|
74
|
+
buildLog: ({ input, result }) => buildLog(input.quoteId, result),
|
|
75
|
+
undo
|
|
76
|
+
};
|
|
77
|
+
const updateAdjustmentCommand = {
|
|
78
|
+
id: "cpq.quote_price_adjustment.update",
|
|
79
|
+
isUndoable: true,
|
|
80
|
+
async execute(input, ctx) {
|
|
81
|
+
const parsed = updateSchema.parse(input);
|
|
82
|
+
const { actor, scope, service } = await context(ctx);
|
|
83
|
+
return service.update(parsed.quoteId, parsed.adjustmentId, parsed.patch, actor, scope);
|
|
84
|
+
},
|
|
85
|
+
buildLog: ({ input, result }) => buildLog(input.quoteId, result),
|
|
86
|
+
undo
|
|
87
|
+
};
|
|
88
|
+
const deleteAdjustmentCommand = {
|
|
89
|
+
id: "cpq.quote_price_adjustment.delete",
|
|
90
|
+
isUndoable: true,
|
|
91
|
+
async execute(input, ctx) {
|
|
92
|
+
const parsed = deleteSchema.parse(input);
|
|
93
|
+
const { actor, scope, service } = await context(ctx);
|
|
94
|
+
return service.remove(parsed.quoteId, parsed.adjustmentId, actor, scope, parsed.expectedRevision);
|
|
95
|
+
},
|
|
96
|
+
buildLog: ({ input, result }) => buildLog(input.quoteId, result),
|
|
97
|
+
undo
|
|
98
|
+
};
|
|
99
|
+
registerCommand(createAdjustmentCommand);
|
|
100
|
+
registerCommand(updateAdjustmentCommand);
|
|
101
|
+
registerCommand(deleteAdjustmentCommand);
|
|
102
|
+
export {
|
|
103
|
+
createAdjustmentCommand,
|
|
104
|
+
deleteAdjustmentCommand,
|
|
105
|
+
updateAdjustmentCommand
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=price-adjustments.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/commands/price-adjustments.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { resolveAuditActor } from '../lib/auditActor'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { registerCommand, type CommandHandler, type CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { cpqPriceAdjustmentInputSchema, cpqPriceAdjustmentPatchSchema } from '../data/price-adjustments'\nimport { QuotingError } from '../services/cpqQuotingService'\nimport type { CpqAdjustmentService, AdjustmentWriteResult, AdjustmentSnapshot } from '../services/priceAdjustments/adjustmentService'\n\nconst id = z.string().uuid()\nconst createSchema = z.object({ quoteId: id, adjustment: cpqPriceAdjustmentInputSchema }).strict()\nconst updateSchema = z.object({ quoteId: id, adjustmentId: id, patch: cpqPriceAdjustmentPatchSchema }).strict()\nconst deleteSchema = z.object({ quoteId: id, adjustmentId: id, expectedRevision: z.number().int().min(0).optional() }).strict()\n// Undo data is parsed independently of the original request and never trusted\n// to supply actor/scope. Its target is checked again under the aggregate lock.\nconst snapshotSchema = z.object({\n id, quoteConfigurationId: id, quoteId: id, tenantId: id, organizationId: id,\n createdByUserId: id, createdAt: z.string().datetime(), updatedAt: z.string().datetime(),\n deletedAt: z.string().datetime().nullable(), isActive: z.boolean(),\n level: z.enum(['charge', 'line']), quoteLineId: id, chargeCode: z.string().nullish(),\n chargeTypeFilter: z.enum(['nrc', 'mrc']).nullish(), adjustmentType: z.enum(['discount_percent', 'discount_absolute', 'price_override']),\n value: z.number(), currencyCode: z.string().nullish(), reasonCode: z.string(), reasonNote: z.string().nullish(),\n})\nconst undoSchema = z.object({ quoteId: id, before: snapshotSchema.nullable(), after: snapshotSchema })\n\nasync function context(ctx: CommandRuntimeContext) {\n // RBAC is checked for the authenticated subject (user or API key); the\n // stored author must be a user id (uuid columns).\n const subject = ctx.auth?.sub\n const actor = resolveAuditActor(ctx.auth)\n const tenantId = ctx.organizationScope?.tenantId ?? ctx.auth?.tenantId\n const organizationId = ctx.selectedOrganizationId\n if (!subject || !tenantId || !organizationId) throw new QuotingError(403, 'An authenticated tenant and organization are required')\n if (!actor) throw new QuotingError(403, 'Price adjustments require a user or a user-bound API key')\n const scope = { tenantId, organizationId }\n const rbac = ctx.container.resolve<RbacService>('rbacService')\n if (!(await rbac.userHasAllFeatures(subject, ['cpq.quotes.manage', 'cpq.quotes.adjust_price'], scope))) throw new QuotingError(403, 'Price adjustment permission required')\n return { actor, scope, service: ctx.container.resolve<CpqAdjustmentService>('cpqAdjustmentService') }\n}\n\nasync function buildLog(quoteId: string, result: AdjustmentWriteResult) {\n const { translate } = await resolveTranslations()\n return { actionLabel: translate('cpq.priceAdjustments.audit.adjust', 'Adjust quote price'), resourceKind: 'cpq.quote_price_adjustment', resourceId: result.adjustment.id,\n tenantId: result.adjustment.tenantId, organizationId: result.adjustment.organizationId,\n snapshotBefore: result.before, snapshotAfter: result.adjustment,\n payload: { undo: { quoteId, before: result.before, after: result.adjustment } } }\n}\n\nasync function undo({ logEntry, ctx }: Parameters<NonNullable<CommandHandler['undo']>>[0]) {\n const envelope = undoSchema.safeParse(extractUndoPayload<unknown>(logEntry))\n if (!envelope.success) throw new QuotingError(422, 'Invalid adjustment undo data')\n const { actor, scope, service } = await context(ctx)\n const { quoteId, before, after } = envelope.data\n await service.undo(quoteId, after.id, before as AdjustmentSnapshot | null, after as AdjustmentSnapshot, actor, scope)\n}\n\nexport const createAdjustmentCommand: CommandHandler<z.infer<typeof createSchema>, AdjustmentWriteResult> = {\n id: 'cpq.quote_price_adjustment.create', isUndoable: true,\n async execute(input, ctx) {\n const parsed = createSchema.parse(input)\n const { actor, scope, service } = await context(ctx)\n return service.create(parsed.quoteId, parsed.adjustment, actor, scope)\n },\n buildLog: ({ input, result }) => buildLog(input.quoteId, result),\n undo,\n}\nexport const updateAdjustmentCommand: CommandHandler<z.infer<typeof updateSchema>, AdjustmentWriteResult> = {\n id: 'cpq.quote_price_adjustment.update', isUndoable: true,\n async execute(input, ctx) {\n const parsed = updateSchema.parse(input)\n const { actor, scope, service } = await context(ctx)\n return service.update(parsed.quoteId, parsed.adjustmentId, parsed.patch, actor, scope)\n },\n buildLog: ({ input, result }) => buildLog(input.quoteId, result),\n undo,\n}\nexport const deleteAdjustmentCommand: CommandHandler<z.infer<typeof deleteSchema>, AdjustmentWriteResult> = {\n id: 'cpq.quote_price_adjustment.delete', isUndoable: true,\n async execute(input, ctx) {\n const parsed = deleteSchema.parse(input)\n const { actor, scope, service } = await context(ctx)\n return service.remove(parsed.quoteId, parsed.adjustmentId, actor, scope, parsed.expectedRevision)\n },\n buildLog: ({ input, result }) => buildLog(input.quoteId, result),\n undo,\n}\nregisterCommand(createAdjustmentCommand)\nregisterCommand(updateAdjustmentCommand)\nregisterCommand(deleteAdjustmentCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
5
|
+
import { formatMoney } from "../lib/formatMoney.js";
|
|
6
|
+
const ACTION_VARIANTS = {
|
|
7
|
+
add: "info",
|
|
8
|
+
modify: "warning",
|
|
9
|
+
cancel: "error"
|
|
10
|
+
};
|
|
11
|
+
function LineActionBadge({ action, label, title }) {
|
|
12
|
+
const variant = ACTION_VARIANTS[action] ?? "info";
|
|
13
|
+
return /* @__PURE__ */ jsx(Tag, { variant, className: "px-1.5 text-xs", title, children: label ?? action });
|
|
14
|
+
}
|
|
15
|
+
function ArcLineDiff({ line, currency }) {
|
|
16
|
+
const t = useT();
|
|
17
|
+
const src = line.arcSource;
|
|
18
|
+
if (!src) return null;
|
|
19
|
+
const isCancel = line.action === "cancel";
|
|
20
|
+
const rows = [
|
|
21
|
+
{ label: t("cpq.arc.diff.quantity", "Quantity"), before: src.quantity, after: isCancel ? 0 : line.quantity, asCurrency: false },
|
|
22
|
+
{ label: "MRC", before: src.mrcAmount, after: isCancel ? 0 : line.mrcTotal, asCurrency: true },
|
|
23
|
+
{ label: "NRC", before: src.nrcAmount, after: isCancel ? 0 : line.nrcTotal, asCurrency: true }
|
|
24
|
+
];
|
|
25
|
+
const fmtVal = (value, asCurrency) => asCurrency ? formatMoney(value, currency) : String(value);
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", "data-testid": "cpq-arc-line-diff", children: [
|
|
27
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("cpq.arc.diff.title", "Change Preview") }),
|
|
28
|
+
/* @__PURE__ */ jsx("div", { className: "rounded border bg-background overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
29
|
+
/* @__PURE__ */ jsx("thead", { className: "bg-muted/40", children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
30
|
+
/* @__PURE__ */ jsx("th", { className: "text-left px-3 py-1.5 font-medium" }),
|
|
31
|
+
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: t("cpq.arc.diff.before", "Before") }),
|
|
32
|
+
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: t("cpq.arc.diff.after", "After") }),
|
|
33
|
+
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "\u0394" })
|
|
34
|
+
] }) }),
|
|
35
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((r) => {
|
|
36
|
+
const delta = r.after - r.before;
|
|
37
|
+
const showDelta = delta !== 0;
|
|
38
|
+
return /* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/40", children: [
|
|
39
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-muted-foreground", children: r.label }),
|
|
40
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-right font-mono", children: fmtVal(r.before, r.asCurrency) }),
|
|
41
|
+
/* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono ${isCancel ? "text-muted-foreground line-through" : ""}`, children: fmtVal(r.after, r.asCurrency) }),
|
|
42
|
+
/* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono font-medium ${showDelta ? delta > 0 ? "text-status-success-text" : "text-destructive" : "text-muted-foreground"}`, children: showDelta ? `${delta > 0 ? "+" : ""}${fmtVal(delta, r.asCurrency)}` : "\u2014" })
|
|
43
|
+
] }, r.label);
|
|
44
|
+
}) })
|
|
45
|
+
] }) }),
|
|
46
|
+
isCancel && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive italic", children: t("cpq.arc.diff.cancelNote", "Cancellation \u2014 item will be terminated on activation.") })
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
ArcLineDiff,
|
|
51
|
+
LineActionBadge
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=ArcLineDiff.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/components/ArcLineDiff.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 { formatMoney } from '../lib/formatMoney'\n\nexport type LineAction = 'add' | 'modify' | 'cancel'\n\nconst ACTION_VARIANTS: Record<LineAction, 'info' | 'warning' | 'error'> = {\n add: 'info',\n modify: 'warning',\n cancel: 'error',\n}\n\n/** Coloured add / modify / cancel tag on quote and order lines. */\nexport function LineActionBadge({ action, label, title }: { action: string; label?: string; title?: string }) {\n const variant = ACTION_VARIANTS[action as LineAction] ?? 'info'\n return (\n <Tag variant={variant} className=\"px-1.5 text-xs\" title={title}>\n {label ?? action}\n </Tag>\n )\n}\n\nexport type ArcDiffLine = {\n action: string\n quantity: number\n nrcTotal: number\n mrcTotal: number\n arcSource: { mrcAmount: number; nrcAmount: number; quantity: number } | null\n}\n\n/** Before / after / \u0394 preview for a line that changes an existing subscription item. */\nexport function ArcLineDiff({ line, currency }: { line: ArcDiffLine; currency: string }) {\n const t = useT()\n const src = line.arcSource\n if (!src) return null\n const isCancel = line.action === 'cancel'\n const rows: Array<{ label: string; before: number; after: number; asCurrency: boolean }> = [\n { label: t('cpq.arc.diff.quantity', 'Quantity'), before: src.quantity, after: isCancel ? 0 : line.quantity, asCurrency: false },\n { label: 'MRC', before: src.mrcAmount, after: isCancel ? 0 : line.mrcTotal, asCurrency: true },\n { label: 'NRC', before: src.nrcAmount, after: isCancel ? 0 : line.nrcTotal, asCurrency: true },\n ]\n const fmtVal = (value: number, asCurrency: boolean) => (asCurrency ? formatMoney(value, currency) : String(value))\n\n return (\n <div className=\"space-y-1.5\" data-testid=\"cpq-arc-line-diff\">\n <p className=\"text-xs font-medium text-muted-foreground\">{t('cpq.arc.diff.title', 'Change Preview')}</p>\n <div className=\"rounded border bg-background overflow-hidden\">\n <table className=\"w-full text-xs\">\n <thead className=\"bg-muted/40\">\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left px-3 py-1.5 font-medium\" />\n <th className=\"text-right px-3 py-1.5 font-medium\">{t('cpq.arc.diff.before', 'Before')}</th>\n <th className=\"text-right px-3 py-1.5 font-medium\">{t('cpq.arc.diff.after', 'After')}</th>\n <th className=\"text-right px-3 py-1.5 font-medium\">\u0394</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((r) => {\n const delta = r.after - r.before\n const showDelta = delta !== 0\n return (\n <tr key={r.label} className=\"border-t border-dashed border-border/40\">\n <td className=\"px-3 py-1 text-muted-foreground\">{r.label}</td>\n <td className=\"px-3 py-1 text-right font-mono\">{fmtVal(r.before, r.asCurrency)}</td>\n <td className={`px-3 py-1 text-right font-mono ${isCancel ? 'text-muted-foreground line-through' : ''}`}>\n {fmtVal(r.after, r.asCurrency)}\n </td>\n <td className={`px-3 py-1 text-right font-mono font-medium ${showDelta ? (delta > 0 ? 'text-status-success-text' : 'text-destructive') : 'text-muted-foreground'}`}>\n {showDelta ? `${delta > 0 ? '+' : ''}${fmtVal(delta, r.asCurrency)}` : '\u2014'}\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n {isCancel && (\n <p className=\"text-xs text-destructive italic\">\n {t('cpq.arc.diff.cancelNote', 'Cancellation \u2014 item will be terminated on activation.')}\n </p>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAmBI,cAiCQ,YAjCR;AAhBJ,SAAS,YAAY;AACrB,SAAS,WAAW;AACpB,SAAS,mBAAmB;AAI5B,MAAM,kBAAoE;AAAA,EACxE,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,QAAQ;AACV;AAGO,SAAS,gBAAgB,EAAE,QAAQ,OAAO,MAAM,GAAuD;AAC5G,QAAM,UAAU,gBAAgB,MAAoB,KAAK;AACzD,SACE,oBAAC,OAAI,SAAkB,WAAU,kBAAiB,OAC/C,mBAAS,QACZ;AAEJ;AAWO,SAAS,YAAY,EAAE,MAAM,SAAS,GAA4C;AACvF,QAAM,IAAI,KAAK;AACf,QAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,WAAW,KAAK,WAAW;AACjC,QAAM,OAAqF;AAAA,IACzF,EAAE,OAAO,EAAE,yBAAyB,UAAU,GAAG,QAAQ,IAAI,UAAU,OAAO,WAAW,IAAI,KAAK,UAAU,YAAY,MAAM;AAAA,IAC9H,EAAE,OAAO,OAAO,QAAQ,IAAI,WAAW,OAAO,WAAW,IAAI,KAAK,UAAU,YAAY,KAAK;AAAA,IAC7F,EAAE,OAAO,OAAO,QAAQ,IAAI,WAAW,OAAO,WAAW,IAAI,KAAK,UAAU,YAAY,KAAK;AAAA,EAC/F;AACA,QAAM,SAAS,CAAC,OAAe,eAAyB,aAAa,YAAY,OAAO,QAAQ,IAAI,OAAO,KAAK;AAEhH,SACE,qBAAC,SAAI,WAAU,eAAc,eAAY,qBACvC;AAAA,wBAAC,OAAE,WAAU,6CAA6C,YAAE,sBAAsB,gBAAgB,GAAE;AAAA,IACpG,oBAAC,SAAI,WAAU,gDACb,+BAAC,WAAM,WAAU,kBACf;AAAA,0BAAC,WAAM,WAAU,eACf,+BAAC,QAAG,WAAU,yBACZ;AAAA,4BAAC,QAAG,WAAU,qCAAoC;AAAA,QAClD,oBAAC,QAAG,WAAU,sCAAsC,YAAE,uBAAuB,QAAQ,GAAE;AAAA,QACvF,oBAAC,QAAG,WAAU,sCAAsC,YAAE,sBAAsB,OAAO,GAAE;AAAA,QACrF,oBAAC,QAAG,WAAU,sCAAqC,oBAAC;AAAA,SACtD,GACF;AAAA,MACA,oBAAC,WACE,eAAK,IAAI,CAAC,MAAM;AACf,cAAM,QAAQ,EAAE,QAAQ,EAAE;AAC1B,cAAM,YAAY,UAAU;AAC5B,eACE,qBAAC,QAAiB,WAAU,2CAC1B;AAAA,8BAAC,QAAG,WAAU,mCAAmC,YAAE,OAAM;AAAA,UACzD,oBAAC,QAAG,WAAU,kCAAkC,iBAAO,EAAE,QAAQ,EAAE,UAAU,GAAE;AAAA,UAC/E,oBAAC,QAAG,WAAW,kCAAkC,WAAW,uCAAuC,EAAE,IAClG,iBAAO,EAAE,OAAO,EAAE,UAAU,GAC/B;AAAA,UACA,oBAAC,QAAG,WAAW,8CAA8C,YAAa,QAAQ,IAAI,6BAA6B,qBAAsB,uBAAuB,IAC7J,sBAAY,GAAG,QAAQ,IAAI,MAAM,EAAE,GAAG,OAAO,OAAO,EAAE,UAAU,CAAC,KAAK,UACzE;AAAA,aARO,EAAE,KASX;AAAA,MAEJ,CAAC,GACH;AAAA,OACF,GACF;AAAA,IACC,YACC,oBAAC,OAAE,WAAU,mCACV,YAAE,2BAA2B,4DAAuD,GACvF;AAAA,KAEJ;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
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 { Info } from "lucide-react";
|
|
6
|
+
import { SimpleTooltip } from "@open-mercato/ui/primitives/tooltip";
|
|
7
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
8
|
+
import { DepthBadge, PriceWaterfall } from "./PriceWaterfall.js";
|
|
9
|
+
import { ADJUSTMENT_ERROR_FALLBACKS } from "../lib/priceAdjustmentView.js";
|
|
10
|
+
import { formatMoney } from "../lib/formatMoney.js";
|
|
11
|
+
function PriceColumnHeader({ label, hint }) {
|
|
12
|
+
return /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: /* @__PURE__ */ jsx(SimpleTooltip, { content: hint, size: "sm", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 cursor-help", tabIndex: 0, "aria-label": `${label}: ${hint}`, children: [
|
|
13
|
+
label,
|
|
14
|
+
/* @__PURE__ */ jsx(Info, { className: "h-3 w-3", "aria-hidden": true })
|
|
15
|
+
] }) }) });
|
|
16
|
+
}
|
|
17
|
+
function ChargeBreakdownTable({
|
|
18
|
+
charges,
|
|
19
|
+
currency,
|
|
20
|
+
openDetailIndex,
|
|
21
|
+
onToggleDetail,
|
|
22
|
+
reasonLabels,
|
|
23
|
+
renderChargeActions,
|
|
24
|
+
renderManualActions
|
|
25
|
+
}) {
|
|
26
|
+
const t = useT();
|
|
27
|
+
return /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", "data-testid": "cpq-charge-breakdown", children: [
|
|
28
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
29
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: t("cpq.charges.charge", "Charge") }),
|
|
30
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: t("cpq.charges.type", "Type") }),
|
|
31
|
+
/* @__PURE__ */ jsx(
|
|
32
|
+
PriceColumnHeader,
|
|
33
|
+
{
|
|
34
|
+
label: t("cpq.priceAdjustments.listUnit", "List"),
|
|
35
|
+
hint: t("cpq.priceAdjustments.listUnitHint", "Catalog unit price from the pricing table, before any price rules or manual adjustments.")
|
|
36
|
+
}
|
|
37
|
+
),
|
|
38
|
+
/* @__PURE__ */ jsx(
|
|
39
|
+
PriceColumnHeader,
|
|
40
|
+
{
|
|
41
|
+
label: t("cpq.priceAdjustments.standardUnit", "Standard"),
|
|
42
|
+
hint: t("cpq.priceAdjustments.standardUnitHint", "Unit price after automatic price rules (e.g. volume or segment discounts), before manual adjustments.")
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
/* @__PURE__ */ jsx(
|
|
46
|
+
PriceColumnHeader,
|
|
47
|
+
{
|
|
48
|
+
label: t("cpq.priceAdjustments.offerUnit", "Offer"),
|
|
49
|
+
hint: t("cpq.priceAdjustments.offerUnitHint", "Final unit price quoted to the customer, after price rules and manual adjustments. The badge shows the total change vs list.")
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.charges.qty", "Qty") }),
|
|
53
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.charges.total", "Total") }),
|
|
54
|
+
/* @__PURE__ */ jsx("th", { className: "w-16" })
|
|
55
|
+
] }) }),
|
|
56
|
+
/* @__PURE__ */ jsx("tbody", { children: charges.map((c, i) => {
|
|
57
|
+
const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length);
|
|
58
|
+
const isDetailOpen = openDetailIndex === i;
|
|
59
|
+
const listUnit = c.listUnitPrice ?? c.unitPrice;
|
|
60
|
+
const standardUnit = c.standardUnitPrice ?? c.unitPrice;
|
|
61
|
+
const errors = c.adjustmentErrors ?? [];
|
|
62
|
+
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
63
|
+
/* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/50", children: [
|
|
64
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1", children: [
|
|
65
|
+
/* @__PURE__ */ jsx("span", { children: c.chargeName }),
|
|
66
|
+
errors.length > 0 && /* @__PURE__ */ jsxs(Tag, { variant: "warning", className: "ml-1.5 px-1.5 text-xs", "data-testid": "cpq-adjustment-invalid", children: [
|
|
67
|
+
t("cpq.priceAdjustments.invalid", "Invalid"),
|
|
68
|
+
": ",
|
|
69
|
+
t(`cpq.priceAdjustments.errors.${errors[0].code}`, ADJUSTMENT_ERROR_FALLBACKS[errors[0].code] ?? errors[0].code)
|
|
70
|
+
] })
|
|
71
|
+
] }),
|
|
72
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 uppercase", children: c.chargeType }),
|
|
73
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono text-muted-foreground", children: formatMoney(listUnit, currency) }),
|
|
74
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono text-muted-foreground", children: formatMoney(standardUnit, currency) }),
|
|
75
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
76
|
+
/* @__PURE__ */ jsx(DepthBadge, { before: listUnit, after: c.unitPrice }),
|
|
77
|
+
formatMoney(c.unitPrice, currency)
|
|
78
|
+
] }) }),
|
|
79
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.quantity ?? "\u2014" }),
|
|
80
|
+
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.totalPrice != null ? formatMoney(c.totalPrice, currency) : c.note ?? "\u2014" }),
|
|
81
|
+
/* @__PURE__ */ jsxs("td", { className: "py-1 text-right whitespace-nowrap", children: [
|
|
82
|
+
renderChargeActions?.(c),
|
|
83
|
+
hasDetail && /* @__PURE__ */ jsx(
|
|
84
|
+
"button",
|
|
85
|
+
{
|
|
86
|
+
type: "button",
|
|
87
|
+
onClick: () => onToggleDetail(i),
|
|
88
|
+
className: "inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
|
|
89
|
+
title: t("cpq.priceAdjustments.showBreakdown", "Show price breakdown"),
|
|
90
|
+
children: /* @__PURE__ */ jsx(Info, { className: "h-3.5 w-3.5" })
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
] })
|
|
94
|
+
] }),
|
|
95
|
+
isDetailOpen && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 8, className: "pb-3 pt-1", children: /* @__PURE__ */ jsx(ChargePricingDetail, { charge: c, currency, reasonLabels, renderManualActions }) }) })
|
|
96
|
+
] }, i);
|
|
97
|
+
}) })
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
function ChargePricingDetail({ charge: c, currency, reasonLabels, renderManualActions }) {
|
|
101
|
+
const t = useT();
|
|
102
|
+
const tiers = c.breakdown?.tiers ?? [];
|
|
103
|
+
return /* @__PURE__ */ jsxs("div", { className: "rounded border bg-background p-3 space-y-3 text-xs", children: [
|
|
104
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
105
|
+
/* @__PURE__ */ jsxs("span", { className: "font-semibold text-sm", children: [
|
|
106
|
+
t("cpq.charges.pricingDetails", "Pricing Details"),
|
|
107
|
+
" \u2014 ",
|
|
108
|
+
c.chargeName
|
|
109
|
+
] }),
|
|
110
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: c.pricingMethod ?? "flat" })
|
|
111
|
+
] }),
|
|
112
|
+
tiers.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
113
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: t("cpq.charges.tierBreakdown", "Tier Breakdown") }),
|
|
114
|
+
/* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
115
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
116
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: t("cpq.charges.tier", "Tier") }),
|
|
117
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.range", "Range") }),
|
|
118
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.pricePerUnit", "Price/Unit") }),
|
|
119
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.qty", "Qty") }),
|
|
120
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.charges.subtotal", "Subtotal") })
|
|
121
|
+
] }) }),
|
|
122
|
+
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
123
|
+
tiers.map((tier) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
|
|
124
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5", children: tier.tier }),
|
|
125
|
+
/* @__PURE__ */ jsxs("td", { className: "py-0.5 text-right font-mono", children: [
|
|
126
|
+
tier.from,
|
|
127
|
+
"\u2013",
|
|
128
|
+
tier.to
|
|
129
|
+
] }),
|
|
130
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: formatMoney(tier.pricePerUnit, currency) }),
|
|
131
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: tier.quantity }),
|
|
132
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: formatMoney(tier.subtotal, currency) })
|
|
133
|
+
] }, tier.tier)),
|
|
134
|
+
/* @__PURE__ */ jsxs("tr", { className: "border-t font-medium", children: [
|
|
135
|
+
/* @__PURE__ */ jsx("td", { colSpan: 4, className: "py-0.5 text-right", children: t("cpq.charges.tieredTotal", "Tiered Total") }),
|
|
136
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: formatMoney(tiers.reduce((sum, tier) => sum + tier.subtotal, 0), currency) })
|
|
137
|
+
] })
|
|
138
|
+
] })
|
|
139
|
+
] })
|
|
140
|
+
] }),
|
|
141
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
142
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: t("cpq.priceAdjustments.waterfall.title", "Price waterfall") }),
|
|
143
|
+
/* @__PURE__ */ jsx(PriceWaterfall, { charge: c, currency, reasonLabels, renderManualActions })
|
|
144
|
+
] }),
|
|
145
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-1 border-t text-sm font-semibold", children: [
|
|
146
|
+
/* @__PURE__ */ jsx("span", { children: t("cpq.charges.finalTotal", "Final Total") }),
|
|
147
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: c.totalPrice != null ? formatMoney(c.totalPrice, currency) : "\u2014" })
|
|
148
|
+
] })
|
|
149
|
+
] });
|
|
150
|
+
}
|
|
151
|
+
export {
|
|
152
|
+
ChargeBreakdownTable
|
|
153
|
+
};
|
|
154
|
+
//# sourceMappingURL=ChargeBreakdownTable.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/components/ChargeBreakdownTable.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Info } from 'lucide-react'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { DepthBadge, PriceWaterfall } from './PriceWaterfall'\nimport { ADJUSTMENT_ERROR_FALLBACKS, type WaterfallChargeInput } from '../lib/priceAdjustmentView'\nimport { formatMoney } from '../lib/formatMoney'\n\ntype TierBreakdownEntry = { tier: number; from: number; to: number; pricePerUnit: number; quantity: number; subtotal: number }\n\n/** One priced charge as stored on a quote or order line. */\nexport type ChargeRowView = WaterfallChargeInput & {\n chargeName: string\n quantity?: number | null\n note?: string | null\n breakdown?: { tiers: TierBreakdownEntry[] } | null\n adjustmentErrors?: Array<{ code: string }>\n}\n\n/** Right-aligned price column header with an explanatory hover tooltip. */\nfunction PriceColumnHeader({ label, hint }: { label: string; hint: string }) {\n return (\n <th className=\"text-right py-1 font-medium\">\n <SimpleTooltip content={hint} size=\"sm\">\n <span className=\"inline-flex items-center gap-1 cursor-help\" tabIndex={0} aria-label={`${label}: ${hint}`}>\n {label}\n <Info className=\"h-3 w-3\" aria-hidden />\n </span>\n </SimpleTooltip>\n </th>\n )\n}\n\n/**\n * Charge table shared by the quote and order detail pages: list / standard /\n * offer unit prices with depth badge, and a per-charge pricing detail (tiers +\n * price waterfall). Editing hooks are optional render props.\n */\nexport function ChargeBreakdownTable<C extends ChargeRowView>({\n charges,\n currency,\n openDetailIndex,\n onToggleDetail,\n reasonLabels,\n renderChargeActions,\n renderManualActions,\n}: {\n charges: C[]\n currency: string\n openDetailIndex: number | null\n onToggleDetail: (chargeIndex: number) => void\n reasonLabels?: Record<string, string>\n renderChargeActions?: (charge: C) => React.ReactNode\n renderManualActions?: (adjustmentId: string) => React.ReactNode\n}) {\n const t = useT()\n return (\n <table className=\"w-full text-xs\" data-testid=\"cpq-charge-breakdown\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left py-1 font-medium\">{t('cpq.charges.charge', 'Charge')}</th>\n <th className=\"text-left py-1 font-medium\">{t('cpq.charges.type', 'Type')}</th>\n <PriceColumnHeader\n label={t('cpq.priceAdjustments.listUnit', 'List')}\n hint={t('cpq.priceAdjustments.listUnitHint', 'Catalog unit price from the pricing table, before any price rules or manual adjustments.')}\n />\n <PriceColumnHeader\n label={t('cpq.priceAdjustments.standardUnit', 'Standard')}\n hint={t('cpq.priceAdjustments.standardUnitHint', 'Unit price after automatic price rules (e.g. volume or segment discounts), before manual adjustments.')}\n />\n <PriceColumnHeader\n label={t('cpq.priceAdjustments.offerUnit', 'Offer')}\n hint={t('cpq.priceAdjustments.offerUnitHint', 'Final unit price quoted to the customer, after price rules and manual adjustments. The badge shows the total change vs list.')}\n />\n <th className=\"text-right py-1 font-medium\">{t('cpq.charges.qty', 'Qty')}</th>\n <th className=\"text-right py-1 font-medium\">{t('cpq.charges.total', 'Total')}</th>\n <th className=\"w-16\" />\n </tr>\n </thead>\n <tbody>\n {charges.map((c, i) => {\n const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length)\n const isDetailOpen = openDetailIndex === i\n const listUnit = c.listUnitPrice ?? c.unitPrice\n const standardUnit = c.standardUnitPrice ?? c.unitPrice\n const errors = c.adjustmentErrors ?? []\n return (\n <React.Fragment key={i}>\n <tr className=\"border-t border-dashed border-border/50\">\n <td className=\"py-1\">\n <span>{c.chargeName}</span>\n {errors.length > 0 && (\n <Tag variant=\"warning\" className=\"ml-1.5 px-1.5 text-xs\" data-testid=\"cpq-adjustment-invalid\">\n {t('cpq.priceAdjustments.invalid', 'Invalid')}: {t(`cpq.priceAdjustments.errors.${errors[0].code}`, ADJUSTMENT_ERROR_FALLBACKS[errors[0].code] ?? errors[0].code)}\n </Tag>\n )}\n </td>\n <td className=\"py-1 uppercase\">{c.chargeType}</td>\n <td className=\"py-1 text-right font-mono text-muted-foreground\">{formatMoney(listUnit, currency)}</td>\n <td className=\"py-1 text-right font-mono text-muted-foreground\">{formatMoney(standardUnit, currency)}</td>\n <td className=\"py-1 text-right font-mono\">\n <span className=\"inline-flex items-center gap-1\">\n <DepthBadge before={listUnit} after={c.unitPrice} />\n {formatMoney(c.unitPrice, currency)}\n </span>\n </td>\n <td className=\"py-1 text-right font-mono\">{c.quantity ?? '\u2014'}</td>\n <td className=\"py-1 text-right font-mono\">{c.totalPrice != null ? formatMoney(c.totalPrice, currency) : c.note ?? '\u2014'}</td>\n <td className=\"py-1 text-right whitespace-nowrap\">\n {renderChargeActions?.(c)}\n {hasDetail && (\n <button\n type=\"button\"\n onClick={() => onToggleDetail(i)}\n className=\"inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors\"\n title={t('cpq.priceAdjustments.showBreakdown', 'Show price breakdown')}\n >\n <Info className=\"h-3.5 w-3.5\" />\n </button>\n )}\n </td>\n </tr>\n {isDetailOpen && (\n <tr>\n <td colSpan={8} className=\"pb-3 pt-1\">\n <ChargePricingDetail charge={c} currency={currency} reasonLabels={reasonLabels} renderManualActions={renderManualActions} />\n </td>\n </tr>\n )}\n </React.Fragment>\n )\n })}\n </tbody>\n </table>\n )\n}\n\nfunction ChargePricingDetail({ charge: c, currency, reasonLabels, renderManualActions }: {\n charge: ChargeRowView\n currency: string\n reasonLabels?: Record<string, string>\n renderManualActions?: (adjustmentId: string) => React.ReactNode\n}) {\n const t = useT()\n const tiers = c.breakdown?.tiers ?? []\n return (\n <div className=\"rounded border bg-background p-3 space-y-3 text-xs\">\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-sm\">{t('cpq.charges.pricingDetails', 'Pricing Details')} \u2014 {c.chargeName}</span>\n <span className=\"text-muted-foreground\">{c.pricingMethod ?? 'flat'}</span>\n </div>\n\n {tiers.length > 0 && (\n <div>\n <div className=\"font-medium text-muted-foreground mb-1\">{t('cpq.charges.tierBreakdown', 'Tier Breakdown')}</div>\n <table className=\"w-full\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left py-0.5 font-medium\">{t('cpq.charges.tier', 'Tier')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.range', 'Range')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.pricePerUnit', 'Price/Unit')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.qty', 'Qty')}</th>\n <th className=\"text-right py-0.5 font-medium\">{t('cpq.charges.subtotal', 'Subtotal')}</th>\n </tr>\n </thead>\n <tbody>\n {tiers.map((tier) => (\n <tr key={tier.tier} className=\"border-t border-border/30\">\n <td className=\"py-0.5\">{tier.tier}</td>\n <td className=\"py-0.5 text-right font-mono\">{tier.from}\u2013{tier.to}</td>\n <td className=\"py-0.5 text-right font-mono\">{formatMoney(tier.pricePerUnit, currency)}</td>\n <td className=\"py-0.5 text-right font-mono\">{tier.quantity}</td>\n <td className=\"py-0.5 text-right font-mono\">{formatMoney(tier.subtotal, currency)}</td>\n </tr>\n ))}\n <tr className=\"border-t font-medium\">\n <td colSpan={4} className=\"py-0.5 text-right\">{t('cpq.charges.tieredTotal', 'Tiered Total')}</td>\n <td className=\"py-0.5 text-right font-mono\">\n {formatMoney(tiers.reduce((sum, tier) => sum + tier.subtotal, 0), currency)}\n </td>\n </tr>\n </tbody>\n </table>\n </div>\n )}\n\n <div>\n <div className=\"font-medium text-muted-foreground mb-1\">{t('cpq.priceAdjustments.waterfall.title', 'Price waterfall')}</div>\n <PriceWaterfall charge={c} currency={currency} reasonLabels={reasonLabels} renderManualActions={renderManualActions} />\n </div>\n\n <div className=\"flex items-center justify-between pt-1 border-t text-sm font-semibold\">\n <span>{t('cpq.charges.finalTotal', 'Final Total')}</span>\n <span className=\"font-mono\">{c.totalPrice != null ? formatMoney(c.totalPrice, currency) : '\u2014'}</span>\n </div>\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
5
|
+
import {
|
|
6
|
+
discountDepth,
|
|
7
|
+
formatDepth,
|
|
8
|
+
waterfallRows
|
|
9
|
+
} from "../lib/priceAdjustmentView.js";
|
|
10
|
+
function money(amount, currency) {
|
|
11
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
12
|
+
}
|
|
13
|
+
const TYPE_FALLBACKS = {
|
|
14
|
+
discount_percent: "% discount",
|
|
15
|
+
discount_absolute: "amount off",
|
|
16
|
+
surcharge_percent: "% surcharge",
|
|
17
|
+
surcharge_absolute: "fixed surcharge",
|
|
18
|
+
price_override: "price override"
|
|
19
|
+
};
|
|
20
|
+
function DepthBadge({ before, after, title }) {
|
|
21
|
+
const label = formatDepth(discountDepth(before, after));
|
|
22
|
+
if (!label) return null;
|
|
23
|
+
return /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "px-1.5 text-xs font-mono", title, children: label });
|
|
24
|
+
}
|
|
25
|
+
function PriceWaterfall({ charge, currency, reasonLabels, renderManualActions }) {
|
|
26
|
+
const t = useT();
|
|
27
|
+
const rows = waterfallRows(charge);
|
|
28
|
+
const fmtValue = (ruleType, value) => ruleType.endsWith("_percent") ? `${value}%` : money(value, currency);
|
|
29
|
+
return /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", "data-testid": "cpq-price-waterfall", children: [
|
|
30
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
31
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.step", "Step") }),
|
|
32
|
+
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.source", "Source") }),
|
|
33
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.value", "Value") }),
|
|
34
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.delta", "Delta") }),
|
|
35
|
+
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: t("cpq.priceAdjustments.waterfall.unitPrice", "Unit price") }),
|
|
36
|
+
renderManualActions && /* @__PURE__ */ jsx("th", { className: "w-16" })
|
|
37
|
+
] }) }),
|
|
38
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => {
|
|
39
|
+
if (row.kind === "list" || row.kind === "standard" || row.kind === "offer") {
|
|
40
|
+
const label = row.kind === "list" ? t("cpq.priceAdjustments.waterfall.list", "List price") : row.kind === "standard" ? t("cpq.priceAdjustments.waterfall.standard", "Standard price") : t("cpq.priceAdjustments.waterfall.offer", "Offer price");
|
|
41
|
+
return /* @__PURE__ */ jsxs("tr", { className: `border-t border-border/30 ${row.kind === "offer" ? "font-semibold" : "font-medium"}`, children: [
|
|
42
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5", colSpan: 4, children: label }),
|
|
43
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: money(row.unitPrice, currency) }),
|
|
44
|
+
renderManualActions && /* @__PURE__ */ jsx("td", {})
|
|
45
|
+
] }, `${row.kind}-${index}`);
|
|
46
|
+
}
|
|
47
|
+
const source = row.kind === "rule" ? `${t("cpq.priceAdjustments.waterfall.rule", "Price rule")}: ${row.label}` : `${t("cpq.priceAdjustments.waterfall.manual", "Negotiated")}: ${reasonLabels?.[row.reasonCode ?? ""] ?? row.reasonCode ?? row.label}`;
|
|
48
|
+
return /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
|
|
49
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 pl-3 text-muted-foreground", children: t(`cpq.priceAdjustments.types.${row.ruleType}`, TYPE_FALLBACKS[row.ruleType] ?? row.ruleType) }),
|
|
50
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5", children: source }),
|
|
51
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmtValue(row.ruleType, row.value) }),
|
|
52
|
+
/* @__PURE__ */ jsxs("td", { className: "py-0.5 text-right font-mono", children: [
|
|
53
|
+
row.delta > 0 ? "+" : "",
|
|
54
|
+
money(row.delta, currency)
|
|
55
|
+
] }),
|
|
56
|
+
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: money(row.unitPriceAfter, currency) }),
|
|
57
|
+
renderManualActions && /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right", children: row.kind === "manual" && row.adjustmentId ? renderManualActions(row.adjustmentId) : null })
|
|
58
|
+
] }, `${row.kind}-${index}`);
|
|
59
|
+
}) })
|
|
60
|
+
] });
|
|
61
|
+
}
|
|
62
|
+
export {
|
|
63
|
+
DepthBadge,
|
|
64
|
+
PriceWaterfall
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=PriceWaterfall.js.map
|