@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,15 +1,27 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
5
|
import { useRouter, useParams, useSearchParams } from "next/navigation";
|
|
5
6
|
import ArcQuoteConfigurator from "./_components/ArcQuoteConfigurator.js";
|
|
6
7
|
import { StatusPath } from "../../../../components/StatusPath.js";
|
|
7
|
-
import { ArrowRight, ExternalLink, Plus } from "lucide-react";
|
|
8
|
+
import { ArrowRight, ExternalLink, Plus, RotateCcw } from "lucide-react";
|
|
8
9
|
import { Alert, AlertTitle, AlertDescription } from "@open-mercato/ui/primitives/alert";
|
|
9
10
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
10
11
|
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
11
12
|
import { FormHeader } from "@open-mercato/ui/backend/forms";
|
|
12
13
|
import { NumberInput } from "../../../../components/NumberInput.js";
|
|
14
|
+
import { ChargeBreakdownTable } from "../../../../components/ChargeBreakdownTable.js";
|
|
15
|
+
import { PricingSummaryCard } from "../../../../components/PricingSummaryCard.js";
|
|
16
|
+
import { ArcLineDiff, LineActionBadge } from "../../../../components/ArcLineDiff.js";
|
|
17
|
+
import { ADJUST_PRICE_FEATURE } from "../../../../lib/priceAdjustmentView.js";
|
|
18
|
+
import { arcRecurringDelta } from "../../../../lib/quoteSummaryView.js";
|
|
19
|
+
import { usePriceAdjustments } from "./_components/usePriceAdjustments.js";
|
|
20
|
+
import { PriceAdjustmentsPanel } from "./_components/PriceAdjustmentsPanel.js";
|
|
21
|
+
import { ArcLineRemovalProvider, useArcLineRemoval, useArcLineRemovalUi } from "./_components/arcLineRemoval.js";
|
|
22
|
+
import { AdjustChargeAction, DiscountLineAction, ManualAdjustmentActions, PriceAdjustmentProvider, usePriceAdjustmentUi } from "./_components/priceAdjustmentContext.js";
|
|
23
|
+
import { useGrantedFeatures } from "../../../../components/useGrantedFeatures.js";
|
|
24
|
+
import { canRecalculateQuote, QUOTE_MANAGE_FEATURE } from "../../../../lib/quotePermissions.js";
|
|
13
25
|
import {
|
|
14
26
|
formatStatusLabel,
|
|
15
27
|
quoteCpqStatusMap
|
|
@@ -17,24 +29,24 @@ import {
|
|
|
17
29
|
function fmt(amount, currency) {
|
|
18
30
|
return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
|
|
19
31
|
}
|
|
20
|
-
|
|
21
|
-
discount_percent: "% discount",
|
|
22
|
-
discount_absolute: "fixed discount",
|
|
23
|
-
surcharge_percent: "% surcharge",
|
|
24
|
-
surcharge_absolute: "fixed surcharge",
|
|
25
|
-
price_override: "override"
|
|
26
|
-
};
|
|
27
|
-
function fmtRuleValue(adj, currency) {
|
|
28
|
-
if (adj.ruleType === "discount_percent" || adj.ruleType === "surcharge_percent") return `${adj.value}%`;
|
|
29
|
-
if (currency) return fmt(adj.value, currency);
|
|
30
|
-
return String(adj.value);
|
|
31
|
-
}
|
|
32
|
-
async function apiJson(url, init) {
|
|
32
|
+
async function apiJson(t, url, init) {
|
|
33
33
|
const res = await fetch(url, init);
|
|
34
34
|
const json = await res.json();
|
|
35
|
-
if (!res.ok) throw new Error(json?.error ??
|
|
35
|
+
if (!res.ok) throw new Error(json?.error ?? t("cpq.common.requestFailed", "Request failed ({status})", { status: res.status }));
|
|
36
36
|
return json;
|
|
37
37
|
}
|
|
38
|
+
function quoteTypeLabel(t, quoteType) {
|
|
39
|
+
switch (quoteType) {
|
|
40
|
+
case "amend":
|
|
41
|
+
return t("cpq.quotes.type.amend", "amend");
|
|
42
|
+
case "renew":
|
|
43
|
+
return t("cpq.quotes.type.renew", "renew");
|
|
44
|
+
case "cancel":
|
|
45
|
+
return t("cpq.quotes.type.cancel", "cancel");
|
|
46
|
+
default:
|
|
47
|
+
return quoteType;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
38
50
|
function MetaCard({ label, children }) {
|
|
39
51
|
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
|
|
40
52
|
/* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: label }),
|
|
@@ -42,6 +54,7 @@ function MetaCard({ label, children }) {
|
|
|
42
54
|
] });
|
|
43
55
|
}
|
|
44
56
|
function MoreActionsDropdown({ items }) {
|
|
57
|
+
const t = useT();
|
|
45
58
|
const [open, setOpen] = React.useState(false);
|
|
46
59
|
const ref = React.useRef(null);
|
|
47
60
|
React.useEffect(() => {
|
|
@@ -55,7 +68,7 @@ function MoreActionsDropdown({ items }) {
|
|
|
55
68
|
if (items.length === 0) return null;
|
|
56
69
|
return /* @__PURE__ */ jsxs("div", { className: "relative", ref, children: [
|
|
57
70
|
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: () => setOpen((v) => !v), children: [
|
|
58
|
-
"Actions",
|
|
71
|
+
t("cpq.quotes.detail.actions", "Actions"),
|
|
59
72
|
/* @__PURE__ */ jsx("svg", { className: "h-3 w-3 ml-1", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" }) })
|
|
60
73
|
] }),
|
|
61
74
|
open && /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-full mt-1 z-50 min-w-[200px] rounded-md border bg-popover shadow-md py-1", children: items.map((item) => {
|
|
@@ -81,25 +94,28 @@ function MoreActionsDropdown({ items }) {
|
|
|
81
94
|
] });
|
|
82
95
|
}
|
|
83
96
|
function buildMoreActions({
|
|
97
|
+
t,
|
|
84
98
|
recalculate,
|
|
99
|
+
canRecalculate,
|
|
85
100
|
submitting,
|
|
86
101
|
deletable,
|
|
87
102
|
onDelete,
|
|
88
103
|
deleting
|
|
89
104
|
}) {
|
|
90
|
-
const items = [
|
|
91
|
-
|
|
105
|
+
const items = [];
|
|
106
|
+
if (canRecalculate) {
|
|
107
|
+
items.push({
|
|
92
108
|
id: "recalculate",
|
|
93
|
-
label: "Recalculate",
|
|
109
|
+
label: t("cpq.quotes.detail.recalculate", "Recalculate"),
|
|
94
110
|
onSelect: recalculate,
|
|
95
111
|
loading: submitting,
|
|
96
112
|
disabled: submitting
|
|
97
|
-
}
|
|
98
|
-
|
|
113
|
+
});
|
|
114
|
+
}
|
|
99
115
|
if (deletable) {
|
|
100
116
|
items.push({
|
|
101
117
|
id: "delete",
|
|
102
|
-
label: "Delete",
|
|
118
|
+
label: t("cpq.common.delete", "Delete"),
|
|
103
119
|
onSelect: onDelete,
|
|
104
120
|
disabled: deleting,
|
|
105
121
|
loading: deleting
|
|
@@ -113,7 +129,9 @@ function CpqQuoteDetailPage(props) {
|
|
|
113
129
|
const searchParams = useSearchParams();
|
|
114
130
|
const cpqConfigId = props.params?.id ?? urlParams?.id;
|
|
115
131
|
const isNew = cpqConfigId === "new";
|
|
132
|
+
const t = useT();
|
|
116
133
|
const [cpqQuote, setCpqQuote] = React.useState(null);
|
|
134
|
+
const grantedFeatures = useGrantedFeatures([QUOTE_MANAGE_FEATURE, ADJUST_PRICE_FEATURE]);
|
|
117
135
|
const [loading, setLoading] = React.useState(true);
|
|
118
136
|
const [error, setError] = React.useState(null);
|
|
119
137
|
const [submitting, setSubmitting] = React.useState(false);
|
|
@@ -156,12 +174,26 @@ function CpqQuoteDetailPage(props) {
|
|
|
156
174
|
if (!cpqConfigId || isNew) return;
|
|
157
175
|
void loadArcTargets();
|
|
158
176
|
}, [cpqConfigId, isNew, loadArcTargets]);
|
|
177
|
+
const reloadQuote = React.useCallback(async () => {
|
|
178
|
+
try {
|
|
179
|
+
setCpqQuote(await apiJson(t, `/api/cpq/quotes/${cpqConfigId}`));
|
|
180
|
+
} catch (err) {
|
|
181
|
+
setError(err.message);
|
|
182
|
+
}
|
|
183
|
+
}, [cpqConfigId, t]);
|
|
184
|
+
const priceAdjustments = usePriceAdjustments({
|
|
185
|
+
quote: isNew ? null : cpqQuote,
|
|
186
|
+
quoteType: arcQuoteType,
|
|
187
|
+
canAdjust: grantedFeatures.includes(QUOTE_MANAGE_FEATURE) && grantedFeatures.includes(ADJUST_PRICE_FEATURE),
|
|
188
|
+
onQuote: setCpqQuote,
|
|
189
|
+
reloadQuote
|
|
190
|
+
});
|
|
159
191
|
React.useEffect(() => {
|
|
160
192
|
if (isNew) {
|
|
161
193
|
const quoteId = searchParams.get("quoteId");
|
|
162
194
|
const customerId = searchParams.get("customerId");
|
|
163
195
|
if (!customerId) {
|
|
164
|
-
setError("customerId is required to create a CPQ quote");
|
|
196
|
+
setError(t("cpq.quotes.detail.customerIdRequired", "customerId is required to create a CPQ quote"));
|
|
165
197
|
setLoading(false);
|
|
166
198
|
return;
|
|
167
199
|
}
|
|
@@ -169,7 +201,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
169
201
|
try {
|
|
170
202
|
const body = { customerId };
|
|
171
203
|
if (quoteId) body.quoteId = quoteId;
|
|
172
|
-
const result = await apiJson("/api/cpq/quotes", {
|
|
204
|
+
const result = await apiJson(t, "/api/cpq/quotes", {
|
|
173
205
|
method: "POST",
|
|
174
206
|
headers: { "Content-Type": "application/json" },
|
|
175
207
|
body: JSON.stringify(body)
|
|
@@ -186,7 +218,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
186
218
|
let cancelled = false;
|
|
187
219
|
async function load() {
|
|
188
220
|
try {
|
|
189
|
-
const result = await apiJson(`/api/cpq/quotes/${cpqConfigId}`);
|
|
221
|
+
const result = await apiJson(t, `/api/cpq/quotes/${cpqConfigId}`);
|
|
190
222
|
if (!cancelled) setCpqQuote(result);
|
|
191
223
|
} catch (err) {
|
|
192
224
|
if (!cancelled) setError(err.message);
|
|
@@ -198,11 +230,11 @@ function CpqQuoteDetailPage(props) {
|
|
|
198
230
|
return () => {
|
|
199
231
|
cancelled = true;
|
|
200
232
|
};
|
|
201
|
-
}, [cpqConfigId, isNew, searchParams, router]);
|
|
233
|
+
}, [cpqConfigId, isNew, searchParams, router, t]);
|
|
202
234
|
const loadOfferings = async () => {
|
|
203
235
|
setOfferingsLoading(true);
|
|
204
236
|
try {
|
|
205
|
-
const result = await apiJson("/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
|
|
237
|
+
const result = await apiJson(t, "/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
|
|
206
238
|
setOfferings(result.items ?? []);
|
|
207
239
|
} catch {
|
|
208
240
|
setOfferings([]);
|
|
@@ -214,7 +246,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
214
246
|
try {
|
|
215
247
|
const params = new URLSearchParams({ offeringId });
|
|
216
248
|
if (Object.keys(currentConfig).length > 0) params.set("configuration", JSON.stringify(currentConfig));
|
|
217
|
-
const result = await apiJson(`/api/cpq/quotes/attributes?${params}`);
|
|
249
|
+
const result = await apiJson(t, `/api/cpq/quotes/attributes?${params}`);
|
|
218
250
|
setAttributes(result.attributes ?? []);
|
|
219
251
|
} catch {
|
|
220
252
|
setAttributes([]);
|
|
@@ -231,7 +263,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
231
263
|
const body = { offeringId: selectedOffering.id, configuration: config, quantity };
|
|
232
264
|
if (addingToParentLineId) body.parentLineId = addingToParentLineId;
|
|
233
265
|
if (tid) body.targetSubscriptionId = tid;
|
|
234
|
-
lastResult = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items`, {
|
|
266
|
+
lastResult = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/items`, {
|
|
235
267
|
method: "POST",
|
|
236
268
|
headers: { "Content-Type": "application/json" },
|
|
237
269
|
body: JSON.stringify(body)
|
|
@@ -251,7 +283,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
251
283
|
try {
|
|
252
284
|
setSubmitting(true);
|
|
253
285
|
setError(null);
|
|
254
|
-
const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, {
|
|
286
|
+
const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, {
|
|
255
287
|
method: "PUT",
|
|
256
288
|
headers: { "Content-Type": "application/json" },
|
|
257
289
|
body: JSON.stringify({ configuration: config, quantity })
|
|
@@ -265,22 +297,19 @@ function CpqQuoteDetailPage(props) {
|
|
|
265
297
|
setSubmitting(false);
|
|
266
298
|
}
|
|
267
299
|
};
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
setError(err.message);
|
|
276
|
-
}
|
|
277
|
-
};
|
|
300
|
+
const lineRemoval = useArcLineRemoval({
|
|
301
|
+
quote: isNew ? null : cpqQuote,
|
|
302
|
+
quoteType: arcQuoteType,
|
|
303
|
+
onQuote: setCpqQuote,
|
|
304
|
+
onError: setError
|
|
305
|
+
});
|
|
306
|
+
const removeItem = lineRemoval.remove;
|
|
278
307
|
const recalculate = async () => {
|
|
279
308
|
if (!cpqQuote) return;
|
|
280
309
|
try {
|
|
281
310
|
setSubmitting(true);
|
|
282
311
|
setError(null);
|
|
283
|
-
const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/recalculate`, {
|
|
312
|
+
const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/recalculate`, {
|
|
284
313
|
method: "POST",
|
|
285
314
|
headers: { "Content-Type": "application/json" },
|
|
286
315
|
body: JSON.stringify({ save: true })
|
|
@@ -297,7 +326,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
297
326
|
try {
|
|
298
327
|
setConverting(true);
|
|
299
328
|
setError(null);
|
|
300
|
-
const result = await apiJson("/api/cpq/orders", {
|
|
329
|
+
const result = await apiJson(t, "/api/cpq/orders", {
|
|
301
330
|
method: "POST",
|
|
302
331
|
headers: { "Content-Type": "application/json" },
|
|
303
332
|
body: JSON.stringify({ quoteId: cpqQuote.id })
|
|
@@ -314,7 +343,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
314
343
|
try {
|
|
315
344
|
setTransitioning(true);
|
|
316
345
|
setError(null);
|
|
317
|
-
const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/status`, {
|
|
346
|
+
const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/status`, {
|
|
318
347
|
method: "POST",
|
|
319
348
|
headers: { "Content-Type": "application/json" },
|
|
320
349
|
body: JSON.stringify({ targetStatus })
|
|
@@ -332,7 +361,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
332
361
|
setDeleting(true);
|
|
333
362
|
setError(null);
|
|
334
363
|
setShowDeleteConfirm(false);
|
|
335
|
-
await apiJson(`/api/cpq/quotes/${cpqQuote.id}`, { method: "DELETE" });
|
|
364
|
+
await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}`, { method: "DELETE" });
|
|
336
365
|
router.push("/backend/cpq/quotes");
|
|
337
366
|
} catch (err) {
|
|
338
367
|
setError(err.message);
|
|
@@ -350,7 +379,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
350
379
|
};
|
|
351
380
|
const startAddOffering = () => {
|
|
352
381
|
resetConfigState();
|
|
353
|
-
setAddingTargetSubIds(arcTargets.map((
|
|
382
|
+
setAddingTargetSubIds(arcTargets.map((t2) => t2.subscriptionId));
|
|
354
383
|
loadOfferings();
|
|
355
384
|
setView("add-offering");
|
|
356
385
|
};
|
|
@@ -366,7 +395,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
366
395
|
setAddingTargetSubIds(inherited ? [inherited] : []);
|
|
367
396
|
if (offeringId) {
|
|
368
397
|
try {
|
|
369
|
-
const result = await apiJson("/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
|
|
398
|
+
const result = await apiJson(t, "/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
|
|
370
399
|
const match = (result.items ?? []).find((o) => o.id === offeringId);
|
|
371
400
|
if (match) {
|
|
372
401
|
selectOffering(match);
|
|
@@ -402,24 +431,28 @@ function CpqQuoteDetailPage(props) {
|
|
|
402
431
|
if (loading) {
|
|
403
432
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-12 justify-center text-sm text-muted-foreground", children: [
|
|
404
433
|
/* @__PURE__ */ jsx(Spinner, {}),
|
|
405
|
-
"
|
|
434
|
+
" ",
|
|
435
|
+
t("cpq.quotes.detail.loading", "Loading\u2026")
|
|
406
436
|
] });
|
|
407
437
|
}
|
|
408
438
|
if (!cpqQuote) {
|
|
409
439
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
410
|
-
/* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/quotes"), className: "text-sm text-muted-foreground hover:text-foreground", children: "\u2190 Back to Quotes" }),
|
|
440
|
+
/* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/quotes"), className: "text-sm text-muted-foreground hover:text-foreground", children: t("cpq.quotes.detail.backToQuotes", "\u2190 Back to Quotes") }),
|
|
411
441
|
error && /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
|
|
412
|
-
/* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "Quote not found." })
|
|
442
|
+
/* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.quotes.detail.notFound", "Quote not found.") })
|
|
413
443
|
] });
|
|
414
444
|
}
|
|
415
445
|
const { pricingSummary: summary, currencyCode: currency } = cpqQuote;
|
|
446
|
+
const arcDelta = arcQuoteType === "amend" || arcQuoteType === "renew" ? arcRecurringDelta(cpqQuote.lines) : null;
|
|
416
447
|
const arcEditable = ["new", "incomplete", "ready"].includes(cpqQuote.cpqStatus);
|
|
417
|
-
const isMergeRenew = arcQuoteType === "renew" && arcTargets.length >= 2 && arcTargets.every((
|
|
448
|
+
const isMergeRenew = arcQuoteType === "renew" && arcTargets.length >= 2 && arcTargets.every((t2) => t2.mergeAction === "absorb");
|
|
418
449
|
const arcTargetCodeMap = /* @__PURE__ */ new Map();
|
|
419
|
-
for (const
|
|
420
|
-
if (
|
|
450
|
+
for (const t2 of arcTargets) {
|
|
451
|
+
if (t2.subscription?.code) arcTargetCodeMap.set(t2.subscriptionId, t2.subscription.code);
|
|
421
452
|
}
|
|
422
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
453
|
+
return /* @__PURE__ */ jsx(PriceAdjustmentProvider, { value: priceAdjustments.ui, children: /* @__PURE__ */ jsx(ArcLineRemovalProvider, { value: lineRemoval.ui, children: /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
454
|
+
priceAdjustments.elements,
|
|
455
|
+
lineRemoval.ConfirmDialogElement,
|
|
423
456
|
arcDrawerOpen && /* @__PURE__ */ jsx(
|
|
424
457
|
ArcQuoteConfigurator,
|
|
425
458
|
{
|
|
@@ -434,7 +467,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
434
467
|
onChanged: async () => {
|
|
435
468
|
await loadArcTargets();
|
|
436
469
|
try {
|
|
437
|
-
const fresh = await apiJson(`/api/cpq/quotes/${cpqQuote.id}`);
|
|
470
|
+
const fresh = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}`);
|
|
438
471
|
setCpqQuote(fresh);
|
|
439
472
|
} catch {
|
|
440
473
|
}
|
|
@@ -446,13 +479,13 @@ function CpqQuoteDetailPage(props) {
|
|
|
446
479
|
{
|
|
447
480
|
mode: "detail",
|
|
448
481
|
backHref: "/backend/cpq/quotes",
|
|
449
|
-
backLabel: "Back to CPQ Quotes",
|
|
450
|
-
entityTypeLabel: "CPQ Quote",
|
|
451
|
-
title: cpqQuote.quoteNumber || cpqQuote.customerName || "Untitled",
|
|
482
|
+
backLabel: t("cpq.quotes.detail.backLabel", "Back to CPQ Quotes"),
|
|
483
|
+
entityTypeLabel: t("cpq.quotes.detail.entityType", "CPQ Quote"),
|
|
484
|
+
title: cpqQuote.quoteNumber || cpqQuote.customerName || t("cpq.quotes.detail.untitled", "Untitled"),
|
|
452
485
|
statusBadge: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
|
|
453
486
|
/* @__PURE__ */ jsx(Tag, { variant: quoteCpqStatusMap[cpqQuote.cpqStatus] ?? "neutral", dot: true, children: formatStatusLabel(cpqQuote.cpqStatus) }),
|
|
454
|
-
arcQuoteType !== "new" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "uppercase", children: arcQuoteType }),
|
|
455
|
-
cpqQuote.convertedOrderId && /* @__PURE__ */ jsx(Tag, { variant: "success", dot: true, children: "Converted" })
|
|
487
|
+
arcQuoteType !== "new" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "uppercase", children: quoteTypeLabel(t, arcQuoteType) }),
|
|
488
|
+
cpqQuote.convertedOrderId && /* @__PURE__ */ jsx(Tag, { variant: "success", dot: true, children: t("cpq.quotes.detail.converted", "Converted") })
|
|
456
489
|
] }),
|
|
457
490
|
actionsContent: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
458
491
|
/* @__PURE__ */ jsxs(
|
|
@@ -463,7 +496,7 @@ function CpqQuoteDetailPage(props) {
|
|
|
463
496
|
onClick: () => router.push(`/backend/sales/quotes/${cpqQuote.quoteId}`),
|
|
464
497
|
children: [
|
|
465
498
|
/* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5" }),
|
|
466
|
-
"View Sales Quote"
|
|
499
|
+
t("cpq.quotes.detail.viewSalesQuote", "View Sales Quote")
|
|
467
500
|
]
|
|
468
501
|
}
|
|
469
502
|
),
|
|
@@ -474,27 +507,29 @@ function CpqQuoteDetailPage(props) {
|
|
|
474
507
|
onClick: () => router.push(`/backend/cpq/orders/${cpqQuote.convertedOrderId}`),
|
|
475
508
|
children: [
|
|
476
509
|
/* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
|
|
477
|
-
"Go to order"
|
|
510
|
+
t("cpq.quotes.detail.goToOrder", "Go to order")
|
|
478
511
|
]
|
|
479
512
|
}
|
|
480
513
|
) : view !== "summary" ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => {
|
|
481
514
|
resetConfigState();
|
|
482
515
|
setView("summary");
|
|
483
|
-
}, children: "Cancel" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
516
|
+
}, children: t("cpq.common.cancel", "Cancel") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
484
517
|
/* @__PURE__ */ jsxs(Button, { type: "button", onClick: startAddOffering, children: [
|
|
485
518
|
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
486
|
-
"Add Offering"
|
|
519
|
+
t("cpq.quotes.detail.addOffering", "Add Offering")
|
|
487
520
|
] }),
|
|
488
|
-
arcEditable && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setArcDrawerOpen(true), children: "Modify Subscription" }),
|
|
521
|
+
arcEditable && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setArcDrawerOpen(true), children: t("cpq.quotes.detail.modifySubscription", "Modify Subscription") }),
|
|
489
522
|
cpqQuote.cpqStatus === "accepted" && /* @__PURE__ */ jsxs(Button, { type: "button", onClick: convertToOrder, disabled: converting, children: [
|
|
490
523
|
converting ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
|
|
491
|
-
"Convert to Order"
|
|
524
|
+
t("cpq.quotes.detail.convertToOrder", "Convert to Order")
|
|
492
525
|
] }),
|
|
493
526
|
/* @__PURE__ */ jsx(
|
|
494
527
|
MoreActionsDropdown,
|
|
495
528
|
{
|
|
496
529
|
items: buildMoreActions({
|
|
530
|
+
t,
|
|
497
531
|
recalculate,
|
|
532
|
+
canRecalculate: canRecalculateQuote(grantedFeatures, cpqQuote.cpqStatus),
|
|
498
533
|
submitting,
|
|
499
534
|
deletable: !["accepted", "rejected", "cancelled"].includes(cpqQuote.cpqStatus),
|
|
500
535
|
onDelete: () => setShowDeleteConfirm(true),
|
|
@@ -516,89 +551,69 @@ function CpqQuoteDetailPage(props) {
|
|
|
516
551
|
statusMap: quoteCpqStatusMap,
|
|
517
552
|
onTransition: transitionStatus,
|
|
518
553
|
disabled: transitioning,
|
|
519
|
-
ariaLabel: "Quote status path"
|
|
554
|
+
ariaLabel: t("cpq.quotes.detail.statusPath", "Quote status path")
|
|
520
555
|
}
|
|
521
556
|
),
|
|
522
557
|
transitioning && /* @__PURE__ */ jsx(Spinner, {})
|
|
523
558
|
] }),
|
|
524
559
|
error && /* @__PURE__ */ jsx(ErrorBanner, { message: error, onDismiss: () => setError(null) }),
|
|
525
560
|
arcQuoteType !== "new" && arcTargets.length > 0 && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-brand-violet/30 bg-brand-violet/10 px-4 py-3 text-sm text-brand-violet", children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
|
|
526
|
-
/* @__PURE__ */ jsx("span", { className: "font-medium uppercase", children: arcQuoteType }),
|
|
561
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium uppercase", children: quoteTypeLabel(t, arcQuoteType) }),
|
|
527
562
|
" \u2022 ",
|
|
528
|
-
arcTargets.length,
|
|
529
|
-
" target",
|
|
530
|
-
arcTargets.length === 1 ? "" : "s",
|
|
531
|
-
":",
|
|
563
|
+
arcTargets.length === 1 ? t("cpq.quotes.detail.arcTargets.one", "{count} target:", { count: arcTargets.length }) : t("cpq.quotes.detail.arcTargets.other", "{count} targets:", { count: arcTargets.length }),
|
|
532
564
|
" ",
|
|
533
|
-
arcTargets.map((
|
|
565
|
+
arcTargets.map((t2, i) => /* @__PURE__ */ jsxs("span", { children: [
|
|
534
566
|
i > 0 && ", ",
|
|
535
567
|
/* @__PURE__ */ jsx(
|
|
536
568
|
"button",
|
|
537
569
|
{
|
|
538
|
-
onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${
|
|
570
|
+
onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${t2.subscriptionId}`),
|
|
539
571
|
className: "text-brand-violet hover:underline",
|
|
540
|
-
children:
|
|
572
|
+
children: t2.subscription?.code ?? t2.subscriptionId.slice(0, 8)
|
|
541
573
|
}
|
|
542
574
|
)
|
|
543
|
-
] },
|
|
544
|
-
isMergeRenew && /* @__PURE__ */ jsx("span", { className: "ml-2 text-status-warning-text font-medium", children: "\u2022 Merging into a new contract" })
|
|
575
|
+
] }, t2.id)),
|
|
576
|
+
isMergeRenew && /* @__PURE__ */ jsx("span", { className: "ml-2 text-status-warning-text font-medium", children: t("cpq.quotes.detail.mergingRenew", "\u2022 Merging into a new contract") })
|
|
545
577
|
] }) }) }),
|
|
546
578
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3", children: [
|
|
547
|
-
/* @__PURE__ */ jsx(MetaCard, { label: "Customer", children: cpqQuote.customerName ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.customerName }) : /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
|
|
579
|
+
/* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.customer", "Customer"), children: cpqQuote.customerName ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.customerName }) : /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
|
|
548
580
|
cpqQuote.customerId.slice(0, 8),
|
|
549
581
|
"\u2026"
|
|
550
582
|
] }) }),
|
|
551
|
-
/* @__PURE__ */ jsx(MetaCard, { label: "Version", children: /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
583
|
+
/* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.version", "Version"), children: /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
|
|
552
584
|
"v",
|
|
553
585
|
cpqQuote.version
|
|
554
586
|
] }) }),
|
|
555
|
-
/* @__PURE__ */ jsx(MetaCard, { label: "Currency", children: /* @__PURE__ */ jsx("span", { className: "font-medium", children: currency }) })
|
|
587
|
+
/* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.currency", "Currency"), children: /* @__PURE__ */ jsx("span", { className: "font-medium", children: currency }) })
|
|
556
588
|
] }),
|
|
557
589
|
showDeleteConfirm && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setShowDeleteConfirm(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-lg border bg-card shadow-xl", onClick: (e) => e.stopPropagation(), children: [
|
|
558
|
-
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Delete Quote" }) }),
|
|
559
|
-
/* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Are you sure you want to delete this quote? This will soft-delete the quote and all its line items." }) }),
|
|
590
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("cpq.quotes.detail.deleteConfirm.title", "Delete Quote") }) }),
|
|
591
|
+
/* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("cpq.quotes.detail.deleteConfirm.text", "Are you sure you want to delete this quote? This will soft-delete the quote and all its line items.") }) }),
|
|
560
592
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-4 py-3 border-t", children: [
|
|
561
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setShowDeleteConfirm(false), children: "Cancel" }),
|
|
593
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setShowDeleteConfirm(false), children: t("cpq.common.cancel", "Cancel") }),
|
|
562
594
|
/* @__PURE__ */ jsxs(Button, { type: "button", variant: "destructive", onClick: deleteQuote, disabled: deleting, children: [
|
|
563
595
|
deleting ? /* @__PURE__ */ jsx(Spinner, {}) : null,
|
|
564
|
-
"
|
|
596
|
+
" ",
|
|
597
|
+
t("cpq.common.delete", "Delete")
|
|
565
598
|
] })
|
|
566
599
|
] })
|
|
567
600
|
] }) }),
|
|
568
601
|
view === "summary" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
569
|
-
/* @__PURE__ */
|
|
570
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-3", children: "Pricing Summary" }),
|
|
571
|
-
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-4", children: [
|
|
572
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
573
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Non-Recurring (NRC)" }),
|
|
574
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.nrcTotal, currency) })
|
|
575
|
-
] }),
|
|
576
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
577
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Monthly Recurring (MRC)" }),
|
|
578
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.mrcTotal, currency) })
|
|
579
|
-
] }),
|
|
580
|
-
summary.usageTotalEstimated != null && /* @__PURE__ */ jsxs("div", { children: [
|
|
581
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Usage (est.)" }),
|
|
582
|
-
/* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.usageTotalEstimated, currency) })
|
|
583
|
-
] }),
|
|
584
|
-
summary.usageCharges.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
585
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Usage Charges" }),
|
|
586
|
-
summary.usageCharges.map((uc, i) => /* @__PURE__ */ jsxs("p", { className: "text-xs mt-0.5", children: [
|
|
587
|
-
uc.chargeName,
|
|
588
|
-
": ",
|
|
589
|
-
fmt(uc.unitPrice, currency),
|
|
590
|
-
"/unit"
|
|
591
|
-
] }, i))
|
|
592
|
-
] })
|
|
593
|
-
] })
|
|
594
|
-
] }),
|
|
602
|
+
/* @__PURE__ */ jsx(PricingSummaryCard, { summary, currency, arcDelta }),
|
|
595
603
|
!cpqQuote.validationResult.valid && /* @__PURE__ */ jsxs(Alert, { variant: "warning", children: [
|
|
596
|
-
/* @__PURE__ */ jsx(AlertTitle, { children: "Validation Issues" }),
|
|
604
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: t("cpq.quotes.detail.validationIssues", "Validation Issues") }),
|
|
597
605
|
/* @__PURE__ */ jsx(AlertDescription, { children: /* @__PURE__ */ jsx("span", { className: "text-xs space-y-0.5 block", children: cpqQuote.validationResult.errors.map((e, i) => /* @__PURE__ */ jsxs("span", { className: "block", children: [
|
|
598
606
|
"\u2014 ",
|
|
599
607
|
e.message
|
|
600
608
|
] }, i)) }) })
|
|
601
609
|
] }),
|
|
610
|
+
arcQuoteType === "amend" && cpqQuote.lines.some((l) => l.action !== "cancel" && l.configuration?._arcRepriced === true) && /* @__PURE__ */ jsxs(Alert, { status: "information", style: "lighter", children: [
|
|
611
|
+
/* @__PURE__ */ jsx(AlertTitle, { children: t("cpq.quotes.arc.repricedTitle", "Some existing items use the current catalog price") }),
|
|
612
|
+
/* @__PURE__ */ jsx(AlertDescription, { children: t(
|
|
613
|
+
"cpq.quotes.arc.repricedDescription",
|
|
614
|
+
"Reconfigured lines were re-priced at today's catalog price. Negotiated prices from the subscription are not carried over to reconfigured lines yet."
|
|
615
|
+
) })
|
|
616
|
+
] }),
|
|
602
617
|
/* @__PURE__ */ jsx(
|
|
603
618
|
QuoteLineTree,
|
|
604
619
|
{
|
|
@@ -615,12 +630,24 @@ function CpqQuoteDetailPage(props) {
|
|
|
615
630
|
pricingDetail?.lineIdx === lineIdx && pricingDetail?.chargeIdx === chargeIdx ? null : { lineIdx, chargeIdx }
|
|
616
631
|
)
|
|
617
632
|
}
|
|
633
|
+
),
|
|
634
|
+
/* @__PURE__ */ jsx(
|
|
635
|
+
PriceAdjustmentsPanel,
|
|
636
|
+
{
|
|
637
|
+
quoteId: cpqQuote.id,
|
|
638
|
+
items: priceAdjustments.items,
|
|
639
|
+
total: priceAdjustments.total,
|
|
640
|
+
lines: cpqQuote.lines,
|
|
641
|
+
currency,
|
|
642
|
+
errorsById: priceAdjustments.errorsById
|
|
643
|
+
}
|
|
618
644
|
)
|
|
619
645
|
] }),
|
|
620
646
|
view === "add-offering" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
621
647
|
addingToParentLineId && /* @__PURE__ */ jsxs(Alert, { variant: "info", children: [
|
|
622
|
-
"Adding component to bundle:
|
|
623
|
-
|
|
648
|
+
t("cpq.quotes.detail.addingComponentTo", "Adding component to bundle:"),
|
|
649
|
+
" ",
|
|
650
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.lines.find((l) => l.lineId === addingToParentLineId)?.offeringName ?? t("cpq.quotes.detail.bundleFallback", "Bundle") })
|
|
624
651
|
] }),
|
|
625
652
|
/* @__PURE__ */ jsx(
|
|
626
653
|
OfferingBrowser,
|
|
@@ -636,14 +663,14 @@ function CpqQuoteDetailPage(props) {
|
|
|
636
663
|
view === "configure" && selectedOffering && /* @__PURE__ */ jsx(
|
|
637
664
|
ConfigurePanel,
|
|
638
665
|
{
|
|
639
|
-
title: addingToParentLineId ?
|
|
666
|
+
title: addingToParentLineId ? t("cpq.quotes.detail.configure.addComponentTitle", "Add Component: {name}", { name: selectedOffering.name }) : t("cpq.quotes.detail.configure.configureTitle", "Configure: {name}", { name: selectedOffering.name }),
|
|
640
667
|
attributes,
|
|
641
668
|
config,
|
|
642
669
|
quantity,
|
|
643
|
-
arcTargetOptions: arcTargets.length >= 2 && !addingToParentLineId ? arcTargets.map((
|
|
644
|
-
subscriptionId:
|
|
645
|
-
code:
|
|
646
|
-
name:
|
|
670
|
+
arcTargetOptions: arcTargets.length >= 2 && !addingToParentLineId ? arcTargets.map((t2) => ({
|
|
671
|
+
subscriptionId: t2.subscriptionId,
|
|
672
|
+
code: t2.subscription?.code ?? t2.subscriptionId.slice(0, 8),
|
|
673
|
+
name: t2.subscription?.name ?? null
|
|
647
674
|
})) : null,
|
|
648
675
|
arcSelectedTargetIds: addingTargetSubIds,
|
|
649
676
|
onArcTargetToggle: (subId) => {
|
|
@@ -658,14 +685,16 @@ function CpqQuoteDetailPage(props) {
|
|
|
658
685
|
},
|
|
659
686
|
onQuantityChange: setQuantity,
|
|
660
687
|
onSubmit: addItem,
|
|
661
|
-
submitLabel: "Add to Quote",
|
|
688
|
+
submitLabel: t("cpq.quotes.detail.configure.addToQuote", "Add to Quote"),
|
|
662
689
|
submitting
|
|
663
690
|
}
|
|
664
691
|
),
|
|
665
692
|
view === "edit" && editingLineId && /* @__PURE__ */ jsx(
|
|
666
693
|
ConfigurePanel,
|
|
667
694
|
{
|
|
668
|
-
title:
|
|
695
|
+
title: t("cpq.quotes.detail.configure.editTitle", "Edit: {name}", {
|
|
696
|
+
name: cpqQuote.lines.find((l) => l.lineId === editingLineId)?.offeringName ?? t("cpq.quotes.detail.configure.lineFallback", "Line")
|
|
697
|
+
}),
|
|
669
698
|
attributes,
|
|
670
699
|
config,
|
|
671
700
|
quantity,
|
|
@@ -681,13 +710,14 @@ function CpqQuoteDetailPage(props) {
|
|
|
681
710
|
},
|
|
682
711
|
onQuantityChange: setQuantity,
|
|
683
712
|
onSubmit: () => updateItem(editingLineId),
|
|
684
|
-
submitLabel: "Update Line",
|
|
713
|
+
submitLabel: t("cpq.quotes.detail.configure.updateLine", "Update Line"),
|
|
685
714
|
submitting
|
|
686
715
|
}
|
|
687
716
|
)
|
|
688
|
-
] });
|
|
717
|
+
] }) }) });
|
|
689
718
|
}
|
|
690
719
|
function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCodes, onToggleLine, onEditLine, onRemoveLine, onAddComponent, onTogglePricingDetail }) {
|
|
720
|
+
const t = useT();
|
|
691
721
|
const [bundleTrees, setBundleTrees] = React.useState(/* @__PURE__ */ new Map());
|
|
692
722
|
const [loadingTrees, setLoadingTrees] = React.useState(/* @__PURE__ */ new Set());
|
|
693
723
|
const rootLines = lines.filter((l) => !l.parentLineId);
|
|
@@ -703,7 +733,7 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
|
|
|
703
733
|
if (bundleTrees.has(offeringId) || loadingTrees.has(offeringId)) return;
|
|
704
734
|
setLoadingTrees((prev) => new Set(prev).add(offeringId));
|
|
705
735
|
try {
|
|
706
|
-
const result = await apiJson(`/api/cpq/bundle-tree?offeringId=${offeringId}`);
|
|
736
|
+
const result = await apiJson(t, `/api/cpq/bundle-tree?offeringId=${offeringId}`);
|
|
707
737
|
setBundleTrees((prev) => new Map(prev).set(offeringId, result.tree));
|
|
708
738
|
} catch {
|
|
709
739
|
} finally {
|
|
@@ -713,10 +743,10 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
|
|
|
713
743
|
return n;
|
|
714
744
|
});
|
|
715
745
|
}
|
|
716
|
-
}, [bundleTrees, loadingTrees]);
|
|
746
|
+
}, [bundleTrees, loadingTrees, t]);
|
|
717
747
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
718
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: "Line Items" }),
|
|
719
|
-
lines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: 'No items configured yet. Click "Add Offering" to start.' }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: rootLines.map((line) => {
|
|
748
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: t("cpq.quotes.detail.lineItems", "Line Items") }),
|
|
749
|
+
lines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: t("cpq.quotes.detail.noLines", 'No items configured yet. Click "Add Offering" to start.') }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: rootLines.map((line) => {
|
|
720
750
|
const lineIdx = lines.indexOf(line);
|
|
721
751
|
const isBundle = line.offeringType === "bundle";
|
|
722
752
|
const children = childrenByParent.get(line.lineId) ?? [];
|
|
@@ -770,10 +800,12 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
|
|
|
770
800
|
] });
|
|
771
801
|
}
|
|
772
802
|
function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency, expandedLines, pricingDetail, parentLineId, targetCodes, onToggleLine, onEditLine, onRemoveLine, onAddComponent, onTogglePricingDetail }) {
|
|
803
|
+
const t = useT();
|
|
773
804
|
if (treeLoading) {
|
|
774
805
|
return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/5 px-8 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
775
806
|
/* @__PURE__ */ jsx(Spinner, {}),
|
|
776
|
-
"
|
|
807
|
+
" ",
|
|
808
|
+
t("cpq.quotes.bundle.loadingStructure", "Loading bundle structure\u2026")
|
|
777
809
|
] }) });
|
|
778
810
|
}
|
|
779
811
|
if (!bundleTree || bundleTree.slots.length === 0) {
|
|
@@ -814,12 +846,13 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
|
|
|
814
846
|
return /* @__PURE__ */ jsxs("div", { className: "border-b last:border-b-0", children: [
|
|
815
847
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-6 py-2 bg-muted/20", children: [
|
|
816
848
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: slot.name }),
|
|
817
|
-
isRequired ? /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "text-[10px] px-1.5", children: "required" }) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "text-[10px] px-1.5", children: "optional" }),
|
|
849
|
+
isRequired ? /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "text-[10px] px-1.5", children: t("cpq.quotes.bundle.slotRequired", "required") }) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "text-[10px] px-1.5", children: t("cpq.quotes.bundle.slotOptional", "optional") }),
|
|
818
850
|
/* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
819
851
|
filledCount,
|
|
820
852
|
"/",
|
|
821
853
|
slot.cardinalityMax ?? "\u221E",
|
|
822
|
-
slot.cardinalityMin > 0 &&
|
|
854
|
+
slot.cardinalityMin > 0 && " ",
|
|
855
|
+
slot.cardinalityMin > 0 && t("cpq.quotes.bundle.slotMin", "(min {min})", { min: slot.cardinalityMin })
|
|
823
856
|
] }),
|
|
824
857
|
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
825
858
|
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: slot.components.map((comp) => {
|
|
@@ -830,7 +863,7 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
|
|
|
830
863
|
onClick: () => !isFilled && canAdd && onAddComponent(parentLineId, comp.childOffering.offeringId),
|
|
831
864
|
disabled: isFilled || !canAdd,
|
|
832
865
|
className: `inline-flex items-center gap-1 rounded border px-2 py-1 text-[11px] font-medium transition-colors ${isFilled ? "border-status-success-border bg-status-success-bg text-status-success-text cursor-default" : canAdd ? "border-border hover:border-primary hover:bg-primary/5 hover:text-primary cursor-pointer" : "border-border text-muted-foreground opacity-50 cursor-not-allowed"}`,
|
|
833
|
-
title: isFilled ? "Already added" : canAdd ?
|
|
866
|
+
title: isFilled ? t("cpq.quotes.bundle.alreadyAdded", "Already added") : canAdd ? t("cpq.quotes.bundle.addComponent", "Add {name}", { name: comp.childOffering.offeringName }) : t("cpq.quotes.bundle.slotFull", "Slot is full"),
|
|
834
867
|
children: [
|
|
835
868
|
isFilled ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }),
|
|
836
869
|
comp.childOffering.offeringName
|
|
@@ -889,229 +922,149 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
|
|
|
889
922
|
] });
|
|
890
923
|
}
|
|
891
924
|
function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBundle, childCount, indent, targetCodes, onToggle, onEdit, onRemove, onAddComponent, onTogglePricingDetail }) {
|
|
925
|
+
const t = useT();
|
|
926
|
+
const adjustmentUi = usePriceAdjustmentUi();
|
|
927
|
+
const removalUi = useArcLineRemovalUi();
|
|
928
|
+
const isRemoved = removalUi?.isRemoved(line) ?? false;
|
|
892
929
|
const paddingLeft = indent > 0 ? `${indent * 2 + 1}rem` : void 0;
|
|
930
|
+
const chargeActions = {
|
|
931
|
+
reasonLabels: adjustmentUi?.reasonLabels,
|
|
932
|
+
renderChargeActions: (charge) => /* @__PURE__ */ jsx(AdjustChargeAction, { line, charge }),
|
|
933
|
+
renderManualActions: (adjustmentId) => /* @__PURE__ */ jsx(ManualAdjustmentActions, { adjustmentId })
|
|
934
|
+
};
|
|
893
935
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
894
|
-
/* @__PURE__ */ jsxs("div", { className:
|
|
936
|
+
/* @__PURE__ */ jsxs("div", { className: `flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors ${isRemoved ? "bg-muted/20" : ""}`, style: paddingLeft ? { paddingLeft } : void 0, "data-testid": "cpq-quote-line", "data-removed": isRemoved ? "true" : void 0, children: [
|
|
895
937
|
/* @__PURE__ */ jsx("button", { onClick: onToggle, className: "flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx("svg", { className: `h-4 w-4 transition-transform ${isExpanded ? "rotate-90" : ""}`, fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) }) }),
|
|
896
938
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
897
939
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
898
940
|
indent > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: "\u2514" }),
|
|
899
|
-
isBundle && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-1.5 text-xs", children: "bundle" }),
|
|
900
|
-
/* @__PURE__ */ jsx("span", { className:
|
|
941
|
+
isBundle && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-1.5 text-xs", children: t("cpq.lines.bundle", "bundle") }),
|
|
942
|
+
/* @__PURE__ */ jsx("span", { className: `font-medium text-sm ${isRemoved ? "text-muted-foreground line-through" : ""}`, children: line.offeringName }),
|
|
901
943
|
line.quantity > 1 && /* @__PURE__ */ jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs font-medium", children: [
|
|
902
944
|
"\xD7",
|
|
903
945
|
line.quantity
|
|
904
946
|
] }),
|
|
905
|
-
/* @__PURE__ */ jsx(
|
|
947
|
+
/* @__PURE__ */ jsx(
|
|
948
|
+
LineActionBadge,
|
|
949
|
+
{
|
|
950
|
+
action: line.action,
|
|
951
|
+
label: isRemoved ? t("cpq.quotes.arc.lineRemoved", "remove") : void 0,
|
|
952
|
+
title: isRemoved ? t("cpq.quotes.arc.lineRemovedHint", "This item is cancelled on the subscription when the order is activated") : void 0
|
|
953
|
+
}
|
|
954
|
+
),
|
|
955
|
+
!isRemoved && line.configuration?._arcPriceFrozen === true && /* @__PURE__ */ jsx(
|
|
956
|
+
Tag,
|
|
957
|
+
{
|
|
958
|
+
variant: "neutral",
|
|
959
|
+
className: "px-1.5 text-xs",
|
|
960
|
+
title: t(
|
|
961
|
+
"cpq.quotes.arc.priceKeptHint",
|
|
962
|
+
"Price kept from the subscription \u2014 it changes only if you reconfigure this line"
|
|
963
|
+
),
|
|
964
|
+
children: t("cpq.quotes.arc.priceKept", "price kept")
|
|
965
|
+
}
|
|
966
|
+
),
|
|
906
967
|
targetCodes.size >= 2 && line.targetSubscriptionId && targetCodes.get(line.targetSubscriptionId) && /* @__PURE__ */ jsxs(
|
|
907
968
|
Tag,
|
|
908
969
|
{
|
|
909
970
|
variant: "brand",
|
|
910
971
|
className: "px-1.5 text-xs",
|
|
911
|
-
title:
|
|
972
|
+
title: t("cpq.quotes.detail.actsOnSubscription", "Acts on subscription {code}", { code: targetCodes.get(line.targetSubscriptionId) ?? "" }),
|
|
912
973
|
children: [
|
|
913
974
|
"\u2192 ",
|
|
914
975
|
targetCodes.get(line.targetSubscriptionId)
|
|
915
976
|
]
|
|
916
977
|
}
|
|
917
978
|
),
|
|
918
|
-
line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: "configured" }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: "incomplete" }),
|
|
919
|
-
isBundle && /* @__PURE__ */
|
|
920
|
-
childCount,
|
|
921
|
-
" component",
|
|
922
|
-
childCount !== 1 ? "s" : ""
|
|
923
|
-
] })
|
|
979
|
+
isRemoved ? null : line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: t("cpq.quotes.detail.lineConfigured", "configured") }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: t("cpq.quotes.detail.lineIncomplete", "incomplete") }),
|
|
980
|
+
isBundle && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: childCount !== 1 ? t("cpq.lines.componentCount.other", "{count} components", { count: childCount }) : t("cpq.lines.componentCount.one", "{count} component", { count: childCount }) })
|
|
924
981
|
] }),
|
|
925
982
|
line.validationErrors && line.validationErrors.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-0.5", children: line.validationErrors[0].message })
|
|
926
983
|
] }),
|
|
927
984
|
/* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right text-xs space-y-0.5", children: [
|
|
985
|
+
isRemoved && line.arcSource && line.arcSource.mrcAmount > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
986
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
987
|
+
t("cpq.lines.mrc", "MRC"),
|
|
988
|
+
" "
|
|
989
|
+
] }),
|
|
990
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground line-through", children: fmt(line.arcSource.mrcAmount, currency) })
|
|
991
|
+
] }),
|
|
928
992
|
line.nrcTotal > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
929
|
-
/* @__PURE__ */
|
|
993
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
994
|
+
t("cpq.lines.nrc", "NRC"),
|
|
995
|
+
" "
|
|
996
|
+
] }),
|
|
930
997
|
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.nrcTotal, currency) })
|
|
931
998
|
] }),
|
|
932
999
|
line.mrcTotal > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
933
|
-
/* @__PURE__ */
|
|
1000
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
1001
|
+
t("cpq.lines.mrc", "MRC"),
|
|
1002
|
+
" "
|
|
1003
|
+
] }),
|
|
934
1004
|
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.mrcTotal, currency) })
|
|
935
1005
|
] })
|
|
936
1006
|
] }),
|
|
937
|
-
/* @__PURE__ */
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1007
|
+
isRemoved ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 shrink-0", children: /* @__PURE__ */ jsxs(
|
|
1008
|
+
Button,
|
|
1009
|
+
{
|
|
1010
|
+
type: "button",
|
|
1011
|
+
variant: "ghost",
|
|
1012
|
+
size: "sm",
|
|
1013
|
+
onClick: () => removalUi?.restore(line.lineId),
|
|
1014
|
+
title: t("cpq.quotes.arc.restoreHint", "Keep this item on the subscription"),
|
|
1015
|
+
"data-testid": "cpq-line-restore",
|
|
1016
|
+
children: [
|
|
1017
|
+
/* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" }),
|
|
1018
|
+
t("cpq.quotes.arc.restore", "Restore")
|
|
1019
|
+
]
|
|
1020
|
+
}
|
|
1021
|
+
) }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
1022
|
+
/* @__PURE__ */ jsx(DiscountLineAction, { line }),
|
|
1023
|
+
onAddComponent && /* @__PURE__ */ jsx("button", { onClick: onAddComponent, className: "rounded p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors", title: t("cpq.quotes.detail.addComponent", "Add Component"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }) }),
|
|
1024
|
+
/* @__PURE__ */ jsx("button", { onClick: onEdit, className: "rounded p-1.5 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors", title: t("cpq.common.edit", "Edit"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" }) }) }),
|
|
1025
|
+
/* @__PURE__ */ jsx("button", { onClick: onRemove, className: "rounded p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors", title: t("cpq.quotes.detail.removeLine", "Remove"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" }) }) })
|
|
941
1026
|
] })
|
|
942
1027
|
] }),
|
|
943
|
-
isExpanded && line.
|
|
944
|
-
isExpanded &&
|
|
945
|
-
isExpanded &&
|
|
1028
|
+
isExpanded && !isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail, ...chargeActions }),
|
|
1029
|
+
isExpanded && isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail, ...chargeActions }),
|
|
1030
|
+
isExpanded && line.arcSource && /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsx(ArcLineDiff, { line, currency }) }),
|
|
946
1031
|
isExpanded && line.charges.length === 0 && !line.arcSource && /* @__PURE__ */ jsx(LineSummaryFallback, { line, currency })
|
|
947
1032
|
] });
|
|
948
1033
|
}
|
|
949
|
-
function ArcLineDiff({ line, currency }) {
|
|
950
|
-
const src = line.arcSource;
|
|
951
|
-
if (!src) return null;
|
|
952
|
-
const isCancel = line.action === "cancel";
|
|
953
|
-
const beforeMrc = src.mrcAmount;
|
|
954
|
-
const beforeNrc = src.nrcAmount;
|
|
955
|
-
const beforeQty = src.quantity;
|
|
956
|
-
const afterMrc = isCancel ? 0 : line.mrcTotal;
|
|
957
|
-
const afterNrc = isCancel ? 0 : line.nrcTotal;
|
|
958
|
-
const afterQty = isCancel ? 0 : line.quantity;
|
|
959
|
-
const fmtVal = (v, asCurrency) => asCurrency ? fmt(v, currency) : String(v);
|
|
960
|
-
const rows = [
|
|
961
|
-
{ label: "Quantity", before: beforeQty, after: afterQty, asCurrency: false },
|
|
962
|
-
{ label: "MRC", before: beforeMrc, after: afterMrc, asCurrency: true },
|
|
963
|
-
{ label: "NRC", before: beforeNrc, after: afterNrc, asCurrency: true }
|
|
964
|
-
];
|
|
965
|
-
return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/10 px-12 py-2 space-y-1.5", children: [
|
|
966
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Change Preview" }),
|
|
967
|
-
/* @__PURE__ */ jsx("div", { className: "rounded border bg-background overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
968
|
-
/* @__PURE__ */ jsx("thead", { className: "bg-muted/40", children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
969
|
-
/* @__PURE__ */ jsx("th", { className: "text-left px-3 py-1.5 font-medium" }),
|
|
970
|
-
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "Before" }),
|
|
971
|
-
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "After" }),
|
|
972
|
-
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "\u0394" })
|
|
973
|
-
] }) }),
|
|
974
|
-
/* @__PURE__ */ jsx("tbody", { children: rows.map((r) => {
|
|
975
|
-
const delta = r.after - r.before;
|
|
976
|
-
const showDelta = delta !== 0;
|
|
977
|
-
return /* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/40", children: [
|
|
978
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-muted-foreground", children: r.label }),
|
|
979
|
-
/* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-right font-mono", children: fmtVal(r.before, r.asCurrency) }),
|
|
980
|
-
/* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono ${isCancel ? "text-muted-foreground line-through" : ""}`, children: fmtVal(r.after, r.asCurrency) }),
|
|
981
|
-
/* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono font-medium ${showDelta ? delta > 0 ? "text-status-success-text" : "text-destructive" : "text-muted-foreground"}`, children: showDelta ? `${delta > 0 ? "+" : ""}${fmtVal(delta, r.asCurrency)}` : "\u2014" })
|
|
982
|
-
] }, r.label);
|
|
983
|
-
}) })
|
|
984
|
-
] }) }),
|
|
985
|
-
isCancel && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive italic", children: "Cancellation \u2014 item will be terminated on activation." })
|
|
986
|
-
] });
|
|
987
|
-
}
|
|
988
|
-
function ActionBadge({ action }) {
|
|
989
|
-
const variantByAction = {
|
|
990
|
-
add: "info",
|
|
991
|
-
modify: "warning",
|
|
992
|
-
cancel: "error"
|
|
993
|
-
};
|
|
994
|
-
return /* @__PURE__ */ jsx(Tag, { variant: variantByAction[action], className: "px-1.5 text-xs", children: action });
|
|
995
|
-
}
|
|
996
1034
|
function LineSummaryFallback({ line, currency }) {
|
|
1035
|
+
const t = useT();
|
|
997
1036
|
const hasTotals = line.nrcTotal > 0 || line.mrcTotal > 0;
|
|
998
1037
|
const mirroredFromItemId = line.configuration?._arcMirroredFromItemId ?? null;
|
|
999
1038
|
return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/10 px-12 py-2 text-xs text-muted-foreground space-y-1", children: [
|
|
1000
1039
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1001
|
-
"No detailed charge breakdown available for this line.",
|
|
1002
|
-
mirroredFromItemId && "
|
|
1040
|
+
t("cpq.quotes.detail.noBreakdown", "No detailed charge breakdown available for this line."),
|
|
1041
|
+
mirroredFromItemId && " ",
|
|
1042
|
+
mirroredFromItemId && t("cpq.orders.detail.mirroredItem", "Mirrored from existing subscription item.")
|
|
1003
1043
|
] }),
|
|
1004
1044
|
hasTotals && /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
|
|
1005
1045
|
line.nrcTotal > 0 && /* @__PURE__ */ jsxs("span", { children: [
|
|
1006
|
-
"
|
|
1046
|
+
t("cpq.lines.nrc", "NRC"),
|
|
1047
|
+
" ",
|
|
1007
1048
|
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-foreground", children: fmt(line.nrcTotal, currency) })
|
|
1008
1049
|
] }),
|
|
1009
1050
|
line.mrcTotal > 0 && /* @__PURE__ */ jsxs("span", { children: [
|
|
1010
|
-
"
|
|
1051
|
+
t("cpq.lines.mrc", "MRC"),
|
|
1052
|
+
" ",
|
|
1011
1053
|
/* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-foreground", children: fmt(line.mrcTotal, currency) })
|
|
1012
1054
|
] })
|
|
1013
1055
|
] })
|
|
1014
1056
|
] });
|
|
1015
1057
|
}
|
|
1016
|
-
function ChargeBreakdown({ line, lineIdx,
|
|
1017
|
-
return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
/* @__PURE__ */ jsx("tbody", { children: line.charges.map((c, i) => {
|
|
1027
|
-
const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length);
|
|
1028
|
-
const isDetailOpen = pricingDetail?.lineIdx === lineIdx && pricingDetail?.chargeIdx === i;
|
|
1029
|
-
return /* @__PURE__ */ jsxs(React.Fragment, { children: [
|
|
1030
|
-
/* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/50", children: [
|
|
1031
|
-
/* @__PURE__ */ jsx("td", { className: "py-1", children: c.chargeName }),
|
|
1032
|
-
/* @__PURE__ */ jsx("td", { className: "py-1 uppercase", children: c.chargeType }),
|
|
1033
|
-
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: fmt(c.unitPrice, currency) }),
|
|
1034
|
-
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.quantity ?? "\u2014" }),
|
|
1035
|
-
/* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.totalPrice != null ? fmt(c.totalPrice, currency) : c.note ?? "\u2014" }),
|
|
1036
|
-
/* @__PURE__ */ jsx("td", { className: "py-1 text-center", children: hasDetail && /* @__PURE__ */ jsx(
|
|
1037
|
-
"button",
|
|
1038
|
-
{
|
|
1039
|
-
onClick: () => onTogglePricingDetail(lineIdx, i),
|
|
1040
|
-
className: "inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
|
|
1041
|
-
title: "Show pricing breakdown",
|
|
1042
|
-
children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", className: "w-3.5 h-3.5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" }) })
|
|
1043
|
-
}
|
|
1044
|
-
) })
|
|
1045
|
-
] }),
|
|
1046
|
-
isDetailOpen && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 6, className: "pb-3 pt-1", children: /* @__PURE__ */ jsxs("div", { className: "rounded border bg-background p-3 space-y-3 text-xs", children: [
|
|
1047
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
1048
|
-
/* @__PURE__ */ jsxs("span", { className: "font-semibold text-sm", children: [
|
|
1049
|
-
"Pricing Details \u2014 ",
|
|
1050
|
-
c.chargeName
|
|
1051
|
-
] }),
|
|
1052
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: c.pricingMethod ?? "flat" })
|
|
1053
|
-
] }),
|
|
1054
|
-
c.breakdown?.tiers && c.breakdown.tiers.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1055
|
-
/* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: "Tier Breakdown" }),
|
|
1056
|
-
/* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
1057
|
-
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
1058
|
-
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Tier" }),
|
|
1059
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Range" }),
|
|
1060
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Price/Unit" }),
|
|
1061
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Qty" }),
|
|
1062
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Subtotal" })
|
|
1063
|
-
] }) }),
|
|
1064
|
-
/* @__PURE__ */ jsxs("tbody", { children: [
|
|
1065
|
-
c.breakdown.tiers.map((t) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
|
|
1066
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5", children: t.tier }),
|
|
1067
|
-
/* @__PURE__ */ jsxs("td", { className: "py-0.5 text-right font-mono", children: [
|
|
1068
|
-
t.from,
|
|
1069
|
-
"\u2013",
|
|
1070
|
-
t.to
|
|
1071
|
-
] }),
|
|
1072
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(t.pricePerUnit, currency) }),
|
|
1073
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: t.quantity }),
|
|
1074
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(t.subtotal, currency) })
|
|
1075
|
-
] }, t.tier)),
|
|
1076
|
-
/* @__PURE__ */ jsxs("tr", { className: "border-t font-medium", children: [
|
|
1077
|
-
/* @__PURE__ */ jsx("td", { colSpan: 4, className: "py-0.5 text-right", children: "Tiered Total" }),
|
|
1078
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(c.breakdown.tiers.reduce((s, t) => s + t.subtotal, 0), currency) })
|
|
1079
|
-
] })
|
|
1080
|
-
] })
|
|
1081
|
-
] })
|
|
1082
|
-
] }),
|
|
1083
|
-
c.adjustments && c.adjustments.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1084
|
-
/* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: "Applied Price Rules" }),
|
|
1085
|
-
/* @__PURE__ */ jsxs("table", { className: "w-full", children: [
|
|
1086
|
-
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
1087
|
-
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Rule" }),
|
|
1088
|
-
/* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Type" }),
|
|
1089
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Value" }),
|
|
1090
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Before" }),
|
|
1091
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "After" }),
|
|
1092
|
-
/* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Delta" })
|
|
1093
|
-
] }) }),
|
|
1094
|
-
/* @__PURE__ */ jsx("tbody", { children: c.adjustments.map((adj, ai) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
|
|
1095
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5", children: adj.ruleName }),
|
|
1096
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5", children: RULE_TYPE_LABELS[adj.ruleType] ?? adj.ruleType }),
|
|
1097
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmtRuleValue(adj, currency) }),
|
|
1098
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(adj.unitPriceBefore, currency) }),
|
|
1099
|
-
/* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(adj.unitPriceAfter, currency) }),
|
|
1100
|
-
/* @__PURE__ */ jsxs("td", { className: `py-0.5 text-right font-mono ${adj.delta < 0 ? "text-destructive" : adj.delta > 0 ? "text-status-success-text" : ""}`, children: [
|
|
1101
|
-
adj.delta > 0 ? "+" : "",
|
|
1102
|
-
fmt(adj.delta, currency)
|
|
1103
|
-
] })
|
|
1104
|
-
] }, ai)) })
|
|
1105
|
-
] })
|
|
1106
|
-
] }),
|
|
1107
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-1 border-t text-sm font-semibold", children: [
|
|
1108
|
-
/* @__PURE__ */ jsx("span", { children: "Final Total" }),
|
|
1109
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono", children: c.totalPrice != null ? fmt(c.totalPrice, currency) : "\u2014" })
|
|
1110
|
-
] })
|
|
1111
|
-
] }) }) })
|
|
1112
|
-
] }, i);
|
|
1113
|
-
}) })
|
|
1114
|
-
] }) });
|
|
1058
|
+
function ChargeBreakdown({ line, lineIdx, pricingDetail, onTogglePricingDetail, ...tableProps }) {
|
|
1059
|
+
return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsx(
|
|
1060
|
+
ChargeBreakdownTable,
|
|
1061
|
+
{
|
|
1062
|
+
charges: line.charges,
|
|
1063
|
+
openDetailIndex: pricingDetail?.lineIdx === lineIdx ? pricingDetail.chargeIdx : null,
|
|
1064
|
+
onToggleDetail: (chargeIdx) => onTogglePricingDetail(lineIdx, chargeIdx),
|
|
1065
|
+
...tableProps
|
|
1066
|
+
}
|
|
1067
|
+
) });
|
|
1115
1068
|
}
|
|
1116
1069
|
function Spinner() {
|
|
1117
1070
|
return /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", children: [
|
|
@@ -1120,6 +1073,7 @@ function Spinner() {
|
|
|
1120
1073
|
] });
|
|
1121
1074
|
}
|
|
1122
1075
|
function ErrorBanner({ message, onDismiss }) {
|
|
1076
|
+
const t = useT();
|
|
1123
1077
|
return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", className: "flex items-start gap-2", children: [
|
|
1124
1078
|
/* @__PURE__ */ jsx("span", { className: "flex-1", children: message }),
|
|
1125
1079
|
onDismiss && /* @__PURE__ */ jsx(
|
|
@@ -1127,27 +1081,28 @@ function ErrorBanner({ message, onDismiss }) {
|
|
|
1127
1081
|
{
|
|
1128
1082
|
onClick: onDismiss,
|
|
1129
1083
|
className: "text-destructive/70 hover:text-destructive text-sm font-bold",
|
|
1130
|
-
"aria-label": "Dismiss",
|
|
1084
|
+
"aria-label": t("cpq.common.dismiss", "Dismiss"),
|
|
1131
1085
|
children: "\xD7"
|
|
1132
1086
|
}
|
|
1133
1087
|
)
|
|
1134
1088
|
] });
|
|
1135
1089
|
}
|
|
1136
1090
|
function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect }) {
|
|
1091
|
+
const t = useT();
|
|
1137
1092
|
const filtered = offerings.filter((o) => {
|
|
1138
1093
|
if (!search.trim()) return true;
|
|
1139
1094
|
const q = search.toLowerCase();
|
|
1140
1095
|
return o.name.toLowerCase().includes(q) || o.code.toLowerCase().includes(q) || (o.description ?? "").toLowerCase().includes(q);
|
|
1141
1096
|
});
|
|
1142
1097
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
1143
|
-
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium px-1", children: "Select Product Offering" }),
|
|
1098
|
+
/* @__PURE__ */ jsx("h3", { className: "text-sm font-medium px-1", children: t("cpq.quotes.offeringBrowser.title", "Select Product Offering") }),
|
|
1144
1099
|
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
1145
1100
|
/* @__PURE__ */ jsx("svg", { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" }) }),
|
|
1146
1101
|
/* @__PURE__ */ jsx(
|
|
1147
1102
|
"input",
|
|
1148
1103
|
{
|
|
1149
1104
|
type: "text",
|
|
1150
|
-
placeholder: "Search offerings\u2026",
|
|
1105
|
+
placeholder: t("cpq.quotes.offeringBrowser.searchPlaceholder", "Search offerings\u2026"),
|
|
1151
1106
|
value: search,
|
|
1152
1107
|
onChange: (e) => onSearchChange(e.target.value),
|
|
1153
1108
|
className: "w-full rounded-md border bg-background pl-10 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50"
|
|
@@ -1156,8 +1111,9 @@ function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect
|
|
|
1156
1111
|
] }),
|
|
1157
1112
|
loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-8 justify-center text-sm text-muted-foreground", children: [
|
|
1158
1113
|
/* @__PURE__ */ jsx(Spinner, {}),
|
|
1159
|
-
"
|
|
1160
|
-
|
|
1114
|
+
" ",
|
|
1115
|
+
t("cpq.quotes.offeringBrowser.loading", "Loading offerings\u2026")
|
|
1116
|
+
] }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: search ? t("cpq.quotes.offeringBrowser.noMatch", "No offerings match your search.") : t("cpq.quotes.offeringBrowser.empty", "No active offerings available.") }) : /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((offering) => /* @__PURE__ */ jsxs(
|
|
1161
1117
|
"button",
|
|
1162
1118
|
{
|
|
1163
1119
|
onClick: () => onSelect(offering),
|
|
@@ -1173,13 +1129,14 @@ function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect
|
|
|
1173
1129
|
] });
|
|
1174
1130
|
}
|
|
1175
1131
|
function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions, arcSelectedTargetIds, onArcTargetToggle, onConfigChange, onQuantityChange, onSubmit, submitLabel, submitting }) {
|
|
1132
|
+
const t = useT();
|
|
1176
1133
|
const noTargetsPicked = arcTargetOptions !== null && arcTargetOptions.length > 0 && arcSelectedTargetIds.length === 0;
|
|
1177
1134
|
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card", children: [
|
|
1178
1135
|
/* @__PURE__ */ jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: title }) }),
|
|
1179
1136
|
/* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4", children: [
|
|
1180
1137
|
arcTargetOptions && arcTargetOptions.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1181
1138
|
/* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium mb-1", children: [
|
|
1182
|
-
"Apply to subscription(s)",
|
|
1139
|
+
t("cpq.quotes.detail.configure.applyToSubscriptions", "Apply to subscription(s)"),
|
|
1183
1140
|
/* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
|
|
1184
1141
|
] }),
|
|
1185
1142
|
/* @__PURE__ */ jsx("div", { className: "space-y-1.5 rounded-md border bg-background px-3 py-2", children: arcTargetOptions.map((opt) => {
|
|
@@ -1208,11 +1165,11 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
|
|
|
1208
1165
|
opt.subscriptionId
|
|
1209
1166
|
);
|
|
1210
1167
|
}) }),
|
|
1211
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Pick one or more \u2014 a separate line will be created on each selected subscription." }),
|
|
1212
|
-
noTargetsPicked && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-1", children: "Select at least one target." })
|
|
1168
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t("cpq.quotes.detail.configure.applyToSubscriptionsHint", "Pick one or more \u2014 a separate line will be created on each selected subscription.") }),
|
|
1169
|
+
noTargetsPicked && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-1", children: t("cpq.quotes.detail.configure.selectTarget", "Select at least one target.") })
|
|
1213
1170
|
] }),
|
|
1214
1171
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1215
|
-
/* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Quantity" }),
|
|
1172
|
+
/* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.quotes.detail.configure.quantity", "Quantity") }),
|
|
1216
1173
|
/* @__PURE__ */ jsx(
|
|
1217
1174
|
NumberInput,
|
|
1218
1175
|
{
|
|
@@ -1224,7 +1181,7 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
|
|
|
1224
1181
|
}
|
|
1225
1182
|
)
|
|
1226
1183
|
] }),
|
|
1227
|
-
attributes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No configurable attributes for this offering." }) : attributes.map((attr) => /* @__PURE__ */ jsx(AttributeField, { attribute: attr, value: config[attr.code], onChange: (v) => onConfigChange(attr.code, v) }, attr.code))
|
|
1184
|
+
attributes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("cpq.quotes.detail.configure.noAttributes", "No configurable attributes for this offering.") }) : attributes.map((attr) => /* @__PURE__ */ jsx(AttributeField, { attribute: attr, value: config[attr.code], onChange: (v) => onConfigChange(attr.code, v) }, attr.code))
|
|
1228
1185
|
] }),
|
|
1229
1186
|
/* @__PURE__ */ jsx("div", { className: "border-t px-4 py-3 flex justify-end", children: /* @__PURE__ */ jsxs(
|
|
1230
1187
|
"button",
|
|
@@ -1242,6 +1199,7 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
|
|
|
1242
1199
|
] });
|
|
1243
1200
|
}
|
|
1244
1201
|
function AttributeField({ attribute, value, onChange }) {
|
|
1202
|
+
const t = useT();
|
|
1245
1203
|
const { code, name, attributeType, isRequired, options, constraints, helpText, defaultValue } = attribute;
|
|
1246
1204
|
const currentValue = value ?? defaultValue ?? "";
|
|
1247
1205
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -1256,13 +1214,13 @@ function AttributeField({ attribute, value, onChange }) {
|
|
|
1256
1214
|
onChange: (e) => onChange(e.target.value),
|
|
1257
1215
|
className: "w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50",
|
|
1258
1216
|
children: [
|
|
1259
|
-
/* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Select \u2014" }),
|
|
1217
|
+
/* @__PURE__ */ jsx("option", { value: "", children: t("cpq.quotes.detail.configure.selectPlaceholder", "\u2014 Select \u2014") }),
|
|
1260
1218
|
options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
|
|
1261
1219
|
]
|
|
1262
1220
|
}
|
|
1263
1221
|
) : attributeType === "boolean" ? /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
|
|
1264
1222
|
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: Boolean(currentValue), onChange: (e) => onChange(e.target.checked), className: "rounded border-border" }),
|
|
1265
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: helpText ?? "Enable" })
|
|
1223
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: helpText ?? t("cpq.quotes.detail.configure.enable", "Enable") })
|
|
1266
1224
|
] }) : attributeType === "number" ? /* @__PURE__ */ jsx(
|
|
1267
1225
|
NumberInput,
|
|
1268
1226
|
{
|