@dainamite/cpq 0.5.0 → 0.7.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 +64 -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/AssetsTable.js +3 -9
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +3 -3
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
- package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +174 -184
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
- package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +140 -280
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
- package/dist/modules/cpq/backend/cpq/orders/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/price-rules/PriceRuleForm.js +41 -36
- package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
- package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
- package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +208 -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 +269 -311
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
- package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
- package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
- package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
- package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
- 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 +148 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
- package/dist/modules/cpq/components/PriceWaterfall.js +68 -0
- package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js +112 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
- package/dist/modules/cpq/components/StatusPath.js +5 -3
- package/dist/modules/cpq/components/StatusPath.js.map +2 -2
- package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
- package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
- 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/i18n/de.json +1220 -0
- package/dist/modules/cpq/i18n/en.json +1220 -0
- package/dist/modules/cpq/i18n/es.json +1220 -0
- package/dist/modules/cpq/i18n/pl.json +1220 -0
- 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/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection-table.js +2 -2
- package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
- package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
- package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
- package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
- package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
- package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
- package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/index.js +12 -0
- package/dist/modules/cpq/workflows/steps/index.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/__tests__/i18n/dictionaries.test.ts +62 -0
- package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
- package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -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/AssetsTable.tsx +3 -9
- package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +3 -3
- package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +164 -120
- package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
- package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +122 -308
- package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
- package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
- package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
- package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
- package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
- package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
- package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
- package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
- 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 +304 -348
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
- package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
- package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
- package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
- package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
- package/src/modules/cpq/ce.ts +8 -0
- package/src/modules/cpq/cli.ts +48 -1
- package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
- package/src/modules/cpq/commands/price-adjustments.ts +90 -0
- package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
- package/src/modules/cpq/components/ChargeBreakdownTable.tsx +203 -0
- package/src/modules/cpq/components/PriceWaterfall.tsx +96 -0
- package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
- package/src/modules/cpq/components/StatusPath.tsx +7 -4
- package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
- package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
- 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/i18n/de.json +1220 -0
- package/src/modules/cpq/i18n/en.json +1220 -0
- package/src/modules/cpq/i18n/es.json +1220 -0
- package/src/modules/cpq/i18n/pl.json +1220 -0
- 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/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
- package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
- package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
- package/src/modules/cpq/widgets/injection-table.ts +8 -2
- package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
- package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
- package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
- package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
- package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
- package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
- package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
- package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
- package/src/modules/cpq/workflows/steps/index.ts +12 -0
- package/src/modules/cpq/workflows/types.ts +4 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../../src/modules/cpq/backend/cpq/quotes/%5Bid%5D/_components/ArcQuoteConfigurator.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { NumberInput as BaseNumberInput } from '../../../../../components/NumberInput'\n\n// XD-250 \u2014 ARC configurator drawer.\n//\n// Self-contained right-side drawer: action selection \u2192 target picker \u2192\n// renew/cancel config \u2192 review \u2192 submit. The host quote detail page renders\n// it on demand and refetches when the drawer reports `onChanged`.\n\ntype ArcType = 'amend' | 'renew' | 'cancel'\ntype Step =\n | 'pick-action'\n | 'pick-targets'\n | 'config-renew'\n | 'config-cancel'\n | 'review'\n | 'submitting'\n | 'done'\n\ntype EligibleSub = {\n id: string\n code: string\n name: string\n status: string\n currencyCode: string\n billingCycle: string\n currentTermEnd: string | null\n mrcAmount: number\n}\n\nexport type AttachedTarget = {\n id: string\n subscriptionId: string\n quoteType: string\n mergeAction: string | null\n newTermStart: string | null\n newTermEnd: string | null\n newTermMonths: number | null\n subscription: {\n code: string\n name: string\n currencyCode: string\n billingCycle: string\n mrcAmount: number\n } | null\n}\n\nexport interface ArcQuoteConfiguratorProps {\n quoteId: string\n customerId: string\n customerName: string | null\n currencyCode: string\n initialType: 'new' | ArcType\n initialTargets: AttachedTarget[]\n editable: boolean\n onClose: () => void\n onChanged: () => void | Promise<void>\n}\n\nconst REASON_CODES = [\n { value: 'upgrade', label: 'Upgrade' },\n { value: 'downgrade', label: 'Downgrade' },\n { value: 'config-change', label: 'Configuration change' },\n { value: 'price-adjustment', label: 'Price adjustment' },\n { value: 'term-extension', label: 'Term extension' },\n { value: 'term-reduction', label: 'Term reduction' },\n { value: 'consolidation', label: 'Consolidation' },\n { value: 'customer-request', label: 'Customer request' },\n { value: 'non-payment', label: 'Non-payment' },\n { value: 'contract-breach', label: 'Contract breach' },\n { value: 'other', label: 'Other' },\n] as const\n\nexport default function ArcQuoteConfigurator(\n props: ArcQuoteConfiguratorProps,\n): React.ReactElement {\n const {\n quoteId,\n customerId,\n customerName,\n currencyCode,\n initialType,\n initialTargets,\n editable,\n onClose,\n onChanged,\n } = props\n\n const [step, setStep] = React.useState<Step>(\n initialType === 'new' ? 'pick-action' : 'pick-targets',\n )\n const [type, setType] = React.useState<ArcType | null>(\n initialType === 'new' ? null : (initialType as ArcType),\n )\n const [eligible, setEligible] = React.useState<EligibleSub[]>([])\n const [eligibleLoading, setEligibleLoading] = React.useState(false)\n const [picked, setPicked] = React.useState<Record<string, EligibleSub>>(() => {\n const seed: Record<string, EligibleSub> = {}\n for (const t of initialTargets) {\n if (t.subscription) {\n seed[t.subscriptionId] = {\n id: t.subscriptionId,\n code: t.subscription.code,\n name: t.subscription.name,\n status: 'active',\n currencyCode: t.subscription.currencyCode,\n billingCycle: t.subscription.billingCycle,\n currentTermEnd: null,\n mrcAmount: t.subscription.mrcAmount,\n }\n }\n }\n return seed\n })\n // Renew config (per-target term for standalone, quote-level for merge).\n const [singleTerm, setSingleTerm] = React.useState<{ start: string; end: string; months: string }>(() => ({\n start: initialTargets[0]?.newTermStart?.slice(0, 10) ?? '',\n end: initialTargets[0]?.newTermEnd?.slice(0, 10) ?? '',\n months: initialTargets[0]?.newTermMonths != null ? String(initialTargets[0].newTermMonths) : '',\n }))\n const [mergeTerm, setMergeTerm] = React.useState({\n start: '',\n end: '',\n months: '',\n code: '',\n name: '',\n })\n // Cancel config.\n const [cancelMeta, setCancelMeta] = React.useState({\n reasonCode: '',\n reasonText: '',\n etfAmount: '',\n etfCurrency: currencyCode,\n })\n const [submitError, setSubmitError] = React.useState<string | null>(null)\n\n // ESC closes the drawer.\n React.useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') onClose()\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [onClose])\n\n // Fetch eligible subscriptions when step requires it.\n React.useEffect(() => {\n if (step !== 'pick-targets') return\n setEligibleLoading(true)\n fetch(`/api/cpq/inventory/subscriptions?customerId=${customerId}&pageSize=100`)\n .then((r) => r.json())\n .then((data) => {\n const items: EligibleSub[] = (data.items ?? []).map((s: EligibleSub) => ({\n ...s,\n mrcAmount: Number(s.mrcAmount ?? 0),\n }))\n setEligible(items)\n })\n .catch(() => setEligible([]))\n .finally(() => setEligibleLoading(false))\n }, [step, customerId])\n\n const pickedList = Object.values(picked)\n const isMergeRenew = type === 'renew' && pickedList.length >= 2\n const billingCycles = new Set(pickedList.map((s) => s.billingCycle))\n const cyclesMatch = billingCycles.size <= 1\n const currenciesMatch = pickedList.every((s) => s.currencyCode === currencyCode)\n\n // Validation gates.\n const canProceedFromTargets = pickedList.length > 0 && currenciesMatch\n const canProceedFromRenew =\n isMergeRenew\n ? !!mergeTerm.start && !!mergeTerm.end && mergeTerm.start < mergeTerm.end && cyclesMatch\n : !!singleTerm.start && !!singleTerm.end && singleTerm.start < singleTerm.end\n const canProceedFromCancel = !!cancelMeta.reasonCode\n\n const submit = React.useCallback(async () => {\n setStep('submitting')\n setSubmitError(null)\n try {\n // Step 1 \u2014 set quote type when transitioning from 'new'.\n if (initialType === 'new' && type) {\n const r = await fetch(`/api/cpq/quotes/${quoteId}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ quoteType: type }),\n })\n if (!r.ok) {\n const body = await r.json().catch(() => ({}))\n throw new Error(body.error ?? `PATCH /quotes/${quoteId} failed`)\n }\n }\n\n // Step 2 \u2014 detach removed targets.\n const initialTargetIds = new Set(initialTargets.map((t) => t.subscriptionId))\n const pickedIds = new Set(pickedList.map((s) => s.id))\n for (const t of initialTargets) {\n if (!pickedIds.has(t.subscriptionId)) {\n await fetch(`/api/cpq/quotes/${quoteId}/target-subscriptions/${t.id}`, { method: 'DELETE' })\n }\n }\n\n // Step 3 \u2014 attach new targets.\n for (const s of pickedList) {\n if (!initialTargetIds.has(s.id) && type) {\n const body: Record<string, unknown> = {\n subscriptionId: s.id,\n quoteType: type,\n }\n if (type === 'renew' && !isMergeRenew) {\n body.mergeAction = 'standalone'\n body.newTermStart = singleTerm.start\n body.newTermEnd = singleTerm.end\n if (singleTerm.months) body.newTermMonths = Number(singleTerm.months)\n } else if (type === 'renew' && isMergeRenew) {\n body.mergeAction = 'absorb'\n }\n const r = await fetch(`/api/cpq/quotes/${quoteId}/target-subscriptions`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!r.ok) {\n const errBody = await r.json().catch(() => ({}))\n throw new Error(errBody.error ?? `POST /target-subscriptions failed`)\n }\n }\n }\n\n // Step 4 \u2014 for merge renew, stamp quote-level merge meta.\n if (type === 'renew' && isMergeRenew) {\n const body: Record<string, unknown> = {\n newTermStart: mergeTerm.start,\n newTermEnd: mergeTerm.end,\n }\n if (mergeTerm.months) body.newTermMonths = Number(mergeTerm.months)\n if (mergeTerm.code) body.newSubCode = mergeTerm.code\n if (mergeTerm.name) body.newSubName = mergeTerm.name\n const r = await fetch(`/api/cpq/quotes/${quoteId}/merge-meta`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!r.ok) {\n const errBody = await r.json().catch(() => ({}))\n throw new Error(errBody.error ?? `POST /merge-meta failed`)\n }\n }\n\n // Step 5 \u2014 for cancel, stamp meta.\n if (type === 'cancel') {\n const body: Record<string, unknown> = { reasonCode: cancelMeta.reasonCode }\n if (cancelMeta.reasonText) body.reasonText = cancelMeta.reasonText\n if (cancelMeta.etfAmount) body.etfAmount = cancelMeta.etfAmount\n if (cancelMeta.etfCurrency) body.etfCurrency = cancelMeta.etfCurrency\n const r = await fetch(`/api/cpq/quotes/${quoteId}/cancel-meta`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!r.ok) {\n const errBody = await r.json().catch(() => ({}))\n throw new Error(errBody.error ?? `POST /cancel-meta failed`)\n }\n }\n\n await onChanged()\n setStep('done')\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : String(err))\n setStep('review')\n }\n }, [\n quoteId,\n type,\n initialType,\n initialTargets,\n pickedList,\n isMergeRenew,\n singleTerm,\n mergeTerm,\n cancelMeta,\n onChanged,\n ])\n\n return (\n <div className=\"fixed inset-0 z-50 flex\" role=\"dialog\" aria-modal=\"true\">\n {/* Overlay */}\n <button\n aria-label=\"Close drawer\"\n className=\"absolute inset-0 bg-black/40\"\n onClick={onClose}\n />\n {/* Drawer */}\n <div className=\"ml-auto h-full w-full max-w-2xl bg-card shadow-xl border-l overflow-y-auto relative flex flex-col\">\n <header className=\"border-b px-5 py-4 flex items-start justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">Modify subscription</h2>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {customerName ?? customerId.slice(0, 12) + '\u2026'} \u2022 {currencyCode}\n </p>\n </div>\n <button\n onClick={onClose}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\n >\n \u2715\n </button>\n </header>\n\n <div className=\"flex-1 px-5 py-4 space-y-4\">\n {/* Step indicator */}\n <p className=\"text-xs text-muted-foreground\">\n Step: <span className=\"font-mono\">{step}</span>\n {type && <span> \u2022 {type}</span>}\n {pickedList.length > 0 && <span> \u2022 {pickedList.length} target(s)</span>}\n </p>\n\n {step === 'pick-action' && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">Pick the kind of change you want to make:</p>\n {(['amend', 'renew', 'cancel'] as const).map((opt) => (\n <button\n key={opt}\n onClick={() => {\n setType(opt)\n setStep('pick-targets')\n }}\n className=\"block w-full text-left rounded-md border border-input px-4 py-3 hover:bg-muted\"\n >\n <span className=\"font-medium capitalize\">{opt}</span>\n <span className=\"block text-xs text-muted-foreground mt-0.5\">\n {opt === 'amend' && 'Add or remove items on existing subscriptions. Term untouched.'}\n {opt === 'renew' && 'Extend term. With 2+ targets, merges them into a new contract.'}\n {opt === 'cancel' && 'Terminate one or more subscriptions.'}\n </span>\n </button>\n ))}\n </div>\n )}\n\n {step === 'pick-targets' && type && (\n <div className=\"space-y-3\">\n {eligibleLoading ? (\n <div className=\"text-sm text-muted-foreground\">Loading subscriptions\u2026</div>\n ) : eligible.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">No subscriptions for this customer.</div>\n ) : (\n <div className=\"space-y-1\">\n {eligible.map((s) => {\n const currencyMismatch = s.currencyCode !== currencyCode\n const isPicked = !!picked[s.id]\n return (\n <label\n key={s.id}\n className={`flex items-start gap-2 rounded-md border px-3 py-2 ${\n currencyMismatch\n ? 'opacity-50 border-dashed'\n : isPicked\n ? 'border-primary bg-primary/5'\n : 'border-input hover:bg-muted'\n }`}\n >\n <input\n type=\"checkbox\"\n disabled={currencyMismatch}\n checked={isPicked}\n onChange={(e) => {\n setPicked((prev) => {\n const next = { ...prev }\n if (e.target.checked) next[s.id] = s\n else delete next[s.id]\n return next\n })\n }}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium\">\n {s.name}{' '}\n <span className=\"text-xs font-mono text-muted-foreground\">{s.code}</span>\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {s.status} \u2022 {s.billingCycle} \u2022 MRC {s.mrcAmount.toFixed(2)} {s.currencyCode}\n {s.currentTermEnd\n ? ` \u2022 Term ends ${new Date(s.currentTermEnd).toLocaleDateString()}`\n : ''}\n </div>\n {currencyMismatch && (\n <div className=\"text-xs text-status-warning-text mt-1\">\n Currency mismatch ({s.currencyCode} vs {currencyCode}) \u2014 cannot include\n </div>\n )}\n </div>\n </label>\n )\n })}\n </div>\n )}\n {!cyclesMatch && (\n <Alert variant=\"warning\" className=\"text-xs\">\n Picked targets have different billing cycles \u2014 merge requires matching cycles.\n </Alert>\n )}\n {isMergeRenew && (\n <Alert variant=\"warning\" className=\"text-xs\">\n Merging {pickedList.length} contracts into a new one \u2014 sources will be marked Merged at activation.\n </Alert>\n )}\n </div>\n )}\n\n {step === 'config-renew' && type === 'renew' && !isMergeRenew && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">New contract term for {pickedList[0]?.name}:</p>\n <p className=\"text-xs text-muted-foreground\">\n Pre-filled with same length as the current term, starting at the\n current term end. Type a new value in <span className=\"font-medium\">Months</span> to\n auto-shift the end date, or edit start/end directly.\n </p>\n <div className=\"grid grid-cols-3 gap-3\">\n <DateInput\n label=\"Start\"\n value={singleTerm.start}\n onChange={(v) => {\n // If months is set, slide end too so the contract length stays.\n const months = singleTerm.months ? Number(singleTerm.months) : null\n const end = months && v ? addMonthsToIso(v, months) : singleTerm.end\n setSingleTerm({ ...singleTerm, start: v, end })\n }}\n />\n <DateInput\n label=\"End\"\n value={singleTerm.end}\n onChange={(v) => setSingleTerm({ ...singleTerm, end: v })}\n />\n <NumberInput\n label=\"Months\"\n value={singleTerm.months}\n onChange={(v) => {\n // Recompute end = start + months when both are present.\n const months = v ? Number(v) : null\n const end = months && singleTerm.start\n ? addMonthsToIso(singleTerm.start, months)\n : singleTerm.end\n setSingleTerm({ ...singleTerm, months: v, end })\n }}\n />\n </div>\n </div>\n )}\n\n {step === 'config-renew' && type === 'renew' && isMergeRenew && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">\n New contract details (merge of {pickedList.length} subscriptions):\n </p>\n <p className=\"text-xs text-muted-foreground\">\n Type a value into <span className=\"font-medium\">Months</span> to\n auto-fill both dates \u2014 start defaults to today, end is start + months.\n </p>\n <div className=\"grid grid-cols-3 gap-3\">\n <DateInput\n label=\"Start\"\n value={mergeTerm.start}\n onChange={(v) => {\n const months = mergeTerm.months ? Number(mergeTerm.months) : null\n const end = months && v ? addMonthsToIso(v, months) : mergeTerm.end\n setMergeTerm({ ...mergeTerm, start: v, end })\n }}\n />\n <DateInput\n label=\"End\"\n value={mergeTerm.end}\n onChange={(v) => setMergeTerm({ ...mergeTerm, end: v })}\n />\n <NumberInput\n label=\"Months\"\n value={mergeTerm.months}\n onChange={(v) => {\n const months = v ? Number(v) : null\n const start = mergeTerm.start || todayIso()\n const end = months ? addMonthsToIso(start, months) : mergeTerm.end\n setMergeTerm({ ...mergeTerm, months: v, start, end })\n }}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <TextInput\n label=\"New contract code (optional \u2014 auto-generated if blank)\"\n value={mergeTerm.code}\n onChange={(v) => setMergeTerm({ ...mergeTerm, code: v })}\n />\n <TextInput\n label=\"New contract name (optional)\"\n value={mergeTerm.name}\n onChange={(v) => setMergeTerm({ ...mergeTerm, name: v })}\n />\n </div>\n </div>\n )}\n\n {step === 'config-cancel' && type === 'cancel' && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">Reason for cancellation:</p>\n <div className=\"space-y-2\">\n <label className=\"block text-xs text-muted-foreground\">Reason code</label>\n <select\n value={cancelMeta.reasonCode}\n onChange={(e) => setCancelMeta({ ...cancelMeta, reasonCode: e.target.value })}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n >\n <option value=\"\">\u2014 pick one \u2014</option>\n {REASON_CODES.map((r) => (\n <option key={r.value} value={r.value}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n <TextInput\n label=\"Free text (optional)\"\n value={cancelMeta.reasonText}\n onChange={(v) => setCancelMeta({ ...cancelMeta, reasonText: v })}\n />\n <div className=\"grid grid-cols-2 gap-3\">\n <TextInput\n label=\"ETF amount (optional)\"\n value={cancelMeta.etfAmount}\n onChange={(v) => setCancelMeta({ ...cancelMeta, etfAmount: v })}\n />\n <TextInput\n label=\"ETF currency\"\n value={cancelMeta.etfCurrency}\n onChange={(v) => setCancelMeta({ ...cancelMeta, etfCurrency: v })}\n />\n </div>\n </div>\n )}\n\n {step === 'review' && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">Review and submit:</p>\n <ul className=\"text-sm space-y-1 list-disc pl-5\">\n <li>Action: {type}</li>\n <li>Targets: {pickedList.length}</li>\n {pickedList.map((s) => (\n <li key={s.id} className=\"text-muted-foreground\">\n {s.code} \u2014 {s.name}\n </li>\n ))}\n {type === 'renew' && !isMergeRenew && (\n <li>\n Term: {singleTerm.start} \u2192 {singleTerm.end}\n {singleTerm.months ? ` (${singleTerm.months}m)` : ''}\n </li>\n )}\n {type === 'renew' && isMergeRenew && (\n <li>\n Merge term: {mergeTerm.start} \u2192 {mergeTerm.end}\n {mergeTerm.code ? ` \u2022 code=${mergeTerm.code}` : ''}\n {mergeTerm.name ? ` \u2022 name=${mergeTerm.name}` : ''}\n </li>\n )}\n {type === 'cancel' && (\n <li>\n Reason: {cancelMeta.reasonCode}\n {cancelMeta.etfAmount\n ? ` \u2022 ETF ${cancelMeta.etfAmount} ${cancelMeta.etfCurrency}`\n : ''}\n </li>\n )}\n </ul>\n <Alert variant=\"warning\" className=\"text-xs\">\n Submitting locks the quote type. Lines will be edited on the host page after this completes.\n </Alert>\n {submitError && (\n <Alert variant=\"destructive\" className=\"text-xs\">{submitError}</Alert>\n )}\n </div>\n )}\n\n {step === 'submitting' && (\n <div className=\"text-sm text-muted-foreground\">Submitting\u2026</div>\n )}\n\n {step === 'done' && (\n <div className=\"text-sm text-status-success-text\">\n Done \u2014 close to return to the quote.\n </div>\n )}\n </div>\n\n <footer className=\"border-t px-5 pt-3 pb-20 flex items-center justify-between\">\n <button\n onClick={() => {\n if (step === 'pick-action') return\n if (step === 'pick-targets') {\n setStep(initialType === 'new' ? 'pick-action' : 'pick-targets')\n } else if (step === 'config-renew' || step === 'config-cancel') {\n setStep('pick-targets')\n } else if (step === 'review') {\n if (type === 'renew') setStep('config-renew')\n else if (type === 'cancel') setStep('config-cancel')\n else setStep('pick-targets')\n }\n }}\n disabled={\n step === 'pick-action' ||\n step === 'submitting' ||\n step === 'done' ||\n (step === 'pick-targets' && initialType !== 'new')\n }\n className=\"text-sm text-muted-foreground hover:text-foreground disabled:opacity-30\"\n >\n \u2190 Back\n </button>\n <div className=\"flex gap-2\">\n <button\n onClick={onClose}\n className=\"rounded-md border border-input px-3 py-1.5 text-sm hover:bg-muted\"\n >\n Cancel\n </button>\n {step === 'pick-targets' && (\n <button\n disabled={!canProceedFromTargets || !editable}\n onClick={() => {\n if (type === 'renew') setStep('config-renew')\n else if (type === 'cancel') setStep('config-cancel')\n else setStep('review')\n }}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50\"\n >\n Next\n </button>\n )}\n {(step === 'config-renew' || step === 'config-cancel') && (\n <button\n disabled={\n (step === 'config-renew' && !canProceedFromRenew) ||\n (step === 'config-cancel' && !canProceedFromCancel)\n }\n onClick={() => setStep('review')}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50\"\n >\n Next\n </button>\n )}\n {step === 'review' && (\n <button\n disabled={!editable}\n onClick={submit}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50\"\n >\n Submit\n </button>\n )}\n {step === 'done' && (\n <button\n onClick={onClose}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90\"\n >\n Close\n </button>\n )}\n </div>\n </footer>\n </div>\n </div>\n )\n}\n\nfunction DateInput(props: { label: string; value: string; onChange: (v: string) => void }) {\n return (\n <div>\n <label className=\"block text-xs text-muted-foreground mb-1\">{props.label}</label>\n <input\n type=\"date\"\n value={props.value}\n onChange={(e) => props.onChange(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n />\n </div>\n )\n}\n\n// Local \"labelled number\" wrapper kept as a string-in/string-out facade so\n// the surrounding code (which threads `value` through `mergeTerm.months` as\n// a string) doesn't have to change. The actual input behaviour comes from\n// the shared `NumberInput` component (select-on-focus + internal string\n// state \u2014 see components/NumberInput.tsx for the rationale).\nfunction NumberInput(props: { label: string; value: string; onChange: (v: string) => void }) {\n return (\n <div>\n <label className=\"block text-xs text-muted-foreground mb-1\">{props.label}</label>\n <BaseNumberInput\n integer\n min={1}\n value={props.value === '' ? null : Number(props.value)}\n onChange={(n) => props.onChange(n == null ? '' : String(n))}\n />\n </div>\n )\n}\n\nfunction TextInput(props: { label: string; value: string; onChange: (v: string) => void }) {\n return (\n <div>\n <label className=\"block text-xs text-muted-foreground mb-1\">{props.label}</label>\n <input\n type=\"text\"\n value={props.value}\n onChange={(e) => props.onChange(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n />\n </div>\n )\n}\n\n// Add `months` whole calendar months to an ISO date string (YYYY-MM-DD) and\n// return the result in the same format. Used by the renew config step so the\n// operator can edit just the months field and have the end date auto-update.\nfunction addMonthsToIso(iso: string, months: number): string {\n if (!iso) return iso\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return iso\n d.setMonth(d.getMonth() + months)\n return d.toISOString().split('T')[0]\n}\n\n// Today as YYYY-MM-DD \u2014 used as the default `Start` when the operator types\n// a `Months` value first on a brand-new merge contract.\nfunction todayIso(): string {\n return new Date().toISOString().split('T')[0]\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { NumberInput as BaseNumberInput } from '../../../../../components/NumberInput'\n\n// XD-250 \u2014 ARC configurator drawer.\n//\n// Self-contained right-side drawer: action selection \u2192 target picker \u2192\n// renew/cancel config \u2192 review \u2192 submit. The host quote detail page renders\n// it on demand and refetches when the drawer reports `onChanged`.\n\ntype ArcType = 'amend' | 'renew' | 'cancel'\ntype Step =\n | 'pick-action'\n | 'pick-targets'\n | 'config-renew'\n | 'config-cancel'\n | 'review'\n | 'submitting'\n | 'done'\n\ntype EligibleSub = {\n id: string\n code: string\n name: string\n status: string\n currencyCode: string\n billingCycle: string\n currentTermEnd: string | null\n mrcAmount: number\n}\n\nexport type AttachedTarget = {\n id: string\n subscriptionId: string\n quoteType: string\n mergeAction: string | null\n newTermStart: string | null\n newTermEnd: string | null\n newTermMonths: number | null\n subscription: {\n code: string\n name: string\n currencyCode: string\n billingCycle: string\n mrcAmount: number\n } | null\n}\n\nexport interface ArcQuoteConfiguratorProps {\n quoteId: string\n customerId: string\n customerName: string | null\n currencyCode: string\n initialType: 'new' | ArcType\n initialTargets: AttachedTarget[]\n editable: boolean\n onClose: () => void\n onChanged: () => void | Promise<void>\n}\n\nfunction buildReasonCodes(t: TranslateFn): Array<{ value: string; label: string }> {\n return [\n { value: 'upgrade', label: t('cpq.quotes.arc.drawer.reasons.upgrade', 'Upgrade') },\n { value: 'downgrade', label: t('cpq.quotes.arc.drawer.reasons.downgrade', 'Downgrade') },\n { value: 'config-change', label: t('cpq.quotes.arc.drawer.reasons.configChange', 'Configuration change') },\n { value: 'price-adjustment', label: t('cpq.quotes.arc.drawer.reasons.priceAdjustment', 'Price adjustment') },\n { value: 'term-extension', label: t('cpq.quotes.arc.drawer.reasons.termExtension', 'Term extension') },\n { value: 'term-reduction', label: t('cpq.quotes.arc.drawer.reasons.termReduction', 'Term reduction') },\n { value: 'consolidation', label: t('cpq.quotes.arc.drawer.reasons.consolidation', 'Consolidation') },\n { value: 'customer-request', label: t('cpq.quotes.arc.drawer.reasons.customerRequest', 'Customer request') },\n { value: 'non-payment', label: t('cpq.quotes.arc.drawer.reasons.nonPayment', 'Non-payment') },\n { value: 'contract-breach', label: t('cpq.quotes.arc.drawer.reasons.contractBreach', 'Contract breach') },\n { value: 'other', label: t('cpq.quotes.arc.drawer.reasons.other', 'Other') },\n ]\n}\n\nfunction arcTypeLabel(t: TranslateFn, type: ArcType): string {\n switch (type) {\n case 'amend':\n return t('cpq.quotes.type.amend', 'amend')\n case 'renew':\n return t('cpq.quotes.type.renew', 'renew')\n case 'cancel':\n return t('cpq.quotes.type.cancel', 'cancel')\n }\n}\n\nexport default function ArcQuoteConfigurator(\n props: ArcQuoteConfiguratorProps,\n): React.ReactElement {\n const {\n quoteId,\n customerId,\n customerName,\n currencyCode,\n initialType,\n initialTargets,\n editable,\n onClose,\n onChanged,\n } = props\n\n const t = useT()\n const reasonCodes = React.useMemo(() => buildReasonCodes(t), [t])\n const [step, setStep] = React.useState<Step>(\n initialType === 'new' ? 'pick-action' : 'pick-targets',\n )\n const [type, setType] = React.useState<ArcType | null>(\n initialType === 'new' ? null : (initialType as ArcType),\n )\n const [eligible, setEligible] = React.useState<EligibleSub[]>([])\n const [eligibleLoading, setEligibleLoading] = React.useState(false)\n const [picked, setPicked] = React.useState<Record<string, EligibleSub>>(() => {\n const seed: Record<string, EligibleSub> = {}\n for (const target of initialTargets) {\n if (target.subscription) {\n seed[target.subscriptionId] = {\n id: target.subscriptionId,\n code: target.subscription.code,\n name: target.subscription.name,\n status: 'active',\n currencyCode: target.subscription.currencyCode,\n billingCycle: target.subscription.billingCycle,\n currentTermEnd: null,\n mrcAmount: target.subscription.mrcAmount,\n }\n }\n }\n return seed\n })\n // Renew config (per-target term for standalone, quote-level for merge).\n const [singleTerm, setSingleTerm] = React.useState<{ start: string; end: string; months: string }>(() => ({\n start: initialTargets[0]?.newTermStart?.slice(0, 10) ?? '',\n end: initialTargets[0]?.newTermEnd?.slice(0, 10) ?? '',\n months: initialTargets[0]?.newTermMonths != null ? String(initialTargets[0].newTermMonths) : '',\n }))\n const [mergeTerm, setMergeTerm] = React.useState({\n start: '',\n end: '',\n months: '',\n code: '',\n name: '',\n })\n // Cancel config.\n const [cancelMeta, setCancelMeta] = React.useState({\n reasonCode: '',\n reasonText: '',\n etfAmount: '',\n etfCurrency: currencyCode,\n })\n const [submitError, setSubmitError] = React.useState<string | null>(null)\n\n // ESC closes the drawer.\n React.useEffect(() => {\n function onKey(e: KeyboardEvent) {\n if (e.key === 'Escape') onClose()\n }\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [onClose])\n\n // Fetch eligible subscriptions when step requires it.\n React.useEffect(() => {\n if (step !== 'pick-targets') return\n setEligibleLoading(true)\n fetch(`/api/cpq/inventory/subscriptions?customerId=${customerId}&pageSize=100`)\n .then((r) => r.json())\n .then((data) => {\n const items: EligibleSub[] = (data.items ?? []).map((s: EligibleSub) => ({\n ...s,\n mrcAmount: Number(s.mrcAmount ?? 0),\n }))\n setEligible(items)\n })\n .catch(() => setEligible([]))\n .finally(() => setEligibleLoading(false))\n }, [step, customerId])\n\n const pickedList = Object.values(picked)\n const isMergeRenew = type === 'renew' && pickedList.length >= 2\n const billingCycles = new Set(pickedList.map((s) => s.billingCycle))\n const cyclesMatch = billingCycles.size <= 1\n const currenciesMatch = pickedList.every((s) => s.currencyCode === currencyCode)\n\n // Validation gates.\n const canProceedFromTargets = pickedList.length > 0 && currenciesMatch\n const canProceedFromRenew =\n isMergeRenew\n ? !!mergeTerm.start && !!mergeTerm.end && mergeTerm.start < mergeTerm.end && cyclesMatch\n : !!singleTerm.start && !!singleTerm.end && singleTerm.start < singleTerm.end\n const canProceedFromCancel = !!cancelMeta.reasonCode\n\n const submit = React.useCallback(async () => {\n setStep('submitting')\n setSubmitError(null)\n try {\n // Step 1 \u2014 set quote type when transitioning from 'new'.\n if (initialType === 'new' && type) {\n const r = await fetch(`/api/cpq/quotes/${quoteId}`, {\n method: 'PATCH',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ quoteType: type }),\n })\n if (!r.ok) {\n const body = await r.json().catch(() => ({}))\n throw new Error(body.error ?? t('cpq.quotes.arc.drawer.errors.patchFailed', 'PATCH /quotes/{quoteId} failed', { quoteId }))\n }\n }\n\n // Step 2 \u2014 detach removed targets.\n const initialTargetIds = new Set(initialTargets.map((target) => target.subscriptionId))\n const pickedIds = new Set(pickedList.map((s) => s.id))\n for (const target of initialTargets) {\n if (!pickedIds.has(target.subscriptionId)) {\n await fetch(`/api/cpq/quotes/${quoteId}/target-subscriptions/${target.id}`, { method: 'DELETE' })\n }\n }\n\n // Step 3 \u2014 attach new targets.\n for (const s of pickedList) {\n if (!initialTargetIds.has(s.id) && type) {\n const body: Record<string, unknown> = {\n subscriptionId: s.id,\n quoteType: type,\n }\n if (type === 'renew' && !isMergeRenew) {\n body.mergeAction = 'standalone'\n body.newTermStart = singleTerm.start\n body.newTermEnd = singleTerm.end\n if (singleTerm.months) body.newTermMonths = Number(singleTerm.months)\n } else if (type === 'renew' && isMergeRenew) {\n body.mergeAction = 'absorb'\n }\n const r = await fetch(`/api/cpq/quotes/${quoteId}/target-subscriptions`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!r.ok) {\n const errBody = await r.json().catch(() => ({}))\n throw new Error(errBody.error ?? t('cpq.quotes.arc.drawer.errors.attachFailed', 'POST /target-subscriptions failed'))\n }\n }\n }\n\n // Step 4 \u2014 for merge renew, stamp quote-level merge meta.\n if (type === 'renew' && isMergeRenew) {\n const body: Record<string, unknown> = {\n newTermStart: mergeTerm.start,\n newTermEnd: mergeTerm.end,\n }\n if (mergeTerm.months) body.newTermMonths = Number(mergeTerm.months)\n if (mergeTerm.code) body.newSubCode = mergeTerm.code\n if (mergeTerm.name) body.newSubName = mergeTerm.name\n const r = await fetch(`/api/cpq/quotes/${quoteId}/merge-meta`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!r.ok) {\n const errBody = await r.json().catch(() => ({}))\n throw new Error(errBody.error ?? t('cpq.quotes.arc.drawer.errors.mergeMetaFailed', 'POST /merge-meta failed'))\n }\n }\n\n // Step 5 \u2014 for cancel, stamp meta.\n if (type === 'cancel') {\n const body: Record<string, unknown> = { reasonCode: cancelMeta.reasonCode }\n if (cancelMeta.reasonText) body.reasonText = cancelMeta.reasonText\n if (cancelMeta.etfAmount) body.etfAmount = cancelMeta.etfAmount\n if (cancelMeta.etfCurrency) body.etfCurrency = cancelMeta.etfCurrency\n const r = await fetch(`/api/cpq/quotes/${quoteId}/cancel-meta`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(body),\n })\n if (!r.ok) {\n const errBody = await r.json().catch(() => ({}))\n throw new Error(errBody.error ?? t('cpq.quotes.arc.drawer.errors.cancelMetaFailed', 'POST /cancel-meta failed'))\n }\n }\n\n await onChanged()\n setStep('done')\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : String(err))\n setStep('review')\n }\n }, [\n quoteId,\n type,\n initialType,\n initialTargets,\n pickedList,\n isMergeRenew,\n singleTerm,\n mergeTerm,\n cancelMeta,\n onChanged,\n t,\n ])\n\n return (\n <div className=\"fixed inset-0 z-50 flex\" role=\"dialog\" aria-modal=\"true\">\n {/* Overlay */}\n <button\n aria-label={t('cpq.quotes.arc.drawer.close', 'Close drawer')}\n className=\"absolute inset-0 bg-black/40\"\n onClick={onClose}\n />\n {/* Drawer */}\n <div className=\"ml-auto h-full w-full max-w-2xl bg-card shadow-xl border-l overflow-y-auto relative flex flex-col\">\n <header className=\"border-b px-5 py-4 flex items-start justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">{t('cpq.quotes.arc.drawer.title', 'Modify subscription')}</h2>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {customerName ?? customerId.slice(0, 12) + '\u2026'} \u2022 {currencyCode}\n </p>\n </div>\n <button\n onClick={onClose}\n className=\"text-sm text-muted-foreground hover:text-foreground\"\n >\n \u2715\n </button>\n </header>\n\n <div className=\"flex-1 px-5 py-4 space-y-4\">\n {/* Step indicator */}\n <p className=\"text-xs text-muted-foreground\">\n {t('cpq.quotes.arc.drawer.stepLabel', 'Step:')} <span className=\"font-mono\">{step}</span>\n {type && <span> \u2022 {arcTypeLabel(t, type)}</span>}\n {pickedList.length > 0 && <span> \u2022 {t('cpq.quotes.arc.drawer.targetCount', '{count} target(s)', { count: pickedList.length })}</span>}\n </p>\n\n {step === 'pick-action' && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">{t('cpq.quotes.arc.drawer.pickAction', 'Pick the kind of change you want to make:')}</p>\n {(['amend', 'renew', 'cancel'] as const).map((opt) => (\n <button\n key={opt}\n onClick={() => {\n setType(opt)\n setStep('pick-targets')\n }}\n className=\"block w-full text-left rounded-md border border-input px-4 py-3 hover:bg-muted\"\n >\n <span className=\"font-medium capitalize\">{arcTypeLabel(t, opt)}</span>\n <span className=\"block text-xs text-muted-foreground mt-0.5\">\n {opt === 'amend' && t('cpq.quotes.arc.drawer.actions.amendHint', 'Add or remove items on existing subscriptions. Term untouched.')}\n {opt === 'renew' && t('cpq.quotes.arc.drawer.actions.renewHint', 'Extend term. With 2+ targets, merges them into a new contract.')}\n {opt === 'cancel' && t('cpq.quotes.arc.drawer.actions.cancelHint', 'Terminate one or more subscriptions.')}\n </span>\n </button>\n ))}\n </div>\n )}\n\n {step === 'pick-targets' && type && (\n <div className=\"space-y-3\">\n {eligibleLoading ? (\n <div className=\"text-sm text-muted-foreground\">{t('cpq.quotes.arc.drawer.loadingSubscriptions', 'Loading subscriptions\u2026')}</div>\n ) : eligible.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">{t('cpq.quotes.arc.drawer.noSubscriptions', 'No subscriptions for this customer.')}</div>\n ) : (\n <div className=\"space-y-1\">\n {eligible.map((s) => {\n const currencyMismatch = s.currencyCode !== currencyCode\n const isPicked = !!picked[s.id]\n return (\n <label\n key={s.id}\n className={`flex items-start gap-2 rounded-md border px-3 py-2 ${\n currencyMismatch\n ? 'opacity-50 border-dashed'\n : isPicked\n ? 'border-primary bg-primary/5'\n : 'border-input hover:bg-muted'\n }`}\n >\n <input\n type=\"checkbox\"\n disabled={currencyMismatch}\n checked={isPicked}\n onChange={(e) => {\n setPicked((prev) => {\n const next = { ...prev }\n if (e.target.checked) next[s.id] = s\n else delete next[s.id]\n return next\n })\n }}\n />\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-sm font-medium\">\n {s.name}{' '}\n <span className=\"text-xs font-mono text-muted-foreground\">{s.code}</span>\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {s.status} \u2022 {s.billingCycle} \u2022 {t('cpq.lines.mrc', 'MRC')} {s.mrcAmount.toFixed(2)} {s.currencyCode}\n {s.currentTermEnd ? ' \u2022 ' : ''}\n {s.currentTermEnd\n ? t('cpq.quotes.arc.drawer.termEnds', 'Term ends {date}', { date: new Date(s.currentTermEnd).toLocaleDateString() })\n : ''}\n </div>\n {currencyMismatch && (\n <div className=\"text-xs text-status-warning-text mt-1\">\n {t('cpq.quotes.arc.drawer.currencyMismatch', 'Currency mismatch ({subscriptionCurrency} vs {quoteCurrency}) \u2014 cannot include', { subscriptionCurrency: s.currencyCode, quoteCurrency: currencyCode })}\n </div>\n )}\n </div>\n </label>\n )\n })}\n </div>\n )}\n {!cyclesMatch && (\n <Alert variant=\"warning\" className=\"text-xs\">\n {t('cpq.quotes.arc.drawer.cyclesMismatch', 'Picked targets have different billing cycles \u2014 merge requires matching cycles.')}\n </Alert>\n )}\n {isMergeRenew && (\n <Alert variant=\"warning\" className=\"text-xs\">\n {t('cpq.quotes.arc.drawer.mergeWarning', 'Merging {count} contracts into a new one \u2014 sources will be marked Merged at activation.', { count: pickedList.length })}\n </Alert>\n )}\n </div>\n )}\n\n {step === 'config-renew' && type === 'renew' && !isMergeRenew && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">{t('cpq.quotes.arc.drawer.renew.singleTitle', 'New contract term for {name}:', { name: pickedList[0]?.name ?? '' })}</p>\n <p className=\"text-xs text-muted-foreground\">\n {t('cpq.quotes.arc.drawer.renew.singleHintBefore', 'Pre-filled with same length as the current term, starting at the current term end. Type a new value in')}{' '}\n <span className=\"font-medium\">{t('cpq.quotes.arc.drawer.fields.months', 'Months')}</span>{' '}\n {t('cpq.quotes.arc.drawer.renew.singleHintAfter', 'to auto-shift the end date, or edit start/end directly.')}\n </p>\n <div className=\"grid grid-cols-3 gap-3\">\n <DateInput\n label={t('cpq.quotes.arc.drawer.fields.start', 'Start')}\n value={singleTerm.start}\n onChange={(v) => {\n // If months is set, slide end too so the contract length stays.\n const months = singleTerm.months ? Number(singleTerm.months) : null\n const end = months && v ? addMonthsToIso(v, months) : singleTerm.end\n setSingleTerm({ ...singleTerm, start: v, end })\n }}\n />\n <DateInput\n label={t('cpq.quotes.arc.drawer.fields.end', 'End')}\n value={singleTerm.end}\n onChange={(v) => setSingleTerm({ ...singleTerm, end: v })}\n />\n <NumberInput\n label={t('cpq.quotes.arc.drawer.fields.months', 'Months')}\n value={singleTerm.months}\n onChange={(v) => {\n // Recompute end = start + months when both are present.\n const months = v ? Number(v) : null\n const end = months && singleTerm.start\n ? addMonthsToIso(singleTerm.start, months)\n : singleTerm.end\n setSingleTerm({ ...singleTerm, months: v, end })\n }}\n />\n </div>\n </div>\n )}\n\n {step === 'config-renew' && type === 'renew' && isMergeRenew && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">\n {t('cpq.quotes.arc.drawer.renew.mergeTitle', 'New contract details (merge of {count} subscriptions):', { count: pickedList.length })}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {t('cpq.quotes.arc.drawer.renew.mergeHintBefore', 'Type a value into')}{' '}\n <span className=\"font-medium\">{t('cpq.quotes.arc.drawer.fields.months', 'Months')}</span>{' '}\n {t('cpq.quotes.arc.drawer.renew.mergeHintAfter', 'to auto-fill both dates \u2014 start defaults to today, end is start + months.')}\n </p>\n <div className=\"grid grid-cols-3 gap-3\">\n <DateInput\n label={t('cpq.quotes.arc.drawer.fields.start', 'Start')}\n value={mergeTerm.start}\n onChange={(v) => {\n const months = mergeTerm.months ? Number(mergeTerm.months) : null\n const end = months && v ? addMonthsToIso(v, months) : mergeTerm.end\n setMergeTerm({ ...mergeTerm, start: v, end })\n }}\n />\n <DateInput\n label={t('cpq.quotes.arc.drawer.fields.end', 'End')}\n value={mergeTerm.end}\n onChange={(v) => setMergeTerm({ ...mergeTerm, end: v })}\n />\n <NumberInput\n label={t('cpq.quotes.arc.drawer.fields.months', 'Months')}\n value={mergeTerm.months}\n onChange={(v) => {\n const months = v ? Number(v) : null\n const start = mergeTerm.start || todayIso()\n const end = months ? addMonthsToIso(start, months) : mergeTerm.end\n setMergeTerm({ ...mergeTerm, months: v, start, end })\n }}\n />\n </div>\n <div className=\"grid grid-cols-2 gap-3\">\n <TextInput\n label={t('cpq.quotes.arc.drawer.fields.newCode', 'New contract code (optional \u2014 auto-generated if blank)')}\n value={mergeTerm.code}\n onChange={(v) => setMergeTerm({ ...mergeTerm, code: v })}\n />\n <TextInput\n label={t('cpq.quotes.arc.drawer.fields.newName', 'New contract name (optional)')}\n value={mergeTerm.name}\n onChange={(v) => setMergeTerm({ ...mergeTerm, name: v })}\n />\n </div>\n </div>\n )}\n\n {step === 'config-cancel' && type === 'cancel' && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">{t('cpq.quotes.arc.drawer.cancel.title', 'Reason for cancellation:')}</p>\n <div className=\"space-y-2\">\n <label className=\"block text-xs text-muted-foreground\">{t('cpq.quotes.arc.drawer.cancel.reasonCode', 'Reason code')}</label>\n <select\n value={cancelMeta.reasonCode}\n onChange={(e) => setCancelMeta({ ...cancelMeta, reasonCode: e.target.value })}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n >\n <option value=\"\">{t('cpq.quotes.arc.drawer.cancel.pickOne', '\u2014 pick one \u2014')}</option>\n {reasonCodes.map((r) => (\n <option key={r.value} value={r.value}>\n {r.label}\n </option>\n ))}\n </select>\n </div>\n <TextInput\n label={t('cpq.quotes.arc.drawer.cancel.freeText', 'Free text (optional)')}\n value={cancelMeta.reasonText}\n onChange={(v) => setCancelMeta({ ...cancelMeta, reasonText: v })}\n />\n <div className=\"grid grid-cols-2 gap-3\">\n <TextInput\n label={t('cpq.quotes.arc.drawer.cancel.etfAmount', 'ETF amount (optional)')}\n value={cancelMeta.etfAmount}\n onChange={(v) => setCancelMeta({ ...cancelMeta, etfAmount: v })}\n />\n <TextInput\n label={t('cpq.quotes.arc.drawer.cancel.etfCurrency', 'ETF currency')}\n value={cancelMeta.etfCurrency}\n onChange={(v) => setCancelMeta({ ...cancelMeta, etfCurrency: v })}\n />\n </div>\n </div>\n )}\n\n {step === 'review' && (\n <div className=\"space-y-3\">\n <p className=\"text-sm\">{t('cpq.quotes.arc.drawer.review.title', 'Review and submit:')}</p>\n <ul className=\"text-sm space-y-1 list-disc pl-5\">\n <li>{t('cpq.quotes.arc.drawer.review.action', 'Action: {type}', { type: type ? arcTypeLabel(t, type) : '' })}</li>\n <li>{t('cpq.quotes.arc.drawer.review.targets', 'Targets: {count}', { count: pickedList.length })}</li>\n {pickedList.map((s) => (\n <li key={s.id} className=\"text-muted-foreground\">\n {s.code} \u2014 {s.name}\n </li>\n ))}\n {type === 'renew' && !isMergeRenew && (\n <li>\n {singleTerm.months\n ? t('cpq.quotes.arc.drawer.review.termWithMonths', 'Term: {start} \u2192 {end} ({months}m)', { start: singleTerm.start, end: singleTerm.end, months: singleTerm.months })\n : t('cpq.quotes.arc.drawer.review.term', 'Term: {start} \u2192 {end}', { start: singleTerm.start, end: singleTerm.end })}\n </li>\n )}\n {type === 'renew' && isMergeRenew && (\n <li>\n {t('cpq.quotes.arc.drawer.review.mergeTerm', 'Merge term: {start} \u2192 {end}', { start: mergeTerm.start, end: mergeTerm.end })}\n {mergeTerm.code ? ' \u2022 ' : ''}\n {mergeTerm.code ? t('cpq.quotes.arc.drawer.review.mergeCode', 'code={code}', { code: mergeTerm.code }) : ''}\n {mergeTerm.name ? ' \u2022 ' : ''}\n {mergeTerm.name ? t('cpq.quotes.arc.drawer.review.mergeName', 'name={name}', { name: mergeTerm.name }) : ''}\n </li>\n )}\n {type === 'cancel' && (\n <li>\n {t('cpq.quotes.arc.drawer.review.reason', 'Reason: {reason}', { reason: cancelMeta.reasonCode })}\n {cancelMeta.etfAmount ? ' \u2022 ' : ''}\n {cancelMeta.etfAmount\n ? t('cpq.quotes.arc.drawer.review.etf', 'ETF {amount} {currency}', { amount: cancelMeta.etfAmount, currency: cancelMeta.etfCurrency })\n : ''}\n </li>\n )}\n </ul>\n <Alert variant=\"warning\" className=\"text-xs\">\n {t('cpq.quotes.arc.drawer.review.lockWarning', 'Submitting locks the quote type. Lines will be edited on the host page after this completes.')}\n </Alert>\n {submitError && (\n <Alert variant=\"destructive\" className=\"text-xs\">{submitError}</Alert>\n )}\n </div>\n )}\n\n {step === 'submitting' && (\n <div className=\"text-sm text-muted-foreground\">{t('cpq.quotes.arc.drawer.submitting', 'Submitting\u2026')}</div>\n )}\n\n {step === 'done' && (\n <div className=\"text-sm text-status-success-text\">\n {t('cpq.quotes.arc.drawer.done', 'Done \u2014 close to return to the quote.')}\n </div>\n )}\n </div>\n\n <footer className=\"border-t px-5 pt-3 pb-20 flex items-center justify-between\">\n <button\n onClick={() => {\n if (step === 'pick-action') return\n if (step === 'pick-targets') {\n setStep(initialType === 'new' ? 'pick-action' : 'pick-targets')\n } else if (step === 'config-renew' || step === 'config-cancel') {\n setStep('pick-targets')\n } else if (step === 'review') {\n if (type === 'renew') setStep('config-renew')\n else if (type === 'cancel') setStep('config-cancel')\n else setStep('pick-targets')\n }\n }}\n disabled={\n step === 'pick-action' ||\n step === 'submitting' ||\n step === 'done' ||\n (step === 'pick-targets' && initialType !== 'new')\n }\n className=\"text-sm text-muted-foreground hover:text-foreground disabled:opacity-30\"\n >\n {t('cpq.quotes.arc.drawer.back', '\u2190 Back')}\n </button>\n <div className=\"flex gap-2\">\n <button\n onClick={onClose}\n className=\"rounded-md border border-input px-3 py-1.5 text-sm hover:bg-muted\"\n >\n {t('cpq.common.cancel', 'Cancel')}\n </button>\n {step === 'pick-targets' && (\n <button\n disabled={!canProceedFromTargets || !editable}\n onClick={() => {\n if (type === 'renew') setStep('config-renew')\n else if (type === 'cancel') setStep('config-cancel')\n else setStep('review')\n }}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50\"\n >\n {t('cpq.quotes.arc.drawer.next', 'Next')}\n </button>\n )}\n {(step === 'config-renew' || step === 'config-cancel') && (\n <button\n disabled={\n (step === 'config-renew' && !canProceedFromRenew) ||\n (step === 'config-cancel' && !canProceedFromCancel)\n }\n onClick={() => setStep('review')}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50\"\n >\n {t('cpq.quotes.arc.drawer.next', 'Next')}\n </button>\n )}\n {step === 'review' && (\n <button\n disabled={!editable}\n onClick={submit}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90 disabled:opacity-50\"\n >\n {t('cpq.quotes.arc.drawer.submit', 'Submit')}\n </button>\n )}\n {step === 'done' && (\n <button\n onClick={onClose}\n className=\"rounded-md bg-primary px-3 py-1.5 text-sm text-primary-foreground hover:bg-primary/90\"\n >\n {t('cpq.quotes.arc.drawer.closeButton', 'Close')}\n </button>\n )}\n </div>\n </footer>\n </div>\n </div>\n )\n}\n\nfunction DateInput(props: { label: string; value: string; onChange: (v: string) => void }) {\n return (\n <div>\n <label className=\"block text-xs text-muted-foreground mb-1\">{props.label}</label>\n <input\n type=\"date\"\n value={props.value}\n onChange={(e) => props.onChange(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n />\n </div>\n )\n}\n\n// Local \"labelled number\" wrapper kept as a string-in/string-out facade so\n// the surrounding code (which threads `value` through `mergeTerm.months` as\n// a string) doesn't have to change. The actual input behaviour comes from\n// the shared `NumberInput` component (select-on-focus + internal string\n// state \u2014 see components/NumberInput.tsx for the rationale).\nfunction NumberInput(props: { label: string; value: string; onChange: (v: string) => void }) {\n return (\n <div>\n <label className=\"block text-xs text-muted-foreground mb-1\">{props.label}</label>\n <BaseNumberInput\n integer\n min={1}\n value={props.value === '' ? null : Number(props.value)}\n onChange={(n) => props.onChange(n == null ? '' : String(n))}\n />\n </div>\n )\n}\n\nfunction TextInput(props: { label: string; value: string; onChange: (v: string) => void }) {\n return (\n <div>\n <label className=\"block text-xs text-muted-foreground mb-1\">{props.label}</label>\n <input\n type=\"text\"\n value={props.value}\n onChange={(e) => props.onChange(e.target.value)}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm\"\n />\n </div>\n )\n}\n\n// Add `months` whole calendar months to an ISO date string (YYYY-MM-DD) and\n// return the result in the same format. Used by the renew config step so the\n// operator can edit just the months field and have the end date auto-update.\nfunction addMonthsToIso(iso: string, months: number): string {\n if (!iso) return iso\n const d = new Date(iso)\n if (Number.isNaN(d.getTime())) return iso\n d.setMonth(d.getMonth() + months)\n return d.toISOString().split('T')[0]\n}\n\n// Today as YYYY-MM-DD \u2014 used as the default `Start` when the operator types\n// a `Months` value first on a brand-new merge contract.\nfunction todayIso(): string {\n return new Date().toISOString().split('T')[0]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,208 @@
|
|
|
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 { Button } from "@open-mercato/ui/primitives/button";
|
|
7
|
+
import { Input } from "@open-mercato/ui/primitives/input";
|
|
8
|
+
import { Textarea } from "@open-mercato/ui/primitives/textarea";
|
|
9
|
+
import { Alert, AlertDescription } from "@open-mercato/ui/primitives/alert";
|
|
10
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@open-mercato/ui/primitives/dialog";
|
|
11
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@open-mercato/ui/primitives/select";
|
|
12
|
+
import {
|
|
13
|
+
ADJUSTMENT_ERROR_FALLBACKS,
|
|
14
|
+
adjustmentErrorCode,
|
|
15
|
+
allowedAdjustmentTypes,
|
|
16
|
+
buildAdjustmentDraft,
|
|
17
|
+
formatDepth,
|
|
18
|
+
discountDepth,
|
|
19
|
+
toAdjustmentPatch
|
|
20
|
+
} from "../../../../../lib/priceAdjustmentView.js";
|
|
21
|
+
const TYPE_FALLBACKS = {
|
|
22
|
+
price_override: "Set a new unit price",
|
|
23
|
+
discount_percent: "Percentage discount",
|
|
24
|
+
discount_absolute: "Amount off the charge total"
|
|
25
|
+
};
|
|
26
|
+
function money(amount, currency) {
|
|
27
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
28
|
+
}
|
|
29
|
+
function PriceAdjustmentDialog(props) {
|
|
30
|
+
const { open, quoteId, currency, pricingRevision, target, targetLabel, charge, existing, storedAdjustmentIds, reasons, onClose, onSaved, onStale } = props;
|
|
31
|
+
const t = useT();
|
|
32
|
+
const types = allowedAdjustmentTypes(target.level, charge);
|
|
33
|
+
const initialForm = React.useCallback(() => ({
|
|
34
|
+
adjustmentType: existing?.adjustmentType ?? types[0] ?? "discount_percent",
|
|
35
|
+
value: existing ? String(existing.value) : "",
|
|
36
|
+
reasonCode: existing?.reasonCode ?? "",
|
|
37
|
+
reasonNote: existing?.reasonNote ?? "",
|
|
38
|
+
chargeTypeFilter: existing?.chargeTypeFilter ?? ""
|
|
39
|
+
}), [existing, types]);
|
|
40
|
+
const [form, setForm] = React.useState(initialForm);
|
|
41
|
+
const [preview, setPreview] = React.useState(null);
|
|
42
|
+
const [previewing, setPreviewing] = React.useState(false);
|
|
43
|
+
const [errorCode, setErrorCode] = React.useState(null);
|
|
44
|
+
const [saving, setSaving] = React.useState(false);
|
|
45
|
+
React.useEffect(() => {
|
|
46
|
+
if (open) {
|
|
47
|
+
setForm(initialForm());
|
|
48
|
+
setPreview(null);
|
|
49
|
+
setErrorCode(null);
|
|
50
|
+
}
|
|
51
|
+
}, [open]);
|
|
52
|
+
const draft = buildAdjustmentDraft(form, target, currency, pricingRevision);
|
|
53
|
+
const draftKey = draft && form.reasonCode ? JSON.stringify(draft) : null;
|
|
54
|
+
const message = (code) => t(`cpq.priceAdjustments.errors.${code}`, ADJUSTMENT_ERROR_FALLBACKS[code] ?? code);
|
|
55
|
+
React.useEffect(() => {
|
|
56
|
+
if (!open || !draftKey) {
|
|
57
|
+
setPreview(null);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let cancelled = false;
|
|
61
|
+
const timer = window.setTimeout(async () => {
|
|
62
|
+
setPreviewing(true);
|
|
63
|
+
const call = await apiCall(
|
|
64
|
+
`/api/cpq/quotes/${quoteId}/price-adjustments/preview`,
|
|
65
|
+
{ method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ draft: JSON.parse(draftKey), ...existing ? { replaceAdjustmentId: existing.id } : {} }) }
|
|
66
|
+
);
|
|
67
|
+
if (cancelled) return;
|
|
68
|
+
setPreviewing(false);
|
|
69
|
+
if (call.ok && call.result) {
|
|
70
|
+
setPreview(call.result);
|
|
71
|
+
setErrorCode(null);
|
|
72
|
+
} else {
|
|
73
|
+
setPreview(null);
|
|
74
|
+
setErrorCode(adjustmentErrorCode(call.status, call.result));
|
|
75
|
+
}
|
|
76
|
+
}, 400);
|
|
77
|
+
return () => {
|
|
78
|
+
cancelled = true;
|
|
79
|
+
window.clearTimeout(timer);
|
|
80
|
+
};
|
|
81
|
+
}, [open, draftKey, quoteId, existing]);
|
|
82
|
+
const previewLine = preview?.quote.lines.find((line) => line.lineId === target.quoteLineId);
|
|
83
|
+
const previewCharge = target.level === "charge" ? previewLine?.charges.find((entry) => entry.chargeCode === target.chargeCode) : void 0;
|
|
84
|
+
const previewErrors = preview?.errors.filter((error) => existing ? error.adjustmentId === existing.id : !storedAdjustmentIds.includes(error.adjustmentId)) ?? [];
|
|
85
|
+
const noteRequired = form.reasonCode === "other" && !form.reasonNote.trim();
|
|
86
|
+
const canSave = !!draft && !!form.reasonCode && !noteRequired && !saving && previewErrors.length === 0;
|
|
87
|
+
const save = async () => {
|
|
88
|
+
if (!draft || !canSave) return;
|
|
89
|
+
setSaving(true);
|
|
90
|
+
setErrorCode(null);
|
|
91
|
+
const call = existing ? await apiCall(`/api/cpq/quotes/${quoteId}/price-adjustments/${existing.id}`, {
|
|
92
|
+
method: "PATCH",
|
|
93
|
+
headers: { "content-type": "application/json" },
|
|
94
|
+
body: JSON.stringify(toAdjustmentPatch(draft))
|
|
95
|
+
}) : await apiCall(`/api/cpq/quotes/${quoteId}/price-adjustments`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "content-type": "application/json" },
|
|
98
|
+
body: JSON.stringify(draft)
|
|
99
|
+
});
|
|
100
|
+
setSaving(false);
|
|
101
|
+
if (call.ok && call.result?.quote) {
|
|
102
|
+
onSaved(call.result.quote);
|
|
103
|
+
onClose();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
setErrorCode(adjustmentErrorCode(call.status, call.result));
|
|
107
|
+
};
|
|
108
|
+
const onKeyDown = (event) => {
|
|
109
|
+
if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
void save();
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const standardUnit = charge?.standardUnitPrice ?? charge?.unitPrice;
|
|
115
|
+
const valueHint = form.adjustmentType === "discount_percent" ? t("cpq.priceAdjustments.dialog.percentHint", "Percent off the standard price (0\u2013100)") : form.adjustmentType === "price_override" ? t("cpq.priceAdjustments.dialog.overrideHint", "New unit price, at most the standard price") : t("cpq.priceAdjustments.dialog.absoluteHint", "Amount taken off this charge\u2019s total");
|
|
116
|
+
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: (next) => {
|
|
117
|
+
if (!next) onClose();
|
|
118
|
+
}, children: /* @__PURE__ */ jsxs(DialogContent, { onKeyDown, "data-testid": "cpq-price-adjustment-dialog", children: [
|
|
119
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [
|
|
120
|
+
/* @__PURE__ */ jsx(DialogTitle, { children: existing ? t("cpq.priceAdjustments.dialog.editTitle", "Edit price adjustment") : target.level === "line" ? t("cpq.priceAdjustments.dialog.lineTitle", "Discount line") : t("cpq.priceAdjustments.dialog.chargeTitle", "Adjust price") }),
|
|
121
|
+
/* @__PURE__ */ jsxs(DialogDescription, { children: [
|
|
122
|
+
targetLabel,
|
|
123
|
+
charge && standardUnit !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
124
|
+
" \xB7 ",
|
|
125
|
+
t("cpq.priceAdjustments.dialog.standard", "Standard"),
|
|
126
|
+
" ",
|
|
127
|
+
money(standardUnit, currency)
|
|
128
|
+
] })
|
|
129
|
+
] })
|
|
130
|
+
] }),
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-3 text-sm", children: [
|
|
132
|
+
/* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
|
|
133
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: t("cpq.priceAdjustments.dialog.type", "Adjustment") }),
|
|
134
|
+
/* @__PURE__ */ jsxs(Select, { value: form.adjustmentType, onValueChange: (value) => setForm({ ...form, adjustmentType: value }), children: [
|
|
135
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
136
|
+
/* @__PURE__ */ jsx(SelectContent, { children: types.map((type) => /* @__PURE__ */ jsx(SelectItem, { value: type, children: t(`cpq.priceAdjustments.dialog.types.${type}`, TYPE_FALLBACKS[type]) }, type)) })
|
|
137
|
+
] })
|
|
138
|
+
] }),
|
|
139
|
+
target.level === "line" && /* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
|
|
140
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: t("cpq.priceAdjustments.dialog.appliesTo", "Applies to") }),
|
|
141
|
+
/* @__PURE__ */ jsxs(Select, { value: form.chargeTypeFilter || "all", onValueChange: (value) => setForm({ ...form, chargeTypeFilter: value === "all" ? "" : value }), children: [
|
|
142
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
143
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
144
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "all", children: t("cpq.priceAdjustments.dialog.allCharges", "All one-time and recurring charges") }),
|
|
145
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "mrc", children: t("cpq.priceAdjustments.dialog.mrcOnly", "Recurring charges only") }),
|
|
146
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "nrc", children: t("cpq.priceAdjustments.dialog.nrcOnly", "One-time charges only") })
|
|
147
|
+
] })
|
|
148
|
+
] })
|
|
149
|
+
] }),
|
|
150
|
+
/* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
|
|
151
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: t("cpq.priceAdjustments.dialog.value", "Value") }),
|
|
152
|
+
/* @__PURE__ */ jsx(Input, { inputMode: "decimal", autoFocus: true, value: form.value, onChange: (event) => setForm({ ...form, value: event.target.value }), "aria-describedby": "cpq-adjustment-value-hint" }),
|
|
153
|
+
/* @__PURE__ */ jsx("span", { id: "cpq-adjustment-value-hint", className: "block text-xs text-muted-foreground", children: valueHint })
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
|
|
156
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium", children: t("cpq.priceAdjustments.dialog.reason", "Reason") }),
|
|
157
|
+
/* @__PURE__ */ jsxs(Select, { value: form.reasonCode, onValueChange: (value) => setForm({ ...form, reasonCode: value }), children: [
|
|
158
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("cpq.priceAdjustments.dialog.reasonPlaceholder", "Pick a reason") }) }),
|
|
159
|
+
/* @__PURE__ */ jsx(SelectContent, { children: reasons.map((reason) => /* @__PURE__ */ jsx(SelectItem, { value: reason.value, children: reason.label }, reason.value)) })
|
|
160
|
+
] })
|
|
161
|
+
] }),
|
|
162
|
+
/* @__PURE__ */ jsxs("label", { className: "block space-y-1", children: [
|
|
163
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs font-medium", children: [
|
|
164
|
+
t("cpq.priceAdjustments.dialog.note", "Note"),
|
|
165
|
+
form.reasonCode === "other" ? " *" : ""
|
|
166
|
+
] }),
|
|
167
|
+
/* @__PURE__ */ jsx(Textarea, { rows: 2, maxLength: 500, value: form.reasonNote, onChange: (event) => setForm({ ...form, reasonNote: event.target.value }) })
|
|
168
|
+
] }),
|
|
169
|
+
/* @__PURE__ */ jsxs("div", { className: "rounded border bg-muted/20 p-2 text-xs", "aria-live": "polite", "data-testid": "cpq-adjustment-preview", children: [
|
|
170
|
+
!draftKey && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("cpq.priceAdjustments.dialog.previewEmpty", "Enter a value and reason to preview the new price.") }),
|
|
171
|
+
draftKey && previewing && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("cpq.priceAdjustments.dialog.previewing", "Calculating\u2026") }),
|
|
172
|
+
draftKey && !previewing && previewCharge && /* @__PURE__ */ jsxs("span", { children: [
|
|
173
|
+
t("cpq.priceAdjustments.dialog.previewCharge", "New offer price"),
|
|
174
|
+
": ",
|
|
175
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: money(previewCharge.unitPrice, currency) }),
|
|
176
|
+
" ",
|
|
177
|
+
formatDepth(discountDepth(previewCharge.standardUnitPrice ?? standardUnit, previewCharge.unitPrice)) ?? ""
|
|
178
|
+
] }),
|
|
179
|
+
draftKey && !previewing && !previewCharge && previewLine && /* @__PURE__ */ jsxs("span", { children: [
|
|
180
|
+
t("cpq.priceAdjustments.dialog.previewLine", "Line totals"),
|
|
181
|
+
": ",
|
|
182
|
+
t("cpq.lines.mrc", "MRC"),
|
|
183
|
+
" ",
|
|
184
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: money(previewLine.mrcTotal, currency) }),
|
|
185
|
+
" \xB7 ",
|
|
186
|
+
t("cpq.lines.nrc", "NRC"),
|
|
187
|
+
" ",
|
|
188
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: money(previewLine.nrcTotal, currency) })
|
|
189
|
+
] })
|
|
190
|
+
] }),
|
|
191
|
+
(errorCode || previewErrors.length > 0 || noteRequired) && /* @__PURE__ */ jsx(Alert, { variant: "warning", children: /* @__PURE__ */ jsxs(AlertDescription, { children: [
|
|
192
|
+
errorCode ? message(errorCode) : previewErrors.length > 0 ? message(previewErrors[0].code) : message("REASON_NOTE_REQUIRED"),
|
|
193
|
+
errorCode === "STALE_REVISION" && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", className: "ml-2", onClick: () => {
|
|
194
|
+
onStale();
|
|
195
|
+
onClose();
|
|
196
|
+
}, children: t("cpq.priceAdjustments.dialog.reload", "Reload") })
|
|
197
|
+
] }) })
|
|
198
|
+
] }),
|
|
199
|
+
/* @__PURE__ */ jsxs(DialogFooter, { children: [
|
|
200
|
+
/* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, children: t("cpq.priceAdjustments.dialog.cancel", "Cancel") }),
|
|
201
|
+
/* @__PURE__ */ jsx(Button, { onClick: () => void save(), disabled: !canSave, title: t("cpq.priceAdjustments.dialog.saveShortcut", "Ctrl/Cmd+Enter"), children: saving ? t("cpq.priceAdjustments.dialog.saving", "Saving\u2026") : t("cpq.priceAdjustments.dialog.save", "Save") })
|
|
202
|
+
] })
|
|
203
|
+
] }) });
|
|
204
|
+
}
|
|
205
|
+
export {
|
|
206
|
+
PriceAdjustmentDialog
|
|
207
|
+
};
|
|
208
|
+
//# sourceMappingURL=PriceAdjustmentDialog.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../../../src/modules/cpq/backend/cpq/quotes/%5Bid%5D/_components/PriceAdjustmentDialog.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 { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport {\n ADJUSTMENT_ERROR_FALLBACKS,\n adjustmentErrorCode,\n allowedAdjustmentTypes,\n buildAdjustmentDraft,\n formatDepth,\n discountDepth,\n toAdjustmentPatch,\n type AdjustmentForm,\n type AdjustmentTarget,\n type AdjustmentType,\n type WaterfallChargeInput,\n} from '../../../../../lib/priceAdjustmentView'\n\nexport type AdjustmentReason = { value: string; label: string }\n\n/** Minimal view of the quote the dialog needs, kept local so the page owns the full type. */\ntype PreviewQuote = {\n pricingRevision?: number\n lines: Array<{ lineId: string; mrcTotal: number; nrcTotal: number; charges: Array<WaterfallChargeInput & { chargeCode: string; adjustmentErrors?: Array<{ adjustmentId: string; code: string }> }> }>\n}\n\nexport type ExistingAdjustment = {\n id: string\n adjustmentType: AdjustmentType\n value: number\n reasonCode: string\n reasonNote?: string | null\n chargeTypeFilter?: 'nrc' | 'mrc' | null\n}\n\nexport type PriceAdjustmentDialogProps<Q extends PreviewQuote> = {\n open: boolean\n quoteId: string\n currency: string\n pricingRevision: number\n target: AdjustmentTarget\n /** Display name of the line / charge being adjusted. */\n targetLabel: string\n /** The charge for charge-level targets (drives type options and the current price). */\n charge?: WaterfallChargeInput & { chargeCode: string; chargeName: string }\n existing?: ExistingAdjustment | null\n /** Ids of adjustments already stored on the quote; their errors don't block this draft. */\n storedAdjustmentIds: string[]\n reasons: AdjustmentReason[]\n onClose: () => void\n onSaved: (quote: Q) => void\n /** Called when the server reports a stale revision; the page reloads the quote. */\n onStale: () => void\n}\n\nconst TYPE_FALLBACKS: Record<AdjustmentType, string> = {\n price_override: 'Set a new unit price',\n discount_percent: 'Percentage discount',\n discount_absolute: 'Amount off the charge total',\n}\n\nfunction money(amount: number, currency: string) {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nexport function PriceAdjustmentDialog<Q extends PreviewQuote>(props: PriceAdjustmentDialogProps<Q>) {\n const { open, quoteId, currency, pricingRevision, target, targetLabel, charge, existing, storedAdjustmentIds, reasons, onClose, onSaved, onStale } = props\n const t = useT()\n const types = allowedAdjustmentTypes(target.level, charge)\n const initialForm = React.useCallback((): AdjustmentForm => ({\n adjustmentType: existing?.adjustmentType ?? types[0] ?? 'discount_percent',\n value: existing ? String(existing.value) : '',\n reasonCode: existing?.reasonCode ?? '',\n reasonNote: existing?.reasonNote ?? '',\n chargeTypeFilter: existing?.chargeTypeFilter ?? '',\n }), [existing, types])\n const [form, setForm] = React.useState<AdjustmentForm>(initialForm)\n const [preview, setPreview] = React.useState<{ quote: Q; errors: Array<{ adjustmentId: string; code: string }> } | null>(null)\n const [previewing, setPreviewing] = React.useState(false)\n const [errorCode, setErrorCode] = React.useState<string | null>(null)\n const [saving, setSaving] = React.useState(false)\n\n React.useEffect(() => {\n if (open) { setForm(initialForm()); setPreview(null); setErrorCode(null) }\n // Reset only when the dialog (re)opens.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n const draft = buildAdjustmentDraft(form, target, currency, pricingRevision)\n const draftKey = draft && form.reasonCode ? JSON.stringify(draft) : null\n const message = (code: string) => t(`cpq.priceAdjustments.errors.${code}`, ADJUSTMENT_ERROR_FALLBACKS[code] ?? code)\n\n // Debounced, side-effect-free preview of the draft.\n React.useEffect(() => {\n if (!open || !draftKey) { setPreview(null); return }\n let cancelled = false\n const timer = window.setTimeout(async () => {\n setPreviewing(true)\n const call = await apiCall<{ quote: Q; errors: Array<{ adjustmentId: string; code: string }> }>(\n `/api/cpq/quotes/${quoteId}/price-adjustments/preview`,\n { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ draft: JSON.parse(draftKey), ...(existing ? { replaceAdjustmentId: existing.id } : {}) }) },\n )\n if (cancelled) return\n setPreviewing(false)\n if (call.ok && call.result) { setPreview(call.result); setErrorCode(null) }\n else { setPreview(null); setErrorCode(adjustmentErrorCode(call.status, call.result)) }\n }, 400)\n return () => { cancelled = true; window.clearTimeout(timer) }\n }, [open, draftKey, quoteId, existing])\n\n const previewLine = preview?.quote.lines.find((line) => line.lineId === target.quoteLineId)\n const previewCharge = target.level === 'charge' ? previewLine?.charges.find((entry) => entry.chargeCode === target.chargeCode) : undefined\n // The draft's id is server-generated on create, so \"not stored\" identifies it; on edit it keeps its id.\n const previewErrors = preview?.errors.filter((error) => existing ? error.adjustmentId === existing.id : !storedAdjustmentIds.includes(error.adjustmentId)) ?? []\n const noteRequired = form.reasonCode === 'other' && !form.reasonNote.trim()\n const canSave = !!draft && !!form.reasonCode && !noteRequired && !saving && previewErrors.length === 0\n\n const save = async () => {\n if (!draft || !canSave) return\n setSaving(true); setErrorCode(null)\n const call = existing\n ? await apiCall<{ quote: Q }>(`/api/cpq/quotes/${quoteId}/price-adjustments/${existing.id}`, {\n method: 'PATCH', headers: { 'content-type': 'application/json' }, body: JSON.stringify(toAdjustmentPatch(draft)),\n })\n : await apiCall<{ quote: Q }>(`/api/cpq/quotes/${quoteId}/price-adjustments`, {\n method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(draft),\n })\n setSaving(false)\n if (call.ok && call.result?.quote) { onSaved(call.result.quote); onClose(); return }\n setErrorCode(adjustmentErrorCode(call.status, call.result))\n }\n\n const onKeyDown = (event: React.KeyboardEvent) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { event.preventDefault(); void save() }\n }\n\n const standardUnit = charge?.standardUnitPrice ?? charge?.unitPrice\n const valueHint = form.adjustmentType === 'discount_percent'\n ? t('cpq.priceAdjustments.dialog.percentHint', 'Percent off the standard price (0\u2013100)')\n : form.adjustmentType === 'price_override'\n ? t('cpq.priceAdjustments.dialog.overrideHint', 'New unit price, at most the standard price')\n : t('cpq.priceAdjustments.dialog.absoluteHint', 'Amount taken off this charge\u2019s total')\n\n return (\n <Dialog open={open} onOpenChange={(next) => { if (!next) onClose() }}>\n <DialogContent onKeyDown={onKeyDown} data-testid=\"cpq-price-adjustment-dialog\">\n <DialogHeader>\n <DialogTitle>\n {existing\n ? t('cpq.priceAdjustments.dialog.editTitle', 'Edit price adjustment')\n : target.level === 'line'\n ? t('cpq.priceAdjustments.dialog.lineTitle', 'Discount line')\n : t('cpq.priceAdjustments.dialog.chargeTitle', 'Adjust price')}\n </DialogTitle>\n <DialogDescription>\n {targetLabel}\n {charge && standardUnit !== undefined && (\n <> \u00B7 {t('cpq.priceAdjustments.dialog.standard', 'Standard')} {money(standardUnit, currency)}</>\n )}\n </DialogDescription>\n </DialogHeader>\n\n <div className=\"space-y-3 text-sm\">\n <label className=\"block space-y-1\">\n <span className=\"text-xs font-medium\">{t('cpq.priceAdjustments.dialog.type', 'Adjustment')}</span>\n <Select value={form.adjustmentType} onValueChange={(value) => setForm({ ...form, adjustmentType: value as AdjustmentType })}>\n <SelectTrigger><SelectValue /></SelectTrigger>\n <SelectContent>\n {types.map((type) => (\n <SelectItem key={type} value={type}>{t(`cpq.priceAdjustments.dialog.types.${type}`, TYPE_FALLBACKS[type])}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n\n {target.level === 'line' && (\n <label className=\"block space-y-1\">\n <span className=\"text-xs font-medium\">{t('cpq.priceAdjustments.dialog.appliesTo', 'Applies to')}</span>\n <Select value={form.chargeTypeFilter || 'all'} onValueChange={(value) => setForm({ ...form, chargeTypeFilter: value === 'all' ? '' : value as 'nrc' | 'mrc' })}>\n <SelectTrigger><SelectValue /></SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">{t('cpq.priceAdjustments.dialog.allCharges', 'All one-time and recurring charges')}</SelectItem>\n <SelectItem value=\"mrc\">{t('cpq.priceAdjustments.dialog.mrcOnly', 'Recurring charges only')}</SelectItem>\n <SelectItem value=\"nrc\">{t('cpq.priceAdjustments.dialog.nrcOnly', 'One-time charges only')}</SelectItem>\n </SelectContent>\n </Select>\n </label>\n )}\n\n <label className=\"block space-y-1\">\n <span className=\"text-xs font-medium\">{t('cpq.priceAdjustments.dialog.value', 'Value')}</span>\n <Input inputMode=\"decimal\" autoFocus value={form.value} onChange={(event) => setForm({ ...form, value: event.target.value })} aria-describedby=\"cpq-adjustment-value-hint\" />\n <span id=\"cpq-adjustment-value-hint\" className=\"block text-xs text-muted-foreground\">{valueHint}</span>\n </label>\n\n <label className=\"block space-y-1\">\n <span className=\"text-xs font-medium\">{t('cpq.priceAdjustments.dialog.reason', 'Reason')}</span>\n <Select value={form.reasonCode} onValueChange={(value) => setForm({ ...form, reasonCode: value })}>\n <SelectTrigger><SelectValue placeholder={t('cpq.priceAdjustments.dialog.reasonPlaceholder', 'Pick a reason')} /></SelectTrigger>\n <SelectContent>\n {reasons.map((reason) => <SelectItem key={reason.value} value={reason.value}>{reason.label}</SelectItem>)}\n </SelectContent>\n </Select>\n </label>\n\n <label className=\"block space-y-1\">\n <span className=\"text-xs font-medium\">\n {t('cpq.priceAdjustments.dialog.note', 'Note')}{form.reasonCode === 'other' ? ' *' : ''}\n </span>\n <Textarea rows={2} maxLength={500} value={form.reasonNote} onChange={(event) => setForm({ ...form, reasonNote: event.target.value })} />\n </label>\n\n <div className=\"rounded border bg-muted/20 p-2 text-xs\" aria-live=\"polite\" data-testid=\"cpq-adjustment-preview\">\n {!draftKey && <span className=\"text-muted-foreground\">{t('cpq.priceAdjustments.dialog.previewEmpty', 'Enter a value and reason to preview the new price.')}</span>}\n {draftKey && previewing && <span className=\"text-muted-foreground\">{t('cpq.priceAdjustments.dialog.previewing', 'Calculating\u2026')}</span>}\n {draftKey && !previewing && previewCharge && (\n <span>\n {t('cpq.priceAdjustments.dialog.previewCharge', 'New offer price')}: <span className=\"font-mono font-medium\">{money(previewCharge.unitPrice, currency)}</span>\n {' '}{formatDepth(discountDepth(previewCharge.standardUnitPrice ?? standardUnit, previewCharge.unitPrice)) ?? ''}\n </span>\n )}\n {draftKey && !previewing && !previewCharge && previewLine && (\n <span>\n {t('cpq.priceAdjustments.dialog.previewLine', 'Line totals')}: {t('cpq.lines.mrc', 'MRC')} <span className=\"font-mono font-medium\">{money(previewLine.mrcTotal, currency)}</span>\n {' \u00B7 '}{t('cpq.lines.nrc', 'NRC')} <span className=\"font-mono font-medium\">{money(previewLine.nrcTotal, currency)}</span>\n </span>\n )}\n </div>\n\n {(errorCode || previewErrors.length > 0 || noteRequired) && (\n <Alert variant=\"warning\">\n <AlertDescription>\n {errorCode ? message(errorCode) : previewErrors.length > 0 ? message(previewErrors[0].code) : message('REASON_NOTE_REQUIRED')}\n {errorCode === 'STALE_REVISION' && (\n <Button variant=\"outline\" size=\"sm\" className=\"ml-2\" onClick={() => { onStale(); onClose() }}>\n {t('cpq.priceAdjustments.dialog.reload', 'Reload')}\n </Button>\n )}\n </AlertDescription>\n </Alert>\n )}\n </div>\n\n <DialogFooter>\n <Button variant=\"outline\" onClick={onClose}>{t('cpq.priceAdjustments.dialog.cancel', 'Cancel')}</Button>\n <Button onClick={() => void save()} disabled={!canSave} title={t('cpq.priceAdjustments.dialog.saveShortcut', 'Ctrl/Cmd+Enter')}>\n {saving ? t('cpq.priceAdjustments.dialog.saving', 'Saving\u2026') : t('cpq.priceAdjustments.dialog.save', 'Save')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -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
|