@dainamite/cpq 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +70 -0
- package/MIGRATION.md +101 -0
- package/dist/modules/cpq/acl.js +1 -0
- package/dist/modules/cpq/acl.js.map +2 -2
- package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js +45 -0
- package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js.map +7 -0
- package/dist/modules/cpq/api/_helpers/handleArcError.js +2 -2
- package/dist/modules/cpq/api/_helpers/handleArcError.js.map +2 -2
- package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js +23 -0
- package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js.map +7 -0
- package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js +19 -0
- package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js.map +7 -0
- package/dist/modules/cpq/api/orders/[orderId]/activate/route.js +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/route.js +19 -3
- package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/status/route.js +2 -2
- package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +2 -2
- package/dist/modules/cpq/api/orders/route.js +49 -4
- package/dist/modules/cpq/api/orders/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js +54 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +45 -5
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js +47 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js +30 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js +47 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +34 -4
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/route.js +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +33 -2
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/price/route.js +43 -3
- package/dist/modules/cpq/api/quotes/price/route.js.map +2 -2
- package/dist/modules/cpq/api/quotes/route.js +2 -2
- package/dist/modules/cpq/api/quotes/route.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +22 -8
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +89 -232
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/pricing/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +205 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +144 -221
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
- package/dist/modules/cpq/backend/cpq/quotes/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/specifications/page.js.map +1 -1
- package/dist/modules/cpq/backend/cpq/wizards/page.js.map +1 -1
- package/dist/modules/cpq/ce.js +8 -0
- package/dist/modules/cpq/ce.js.map +2 -2
- package/dist/modules/cpq/cli.js +34 -1
- package/dist/modules/cpq/cli.js.map +2 -2
- package/dist/modules/cpq/commands/price-adjustments.js +107 -0
- package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
- package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
- package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js +154 -0
- package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
- package/dist/modules/cpq/components/CpqListView.js.map +2 -2
- package/dist/modules/cpq/components/PriceWaterfall.js +66 -0
- package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js +117 -0
- package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
- package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
- package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
- package/dist/modules/cpq/data/entities.js +97 -0
- package/dist/modules/cpq/data/entities.js.map +2 -2
- package/dist/modules/cpq/data/price-adjustments.js +70 -0
- package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
- package/dist/modules/cpq/data/validators.js +4 -0
- package/dist/modules/cpq/data/validators.js.map +2 -2
- package/dist/modules/cpq/di.js +6 -0
- package/dist/modules/cpq/di.js.map +2 -2
- package/dist/modules/cpq/events.js +47 -0
- package/dist/modules/cpq/events.js.map +2 -2
- package/dist/modules/cpq/index.js +1 -1
- package/dist/modules/cpq/index.js.map +2 -2
- package/dist/modules/cpq/lib/auditActor.js +10 -0
- package/dist/modules/cpq/lib/auditActor.js.map +7 -0
- package/dist/modules/cpq/lib/formatMoney.js +7 -0
- package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
- package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
- package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
- package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
- package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
- package/dist/modules/cpq/lib/quotePermissions.js +13 -0
- package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
- package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
- package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
- package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
- package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
- package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
- package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
- package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
- package/dist/modules/cpq/services/cpqOrderService.js +34 -30
- package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
- package/dist/modules/cpq/services/cpqPricingService.js +7 -0
- package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
- package/dist/modules/cpq/services/cpqQuotingService.js +339 -111
- package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
- package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
- package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
- package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
- package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
- package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
- package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
- package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
- package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
- package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
- package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
- package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
- package/dist/modules/cpq/services/types.js.map +2 -2
- package/dist/modules/cpq/setup.js +6 -0
- package/dist/modules/cpq/setup.js.map +2 -2
- package/package.json +9 -9
- package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +8 -5
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
- package/src/modules/cpq/acl.ts +1 -0
- package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
- package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
- package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
- package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
- package/src/modules/cpq/api/_helpers/handleArcError.ts +12 -12
- package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
- package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
- package/src/modules/cpq/api/orders/[orderId]/activate/route.ts +2 -2
- package/src/modules/cpq/api/orders/[orderId]/route.ts +20 -2
- package/src/modules/cpq/api/orders/[orderId]/status/route.ts +2 -2
- package/src/modules/cpq/api/orders/route.ts +53 -3
- package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +2 -2
- package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
- package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +51 -4
- package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
- package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
- package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
- package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +42 -3
- package/src/modules/cpq/api/quotes/[quoteId]/route.ts +2 -2
- package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
- package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
- package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
- package/src/modules/cpq/api/quotes/price/route.ts +28 -2
- package/src/modules/cpq/api/quotes/route.ts +2 -2
- package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +1 -1
- package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +1 -1
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +32 -10
- package/src/modules/cpq/backend/cpq/offerings/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +60 -269
- package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/orders/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/price-rules/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +178 -266
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/quotes/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/specifications/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
- package/src/modules/cpq/ce.ts +8 -0
- package/src/modules/cpq/cli.ts +48 -1
- package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
- package/src/modules/cpq/commands/price-adjustments.ts +90 -0
- package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
- package/src/modules/cpq/components/ChargeBreakdownTable.tsx +201 -0
- package/src/modules/cpq/components/CpqListView.tsx +2 -1
- package/src/modules/cpq/components/PriceWaterfall.tsx +94 -0
- package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
- package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
- package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
- package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
- package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
- package/src/modules/cpq/data/entities.ts +83 -0
- package/src/modules/cpq/data/price-adjustments.ts +67 -0
- package/src/modules/cpq/data/validators.ts +3 -0
- package/src/modules/cpq/di.ts +6 -0
- package/src/modules/cpq/events.ts +53 -2
- package/src/modules/cpq/index.ts +1 -1
- package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
- package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
- package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
- package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
- package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
- package/src/modules/cpq/lib/auditActor.ts +13 -0
- package/src/modules/cpq/lib/formatMoney.ts +4 -0
- package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
- package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
- package/src/modules/cpq/lib/quotePermissions.ts +22 -0
- package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
- package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
- package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
- package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
- package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
- package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
- package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
- package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
- package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
- package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
- package/src/modules/cpq/services/__tests__/errorBrands.test.ts +72 -0
- package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
- package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
- package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
- package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
- package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
- package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
- package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
- package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
- package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
- package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
- package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
- package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
- package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
- package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
- package/src/modules/cpq/services/cpqOrderService.ts +32 -28
- package/src/modules/cpq/services/cpqPricingService.ts +8 -0
- package/src/modules/cpq/services/cpqQuotingService.ts +388 -125
- package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
- package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
- package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
- package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
- package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
- package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
- package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
- package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
- package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
- package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
- package/src/modules/cpq/services/types.ts +34 -1
- package/src/modules/cpq/setup.ts +7 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/cpq/backend/cpq/quotes/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Plus, Search as SearchIcon } from 'lucide-react'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n formatStatusLabel,\n quoteCpqStatusMap,\n type QuoteCpqStatus,\n} from '../../../components/statusMaps'\n\ntype QuoteConfig = {\n id: string\n quoteId: string\n /** Resolved from SalesQuote.quoteNumber by the list API. */\n quoteNumber: string | null\n customerId: string\n cpqStatus: string\n version: number\n currencyCode: string\n pricingSummary: {\n nrcTotal?: number\n mrcTotal?: number\n } | null\n createdAt: string\n updatedAt: string\n}\n\ntype Customer = {\n id: string\n display_name?: string\n displayName?: string\n name?: string\n companyName?: string\n primary_email?: string\n primaryEmail?: string\n}\n\nconst PAGE_SIZE = 50\n\nfunction fmt(amount: number | undefined, currency: string): string {\n if (amount == null || amount === 0) return '\u2014'\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.cpqStatus === 'string' && values.cpqStatus) {\n params.set('cpqStatus', values.cpqStatus)\n }\n if (typeof values.currencyCode === 'string' && values.currencyCode.trim()) {\n params.set('currencyCode', values.currencyCode.trim().toUpperCase())\n }\n}\n\n// \u2500\u2500\u2500 Customer picker (extracted to keep main page focused) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype CustomerPickerProps = {\n open: boolean\n onClose: () => void\n onSelect: (customer: Customer) => void | Promise<void>\n creating: boolean\n error: string | null\n}\n\nfunction getCustomerLabel(c: Customer) {\n return c.display_name || c.displayName || c.name || c.companyName || 'Unnamed'\n}\n\nfunction getCustomerEmail(c: Customer) {\n return c.primary_email || c.primaryEmail || ''\n}\n\nfunction CustomerPickerModal({ open, onClose, onSelect, creating, error }: CustomerPickerProps) {\n const [customers, setCustomers] = React.useState<Customer[]>([])\n const [customersLoading, setCustomersLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n setSearch('')\n setCustomersLoading(true)\n fetch('/api/customers/companies?pageSize=100')\n .then((r) => (r.ok ? r.json() : { items: [] }))\n .then((data) => {\n if (!cancelled) setCustomers(data.items ?? data ?? [])\n })\n .catch(() => {\n if (!cancelled) setCustomers([])\n })\n .finally(() => {\n if (!cancelled) setCustomersLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [open])\n\n if (!open) return null\n\n const filtered = customers.filter((c) => {\n if (!search.trim()) return true\n const q = search.toLowerCase()\n return (\n getCustomerLabel(c).toLowerCase().includes(q) ||\n getCustomerEmail(c).toLowerCase().includes(q) ||\n c.id.toLowerCase().includes(q)\n )\n })\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n onClick={() => !creating && onClose()}\n >\n <div\n className=\"w-full max-w-lg rounded-lg border bg-card shadow-xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <h3 className=\"text-sm font-medium\">Select Customer for New Quote</h3>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={creating}\n className=\"text-muted-foreground hover:text-foreground text-lg font-bold leading-none cursor-pointer disabled:cursor-not-allowed\"\n >\n ×\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {error && <Alert variant=\"destructive\">{error}</Alert>}\n <div className=\"relative\">\n <SearchIcon className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n <Input\n placeholder=\"Search customers\u2026\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n autoFocus\n className=\"pl-10\"\n />\n </div>\n <div className=\"max-h-72 overflow-y-auto space-y-1\">\n {customersLoading ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n Loading customers\u2026\n </div>\n ) : filtered.length === 0 ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {search ? 'No customers match your search.' : 'No customers found.'}\n </div>\n ) : (\n filtered.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => onSelect(c)}\n disabled={creating}\n className=\"w-full rounded-md border px-3 py-2.5 text-left hover:border-primary hover:bg-primary/5 transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed\"\n >\n <p className=\"text-sm font-medium\">{getCustomerLabel(c)}</p>\n {getCustomerEmail(c) && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{getCustomerEmail(c)}</p>\n )}\n <p className=\"text-xs text-muted-foreground font-mono mt-0.5\">{c.id}</p>\n </button>\n ))\n )}\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default function CpqQuotesListPage() {\n const router = useRouter()\n const t = useT()\n\n const data = useCpqListData<QuoteConfig>({\n endpoint: '/api/cpq/quotes',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.quotes.list.error.load', 'Failed to load quotes'),\n })\n\n const [showCustomerPicker, setShowCustomerPicker] = React.useState(false)\n const [creating, setCreating] = React.useState(false)\n const [createError, setCreateError] = React.useState<string | null>(null)\n\n const statusOptions = React.useMemo(\n () => [\n { value: 'new', label: 'New' },\n { value: 'incomplete', label: 'Incomplete' },\n { value: 'ready', label: 'Ready' },\n { value: 'in_approval', label: 'In Approval' },\n { value: 'approved', label: 'Approved' },\n { value: 'with_customer', label: 'With Customer' },\n { value: 'accepted', label: 'Accepted' },\n { value: 'rejected', label: 'Rejected' },\n { value: 'cancelled', label: 'Cancelled' },\n ],\n [],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'cpqStatus',\n label: t('cpq.quotes.filters.status', 'Status'),\n type: 'select',\n options: statusOptions,\n },\n {\n id: 'currencyCode',\n label: t('cpq.quotes.filters.currency', 'Currency'),\n type: 'text',\n },\n ],\n [statusOptions, t],\n )\n\n const selectCustomer = React.useCallback(\n async (customer: Customer) => {\n setCreating(true)\n setCreateError(null)\n try {\n const res = await fetch('/api/cpq/quotes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ customerId: customer.id }),\n })\n const body = await res.json()\n if (res.ok) {\n setShowCustomerPicker(false)\n router.push(`/backend/cpq/quotes/${body.id}`)\n } else {\n setCreateError(body.error ?? `Failed to create quote (${res.status})`)\n }\n } catch (err) {\n setCreateError((err as Error).message)\n } finally {\n setCreating(false)\n }\n },\n [router],\n )\n\n const columns = React.useMemo<ColumnDef<QuoteConfig>[]>(\n () => [\n {\n accessorKey: 'quoteNumber',\n header: t('cpq.quotes.table.quoteNumber', 'Quote Number'),\n cell: ({ row }) => (\n <span className=\"font-medium\">\n {row.original.quoteNumber ?? (\n <span className=\"font-mono text-xs text-muted-foreground\">\n {(row.original.quoteId ?? row.original.id).slice(0, 8)}\u2026\n </span>\n )}\n </span>\n ),\n },\n {\n accessorKey: 'cpqStatus',\n header: t('cpq.quotes.table.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={quoteCpqStatusMap[row.original.cpqStatus as QuoteCpqStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.cpqStatus)}\n </Tag>\n ),\n },\n {\n accessorKey: 'version',\n header: t('cpq.quotes.table.version', 'Version'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">v{row.original.version}</span>,\n },\n {\n accessorKey: 'currencyCode',\n header: t('cpq.quotes.table.currency', 'Currency'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.currencyCode}</span>,\n },\n {\n id: 'nrcTotal',\n header: t('cpq.quotes.table.nrcTotal', 'NRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.nrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n id: 'mrcTotal',\n header: t('cpq.quotes.table.mrcTotal', 'MRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.mrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('cpq.quotes.table.createdAt', 'Created'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {new Date(row.original.createdAt).toLocaleDateString()}\n </span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<QuoteConfig>\n title={t('cpq.quotes.list.title', 'CPQ Quotes')}\n tableId=\"cpq.quotes.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.quotes.search.placeholder', 'Search by quote / customer id...')}\n actions={\n <Button type=\"button\" onClick={() => setShowCustomerPicker(true)} className=\"gap-1.5\">\n <Plus className=\"h-4 w-4\" />\n {t('cpq.quotes.add', 'New Quote')}\n </Button>\n }\n onRowClick={(row) => router.push(`/backend/cpq/quotes/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'open',\n label: t('cpq.quotes.actions.open', 'Open'),\n href: `/backend/cpq/quotes/${row.id}`,\n },\n ]}\n />\n )}\n toolbarContent={\n <CustomerPickerModal\n open={showCustomerPicker}\n onClose={() => setShowCustomerPicker(false)}\n onSelect={selectCustomer}\n creating={creating}\n error={createError}\n />\n }\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.quotes.empty',\n 'No CPQ quotes found. Create one to start configuring offerings for a customer.',\n )}\n </div>\n }\n />\n )\n}\n"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { Plus, Search as SearchIcon } from 'lucide-react'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n formatStatusLabel,\n quoteCpqStatusMap,\n type QuoteCpqStatus,\n} from '../../../components/statusMaps'\n\ntype QuoteConfig = {\n id: string\n quoteId: string\n /** Resolved from SalesQuote.quoteNumber by the list API. */\n quoteNumber: string | null\n customerId: string\n cpqStatus: string\n version: number\n currencyCode: string\n pricingSummary: {\n nrcTotal?: number\n mrcTotal?: number\n } | null\n createdAt: string\n updatedAt: string\n}\n\ntype Customer = {\n id: string\n display_name?: string\n displayName?: string\n name?: string\n companyName?: string\n primary_email?: string\n primaryEmail?: string\n}\n\nconst PAGE_SIZE = 50\n\nfunction fmt(amount: number | undefined, currency: string): string {\n if (amount == null || amount === 0) return '\u2014'\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.cpqStatus === 'string' && values.cpqStatus) {\n params.set('cpqStatus', values.cpqStatus)\n }\n if (typeof values.currencyCode === 'string' && values.currencyCode.trim()) {\n params.set('currencyCode', values.currencyCode.trim().toUpperCase())\n }\n}\n\n// \u2500\u2500\u2500 Customer picker (extracted to keep main page focused) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype CustomerPickerProps = {\n open: boolean\n onClose: () => void\n onSelect: (customer: Customer) => void | Promise<void>\n creating: boolean\n error: string | null\n}\n\nfunction getCustomerLabel(c: Customer) {\n return c.display_name || c.displayName || c.name || c.companyName || 'Unnamed'\n}\n\nfunction getCustomerEmail(c: Customer) {\n return c.primary_email || c.primaryEmail || ''\n}\n\nfunction CustomerPickerModal({ open, onClose, onSelect, creating, error }: CustomerPickerProps) {\n const [customers, setCustomers] = React.useState<Customer[]>([])\n const [customersLoading, setCustomersLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n\n React.useEffect(() => {\n if (!open) return\n let cancelled = false\n setSearch('')\n setCustomersLoading(true)\n fetch('/api/customers/companies?pageSize=100')\n .then((r) => (r.ok ? r.json() : { items: [] }))\n .then((data) => {\n if (!cancelled) setCustomers(data.items ?? data ?? [])\n })\n .catch(() => {\n if (!cancelled) setCustomers([])\n })\n .finally(() => {\n if (!cancelled) setCustomersLoading(false)\n })\n return () => {\n cancelled = true\n }\n }, [open])\n\n if (!open) return null\n\n const filtered = customers.filter((c) => {\n if (!search.trim()) return true\n const q = search.toLowerCase()\n return (\n getCustomerLabel(c).toLowerCase().includes(q) ||\n getCustomerEmail(c).toLowerCase().includes(q) ||\n c.id.toLowerCase().includes(q)\n )\n })\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n onClick={() => !creating && onClose()}\n >\n <div\n className=\"w-full max-w-lg rounded-lg border bg-card shadow-xl\"\n onClick={(e) => e.stopPropagation()}\n >\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <h3 className=\"text-sm font-medium\">Select Customer for New Quote</h3>\n <button\n type=\"button\"\n onClick={onClose}\n disabled={creating}\n className=\"text-muted-foreground hover:text-foreground text-lg font-bold leading-none cursor-pointer disabled:cursor-not-allowed\"\n >\n ×\n </button>\n </div>\n <div className=\"p-4 space-y-3\">\n {error && <Alert variant=\"destructive\">{error}</Alert>}\n <div className=\"relative\">\n <SearchIcon className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground\" />\n <Input\n placeholder=\"Search customers\u2026\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n autoFocus\n className=\"pl-10\"\n />\n </div>\n <div className=\"max-h-72 overflow-y-auto space-y-1\">\n {customersLoading ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n Loading customers\u2026\n </div>\n ) : filtered.length === 0 ? (\n <div className=\"py-8 text-center text-sm text-muted-foreground\">\n {search ? 'No customers match your search.' : 'No customers found.'}\n </div>\n ) : (\n filtered.map((c) => (\n <button\n key={c.id}\n type=\"button\"\n onClick={() => onSelect(c)}\n disabled={creating}\n className=\"w-full rounded-md border px-3 py-2.5 text-left hover:border-primary hover:bg-primary/5 transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed\"\n >\n <p className=\"text-sm font-medium\">{getCustomerLabel(c)}</p>\n {getCustomerEmail(c) && (\n <p className=\"text-xs text-muted-foreground mt-0.5\">{getCustomerEmail(c)}</p>\n )}\n <p className=\"text-xs text-muted-foreground font-mono mt-0.5\">{c.id}</p>\n </button>\n ))\n )}\n </div>\n </div>\n </div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default function CpqQuotesListPage() {\n const router = useRouter()\n const t = useT()\n\n const data = useCpqListData<QuoteConfig>({\n endpoint: '/api/cpq/quotes',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.quotes.list.error.load', 'Failed to load quotes'),\n })\n\n const [showCustomerPicker, setShowCustomerPicker] = React.useState(false)\n const [creating, setCreating] = React.useState(false)\n const [createError, setCreateError] = React.useState<string | null>(null)\n\n const statusOptions = React.useMemo(\n () => [\n { value: 'new', label: 'New' },\n { value: 'incomplete', label: 'Incomplete' },\n { value: 'ready', label: 'Ready' },\n { value: 'in_approval', label: 'In Approval' },\n { value: 'approved', label: 'Approved' },\n { value: 'with_customer', label: 'With Customer' },\n { value: 'accepted', label: 'Accepted' },\n { value: 'rejected', label: 'Rejected' },\n { value: 'cancelled', label: 'Cancelled' },\n ],\n [],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'cpqStatus',\n label: t('cpq.quotes.filters.status', 'Status'),\n type: 'select',\n options: statusOptions,\n },\n {\n id: 'currencyCode',\n label: t('cpq.quotes.filters.currency', 'Currency'),\n type: 'text',\n },\n ],\n [statusOptions, t],\n )\n\n const selectCustomer = React.useCallback(\n async (customer: Customer) => {\n setCreating(true)\n setCreateError(null)\n try {\n const res = await fetch('/api/cpq/quotes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ customerId: customer.id }),\n })\n const body = await res.json()\n if (res.ok) {\n setShowCustomerPicker(false)\n router.push(`/backend/cpq/quotes/${body.id}`)\n } else {\n setCreateError(body.error ?? `Failed to create quote (${res.status})`)\n }\n } catch (err) {\n setCreateError((err as Error).message)\n } finally {\n setCreating(false)\n }\n },\n [router],\n )\n\n const columns = React.useMemo<ColumnDef<QuoteConfig>[]>(\n () => [\n {\n accessorKey: 'quoteNumber',\n header: t('cpq.quotes.table.quoteNumber', 'Quote Number'),\n cell: ({ row }) => (\n <span className=\"font-medium\">\n {row.original.quoteNumber ?? (\n <span className=\"font-mono text-xs text-muted-foreground\">\n {(row.original.quoteId ?? row.original.id).slice(0, 8)}\u2026\n </span>\n )}\n </span>\n ),\n },\n {\n accessorKey: 'cpqStatus',\n header: t('cpq.quotes.table.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={quoteCpqStatusMap[row.original.cpqStatus as QuoteCpqStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.cpqStatus)}\n </Tag>\n ),\n },\n {\n accessorKey: 'version',\n header: t('cpq.quotes.table.version', 'Version'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">v{row.original.version}</span>,\n },\n {\n accessorKey: 'currencyCode',\n header: t('cpq.quotes.table.currency', 'Currency'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.currencyCode}</span>,\n },\n {\n id: 'nrcTotal',\n header: t('cpq.quotes.table.nrcTotal', 'NRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.nrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n id: 'mrcTotal',\n header: t('cpq.quotes.table.mrcTotal', 'MRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.mrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('cpq.quotes.table.createdAt', 'Created'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {new Date(row.original.createdAt).toLocaleDateString()}\n </span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<QuoteConfig>\n title={t('cpq.quotes.list.title', 'CPQ Quotes')}\n tableId=\"cpq.quotes.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.quotes.search.placeholder', 'Search by quote / customer id...')}\n actions={\n <Button type=\"button\" onClick={() => setShowCustomerPicker(true)} className=\"gap-1.5\">\n <Plus className=\"h-4 w-4\" />\n {t('cpq.quotes.add', 'New Quote')}\n </Button>\n }\n onRowClick={(row) => router.push(`/backend/cpq/quotes/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'open',\n label: t('cpq.quotes.actions.open', 'Open'),\n href: `/backend/cpq/quotes/${row.id}`,\n },\n ]}\n />\n )}\n toolbarContent={\n <CustomerPickerModal\n open={showCustomerPicker}\n onClose={() => setShowCustomerPicker(false)}\n onSelect={selectCustomer}\n creating={creating}\n error={createError}\n />\n }\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.quotes.empty',\n 'No CPQ quotes found. Create one to start configuring offerings for a customer.',\n )}\n </div>\n }\n />\n )\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/cpq/backend/cpq/quotes/page.meta.ts"],
|
|
4
|
-
"sourcesContent": ["import React from 'react'\nimport { FileText } from 'lucide-react'\n\nconst quotesIcon = React.createElement(FileText, { size: 16 })\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['cpq.quotes.view'],\n pageTitle: 'CPQ Quotes',\n pageTitleKey: 'cpq.quotes.title',\n pageGroup: 'CPQ',\n pageGroupKey: 'customers~sales~cpq.nav.group',\n pageOrder:
|
|
4
|
+
"sourcesContent": ["import React from 'react'\nimport { FileText } from 'lucide-react'\n\nconst quotesIcon = React.createElement(FileText, { size: 16 })\n\nexport const metadata = {\n requireAuth: true,\n requireFeatures: ['cpq.quotes.view'],\n pageTitle: 'CPQ Quotes',\n pageTitleKey: 'cpq.quotes.title',\n pageGroup: 'CPQ',\n pageGroupKey: 'customers~sales~cpq.nav.group',\n pageOrder: 1,\n icon: quotesIcon,\n breadcrumb: [{ label: 'CPQ Quotes', labelKey: 'cpq.quotes.title' }],\n} as const\n"],
|
|
5
5
|
"mappings": "AAAA,OAAO,WAAW;AAClB,SAAS,gBAAgB;AAEzB,MAAM,aAAa,MAAM,cAAc,UAAU,EAAE,MAAM,GAAG,CAAC;AAEtD,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,iBAAiB;AAAA,EACnC,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,MAAM;AAAA,EACN,YAAY,CAAC,EAAE,OAAO,cAAc,UAAU,mBAAmB,CAAC;AACpE;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/cpq/backend/cpq/specifications/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n formatStatusLabel,\n specificationLifecycleStatusMap,\n type SpecificationLifecycleStatus,\n} from '../../../components/statusMaps'\n\ntype Specification = {\n id: string\n productId: string\n code: string\n name: string\n description: string | null\n specType: string\n isAssetizable: boolean\n lifecycleStatus: string\n version: number\n isActive: boolean\n createdAt: string\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.lifecycleStatus === 'string' && values.lifecycleStatus) {\n params.set('lifecycleStatus', values.lifecycleStatus)\n }\n if (typeof values.specType === 'string' && values.specType) {\n params.set('specType', values.specType)\n }\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n if (values.isAssetizable === true) params.set('isAssetizable', 'true')\n if (values.isAssetizable === false) params.set('isAssetizable', 'false')\n}\n\nexport default function SpecificationsListPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Specification>({\n endpoint: '/api/cpq/product-specifications',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.specifications.list.error.load', 'Failed to load specifications'),\n })\n\n const lifecycleOptions = React.useMemo(\n () => [\n { value: 'draft', label: t('cpq.specifications.lifecycle.draft', 'Draft') },\n { value: 'active', label: t('cpq.specifications.lifecycle.active', 'Active') },\n { value: 'deprecated', label: t('cpq.specifications.lifecycle.deprecated', 'Deprecated') },\n ],\n [t],\n )\n\n const specTypeOptions = React.useMemo(\n () => [\n { value: 'simple', label: t('cpq.specifications.type.simple', 'Simple') },\n { value: 'bundle', label: t('cpq.specifications.type.bundle', 'Bundle') },\n ],\n [t],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'lifecycleStatus',\n label: t('cpq.specifications.filters.lifecycleStatus', 'Lifecycle Status'),\n type: 'select',\n options: lifecycleOptions,\n },\n {\n id: 'specType',\n label: t('cpq.specifications.filters.specType', 'Type'),\n type: 'select',\n options: specTypeOptions,\n },\n {\n id: 'isActive',\n label: t('cpq.specifications.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n {\n id: 'isAssetizable',\n label: t('cpq.specifications.filters.isAssetizable', 'Assetizable'),\n type: 'checkbox',\n },\n ],\n [lifecycleOptions, specTypeOptions, t],\n )\n\n const columns = React.useMemo<ColumnDef<Specification>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.specifications.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.specifications.name', 'Name'),\n cell: ({ row }) => (\n <span className=\"font-medium\">\n {row.original.name}\n {row.original.specType === 'bundle' && (\n <Tag variant=\"brand\" className=\"ml-2 px-2 text-[10px]\">\n bundle\n </Tag>\n )}\n {row.original.isAssetizable && (\n <Tag variant=\"warning\" className=\"ml-1 px-2 text-[10px]\">\n asset\n </Tag>\n )}\n </span>\n ),\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.specifications.isActive', 'Is Active?'),\n cell: ({ row }) => <Checkbox checked={row.original.isActive} disabled />,\n },\n {\n accessorKey: 'lifecycleStatus',\n header: t('cpq.specifications.lifecycleStatus', 'Lifecycle Status'),\n cell: ({ row }) => (\n <Tag variant={specificationLifecycleStatusMap[row.original.lifecycleStatus as SpecificationLifecycleStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.lifecycleStatus)}\n </Tag>\n ),\n },\n {\n accessorKey: 'version',\n header: t('cpq.specifications.version', 'Version'),\n cell: ({ row }) => <span>v{row.original.version}</span>,\n },\n {\n accessorKey: 'description',\n header: t('cpq.specifications.description', 'Description'),\n cell: ({ row }) => (\n <span className=\"text-muted-foreground\">{row.original.description ?? '\u2014'}</span>\n ),\n meta: { truncate: true, maxWidth: 320 },\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<Specification>\n title={t('cpq.specifications.list.title', 'Product Specifications')}\n tableId=\"cpq.specifications.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.specifications.search.placeholder', 'Search specifications...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/specifications/new\">\n {t('cpq.specifications.add', 'New Specification')}\n </a>\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/product-specifications',\n entityName: t('cpq.specifications.entityName', 'specification'),\n editHref: (row) => `/backend/cpq/specifications/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/specifications/${row.id}`)}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.specifications.empty',\n 'No product specifications found. Create one to define your product catalogue.',\n )}\n </div>\n }\n />\n )\n}\n"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n formatStatusLabel,\n specificationLifecycleStatusMap,\n type SpecificationLifecycleStatus,\n} from '../../../components/statusMaps'\n\ntype Specification = {\n id: string\n productId: string\n code: string\n name: string\n description: string | null\n specType: string\n isAssetizable: boolean\n lifecycleStatus: string\n version: number\n isActive: boolean\n createdAt: string\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.lifecycleStatus === 'string' && values.lifecycleStatus) {\n params.set('lifecycleStatus', values.lifecycleStatus)\n }\n if (typeof values.specType === 'string' && values.specType) {\n params.set('specType', values.specType)\n }\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n if (values.isAssetizable === true) params.set('isAssetizable', 'true')\n if (values.isAssetizable === false) params.set('isAssetizable', 'false')\n}\n\nexport default function SpecificationsListPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Specification>({\n endpoint: '/api/cpq/product-specifications',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.specifications.list.error.load', 'Failed to load specifications'),\n })\n\n const lifecycleOptions = React.useMemo(\n () => [\n { value: 'draft', label: t('cpq.specifications.lifecycle.draft', 'Draft') },\n { value: 'active', label: t('cpq.specifications.lifecycle.active', 'Active') },\n { value: 'deprecated', label: t('cpq.specifications.lifecycle.deprecated', 'Deprecated') },\n ],\n [t],\n )\n\n const specTypeOptions = React.useMemo(\n () => [\n { value: 'simple', label: t('cpq.specifications.type.simple', 'Simple') },\n { value: 'bundle', label: t('cpq.specifications.type.bundle', 'Bundle') },\n ],\n [t],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'lifecycleStatus',\n label: t('cpq.specifications.filters.lifecycleStatus', 'Lifecycle Status'),\n type: 'select',\n options: lifecycleOptions,\n },\n {\n id: 'specType',\n label: t('cpq.specifications.filters.specType', 'Type'),\n type: 'select',\n options: specTypeOptions,\n },\n {\n id: 'isActive',\n label: t('cpq.specifications.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n {\n id: 'isAssetizable',\n label: t('cpq.specifications.filters.isAssetizable', 'Assetizable'),\n type: 'checkbox',\n },\n ],\n [lifecycleOptions, specTypeOptions, t],\n )\n\n const columns = React.useMemo<ColumnDef<Specification>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.specifications.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.specifications.name', 'Name'),\n cell: ({ row }) => (\n <span className=\"font-medium\">\n {row.original.name}\n {row.original.specType === 'bundle' && (\n <Tag variant=\"brand\" className=\"ml-2 px-2 text-[10px]\">\n bundle\n </Tag>\n )}\n {row.original.isAssetizable && (\n <Tag variant=\"warning\" className=\"ml-1 px-2 text-[10px]\">\n asset\n </Tag>\n )}\n </span>\n ),\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.specifications.isActive', 'Is Active?'),\n cell: ({ row }) => <Checkbox checked={row.original.isActive} disabled />,\n },\n {\n accessorKey: 'lifecycleStatus',\n header: t('cpq.specifications.lifecycleStatus', 'Lifecycle Status'),\n cell: ({ row }) => (\n <Tag variant={specificationLifecycleStatusMap[row.original.lifecycleStatus as SpecificationLifecycleStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.lifecycleStatus)}\n </Tag>\n ),\n },\n {\n accessorKey: 'version',\n header: t('cpq.specifications.version', 'Version'),\n cell: ({ row }) => <span>v{row.original.version}</span>,\n },\n {\n accessorKey: 'description',\n header: t('cpq.specifications.description', 'Description'),\n cell: ({ row }) => (\n <span className=\"text-muted-foreground\">{row.original.description ?? '\u2014'}</span>\n ),\n meta: { truncate: true, maxWidth: 320 },\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<Specification>\n title={t('cpq.specifications.list.title', 'Product Specifications')}\n tableId=\"cpq.specifications.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.specifications.search.placeholder', 'Search specifications...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/specifications/new\">\n {t('cpq.specifications.add', 'New Specification')}\n </a>\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/product-specifications',\n entityName: t('cpq.specifications.entityName', 'specification'),\n editHref: (row) => `/backend/cpq/specifications/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/specifications/${row.id}`)}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.specifications.empty',\n 'No product specifications found. Create one to define your product catalogue.',\n )}\n </div>\n }\n />\n )\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/cpq/backend/cpq/wizards/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\n\ntype WizardDefinition = {\n id: string\n code: string\n name: string\n description: string | null\n version: number\n surface: string\n isActive: boolean\n steps: Array<{ stepId: string; type: string; title: string }>\n createdAt: string\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.surface === 'string' && values.surface.trim()) {\n params.set('surface', values.surface.trim())\n }\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function CpqWizardsPage() {\n const router = useRouter()\n const t = useT()\n\n const data = useCpqListData<WizardDefinition>({\n endpoint: '/api/cpq/wizards',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.wizards.list.error.load', 'Failed to load wizards'),\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'surface',\n label: t('cpq.wizards.filters.surface', 'Surface'),\n type: 'text',\n },\n {\n id: 'isActive',\n label: t('cpq.wizards.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [t],\n )\n\n const handlePreviewWizard = React.useCallback(\n (definitionCode: string) => {\n router.push(`/backend/cpq/wizards/${definitionCode}`)\n },\n [router],\n )\n\n const columns = React.useMemo<ColumnDef<WizardDefinition>[]>(\n () => [\n {\n accessorKey: 'name',\n header: t('cpq.wizards.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.wizards.table.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'surface',\n header: t('cpq.wizards.table.surface', 'Surface'),\n },\n {\n id: 'steps',\n header: t('cpq.wizards.table.steps', 'Steps'),\n cell: ({ row }) => <span>{row.original.steps.length}</span>,\n },\n {\n id: 'version',\n header: t('cpq.wizards.table.version', 'Version'),\n cell: ({ row }) => <span>v{row.original.version}</span>,\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.wizards.table.active', 'Active'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? 'Yes' : 'No'}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<WizardDefinition>\n title={t('cpq.wizards.list.title', 'Wizards')}\n tableId=\"cpq.wizards.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.wizards.search.placeholder', 'Search wizards...')}\n onRowClick={(row) => router.push(`/backend/cpq/wizards/${row.code}/detail`)}\n // Wizards are read-only in the admin UI for now: no Edit / Delete\n // entries \u2014 only `Preview` (run the wizard) and `View details`.\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'preview',\n label: t('cpq.wizards.actions.preview', 'Preview'),\n onSelect: () => handlePreviewWizard(row.code),\n },\n {\n id: 'details',\n label: t('cpq.wizards.actions.details', 'View details'),\n href: `/backend/cpq/wizards/${row.code}/detail`,\n },\n ]}\n />\n )}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.wizards.empty', 'No wizard definitions yet. Create one via the API.')}\n </div>\n }\n />\n )\n}\n"],
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\n\ntype WizardDefinition = {\n id: string\n code: string\n name: string\n description: string | null\n version: number\n surface: string\n isActive: boolean\n steps: Array<{ stepId: string; type: string; title: string }>\n createdAt: string\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.surface === 'string' && values.surface.trim()) {\n params.set('surface', values.surface.trim())\n }\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function CpqWizardsPage() {\n const router = useRouter()\n const t = useT()\n\n const data = useCpqListData<WizardDefinition>({\n endpoint: '/api/cpq/wizards',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.wizards.list.error.load', 'Failed to load wizards'),\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'surface',\n label: t('cpq.wizards.filters.surface', 'Surface'),\n type: 'text',\n },\n {\n id: 'isActive',\n label: t('cpq.wizards.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [t],\n )\n\n const handlePreviewWizard = React.useCallback(\n (definitionCode: string) => {\n router.push(`/backend/cpq/wizards/${definitionCode}`)\n },\n [router],\n )\n\n const columns = React.useMemo<ColumnDef<WizardDefinition>[]>(\n () => [\n {\n accessorKey: 'name',\n header: t('cpq.wizards.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.wizards.table.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'surface',\n header: t('cpq.wizards.table.surface', 'Surface'),\n },\n {\n id: 'steps',\n header: t('cpq.wizards.table.steps', 'Steps'),\n cell: ({ row }) => <span>{row.original.steps.length}</span>,\n },\n {\n id: 'version',\n header: t('cpq.wizards.table.version', 'Version'),\n cell: ({ row }) => <span>v{row.original.version}</span>,\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.wizards.table.active', 'Active'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? 'Yes' : 'No'}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<WizardDefinition>\n title={t('cpq.wizards.list.title', 'Wizards')}\n tableId=\"cpq.wizards.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.wizards.search.placeholder', 'Search wizards...')}\n onRowClick={(row) => router.push(`/backend/cpq/wizards/${row.code}/detail`)}\n // Wizards are read-only in the admin UI for now: no Edit / Delete\n // entries \u2014 only `Preview` (run the wizard) and `View details`.\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'preview',\n label: t('cpq.wizards.actions.preview', 'Preview'),\n onSelect: () => handlePreviewWizard(row.code),\n },\n {\n id: 'details',\n label: t('cpq.wizards.actions.details', 'View details'),\n href: `/backend/cpq/wizards/${row.code}/detail`,\n },\n ]}\n />\n )}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.wizards.empty', 'No wizard definitions yet. Create one via the API.')}\n </div>\n }\n />\n )\n}\n"],
|
|
5
5
|
"mappings": ";AAuE2B,cAmBA,YAnBA;AAtE3B,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAC1B,SAAS,YAAY;AAGrB,SAAS,kBAAkB;AAC3B,SAAS,WAAW;AACpB,SAAS,aAAa,sBAAsB;AAc5C,MAAM,YAAY;AAElB,SAAS,kBAAkB,QAAsB,QAAyB;AACxE,MAAI,OAAO,OAAO,YAAY,YAAY,OAAO,QAAQ,KAAK,GAAG;AAC/D,WAAO,IAAI,WAAW,OAAO,QAAQ,KAAK,CAAC;AAAA,EAC7C;AACA,MAAI,OAAO,aAAa,KAAM,QAAO,IAAI,YAAY,MAAM;AAC3D,MAAI,OAAO,aAAa,MAAO,QAAO,IAAI,YAAY,OAAO;AAC/D;AAEe,SAAR,iBAAkC;AACvC,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AAEf,QAAM,OAAO,eAAiC;AAAA,IAC5C,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,kBAAkB,EAAE,+BAA+B,wBAAwB;AAAA,EAC7E,CAAC;AAED,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,+BAA+B,SAAS;AAAA,QACjD,MAAM;AAAA,MACR;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,gCAAgC,QAAQ;AAAA,QACjD,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,sBAAsB,MAAM;AAAA,IAChC,CAAC,mBAA2B;AAC1B,aAAO,KAAK,wBAAwB,cAAc,EAAE;AAAA,IACtD;AAAA,IACA,CAAC,MAAM;AAAA,EACT;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,0BAA0B,MAAM;AAAA,QAC1C,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,eAAe,cAAI,SAAS,MAAK;AAAA,MACtE;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,0BAA0B,MAAM;AAAA,QAC1C,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,qBAAqB,cAAI,SAAS,MAAK;AAAA,MAC5E;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,6BAA6B,SAAS;AAAA,MAClD;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,2BAA2B,OAAO;AAAA,QAC5C,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,cAAI,SAAS,MAAM,QAAO;AAAA,MACtD;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,6BAA6B,SAAS;AAAA,QAChD,MAAM,CAAC,EAAE,IAAI,MAAM,qBAAC,UAAK;AAAA;AAAA,UAAE,IAAI,SAAS;AAAA,WAAQ;AAAA,MAClD;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,4BAA4B,QAAQ;AAAA,QAC9C,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,OAAI,SAAS,IAAI,SAAS,WAAW,YAAY,WAAW,KAAG,MAC7D,cAAI,SAAS,WAAW,QAAQ,MACnC;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,0BAA0B,SAAS;AAAA,MAC5C,SAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,mBAAmB,EAAE,kCAAkC,mBAAmB;AAAA,MAC1E,YAAY,CAAC,QAAQ,OAAO,KAAK,wBAAwB,IAAI,IAAI,SAAS;AAAA,MAG1E,YAAY,CAAC,QACX;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,YACL;AAAA,cACE,IAAI;AAAA,cACJ,OAAO,EAAE,+BAA+B,SAAS;AAAA,cACjD,UAAU,MAAM,oBAAoB,IAAI,IAAI;AAAA,YAC9C;AAAA,YACA;AAAA,cACE,IAAI;AAAA,cACJ,OAAO,EAAE,+BAA+B,cAAc;AAAA,cACtD,MAAM,wBAAwB,IAAI,IAAI;AAAA,YACxC;AAAA,UACF;AAAA;AAAA,MACF;AAAA,MAEF,YACE,oBAAC,SAAI,WAAU,2EACZ,YAAE,qBAAqB,oDAAoD,GAC9E;AAAA;AAAA,EAEJ;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/modules/cpq/ce.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
const entities = [
|
|
2
|
+
{
|
|
3
|
+
id: "cpq:cpq_quote_price_adjustment",
|
|
4
|
+
label: "Quote Price Adjustment",
|
|
5
|
+
description: "Negotiated charge or line discount on a quote.",
|
|
6
|
+
labelField: "reasonCode",
|
|
7
|
+
showInSidebar: false,
|
|
8
|
+
fields: []
|
|
9
|
+
},
|
|
2
10
|
{
|
|
3
11
|
id: "cpq:cpq_product_specification",
|
|
4
12
|
label: "Product Specification",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/cpq/ce.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CustomEntitySpec } from '@open-mercato/shared/modules/entities'\n\nconst entities: CustomEntitySpec[] = [\n {\n id: 'cpq:cpq_product_specification',\n label: 'Product Specification',\n description: 'CPQ product blueprint \u2014 defines attributes, dependencies, and relationships.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_offering',\n label: 'Product Offering',\n description: 'Sellable instance of a product specification with design-time values and charges.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_relationship',\n label: 'Product Relationship',\n description: 'Structural relationship between product specifications (parent_child, requires, excludes).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_attribute',\n label: 'Product Attribute',\n description: 'Configurable attribute definition for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_charge',\n label: 'Product Charge',\n description: 'Charge definition (NRC/MRC/usage) for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table',\n label: 'Pricing Table',\n description: 'Multi-dimensional pricing table with named price columns.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table_entry',\n label: 'Pricing Table Entry',\n description: 'Individual row in a pricing table with dimension values and prices.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_price_rule',\n label: 'Price Rule',\n description: 'Post-calculation price adjustment rule (discount, surcharge, override).',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_bundle_slot',\n label: 'Bundle Slot',\n description: 'Structural slot within a bundle specification \u2014 defines what component specs can fill it.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_offering_component',\n label: 'Offering Component',\n description: 'Links a bundle offering to a specific component offering within a slot.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_configuration',\n label: 'Quote Configuration',\n description: 'CPQ-specific configuration data for a sales quote.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_line_configuration',\n label: 'Quote Line Configuration',\n description: 'CPQ-specific configuration for a sales quote line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_wizard_definition',\n label: 'Wizard Definition',\n description: 'CPQ wizard definition \u2014 multi-step guided quoting flow.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription',\n label: 'Inventory Subscription',\n description: 'Commercial wrapper grouping subscription items under shared contractual terms.',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription_item',\n label: 'Inventory Subscription Item',\n description: 'Concrete product/service record within an inventory subscription.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_asset',\n label: 'Inventory Asset',\n description: 'Tangible or intangible thing the customer owns \u2014 hardware, licenses, fees.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_configuration',\n label: 'Order Configuration',\n description: 'CPQ-specific configuration data for a sales order.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_line_configuration',\n label: 'Order Line Configuration',\n description: 'CPQ-specific configuration for a sales order line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n]\n\nexport { entities }\nexport default entities\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import type { CustomEntitySpec } from '@open-mercato/shared/modules/entities'\n\nconst entities: CustomEntitySpec[] = [\n {\n id: 'cpq:cpq_quote_price_adjustment',\n label: 'Quote Price Adjustment',\n description: 'Negotiated charge or line discount on a quote.',\n labelField: 'reasonCode',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_specification',\n label: 'Product Specification',\n description: 'CPQ product blueprint \u2014 defines attributes, dependencies, and relationships.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_offering',\n label: 'Product Offering',\n description: 'Sellable instance of a product specification with design-time values and charges.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_relationship',\n label: 'Product Relationship',\n description: 'Structural relationship between product specifications (parent_child, requires, excludes).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_attribute',\n label: 'Product Attribute',\n description: 'Configurable attribute definition for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_product_charge',\n label: 'Product Charge',\n description: 'Charge definition (NRC/MRC/usage) for a CPQ product.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table',\n label: 'Pricing Table',\n description: 'Multi-dimensional pricing table with named price columns.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_pricing_table_entry',\n label: 'Pricing Table Entry',\n description: 'Individual row in a pricing table with dimension values and prices.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_price_rule',\n label: 'Price Rule',\n description: 'Post-calculation price adjustment rule (discount, surcharge, override).',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_bundle_slot',\n label: 'Bundle Slot',\n description: 'Structural slot within a bundle specification \u2014 defines what component specs can fill it.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_offering_component',\n label: 'Offering Component',\n description: 'Links a bundle offering to a specific component offering within a slot.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_configuration',\n label: 'Quote Configuration',\n description: 'CPQ-specific configuration data for a sales quote.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_quote_line_configuration',\n label: 'Quote Line Configuration',\n description: 'CPQ-specific configuration for a sales quote line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_wizard_definition',\n label: 'Wizard Definition',\n description: 'CPQ wizard definition \u2014 multi-step guided quoting flow.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription',\n label: 'Inventory Subscription',\n description: 'Commercial wrapper grouping subscription items under shared contractual terms.',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_subscription_item',\n label: 'Inventory Subscription Item',\n description: 'Concrete product/service record within an inventory subscription.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_inventory_asset',\n label: 'Inventory Asset',\n description: 'Tangible or intangible thing the customer owns \u2014 hardware, licenses, fees.',\n labelField: 'name',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_configuration',\n label: 'Order Configuration',\n description: 'CPQ-specific configuration data for a sales order.',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n {\n id: 'cpq:cpq_order_line_configuration',\n label: 'Order Line Configuration',\n description: 'CPQ-specific configuration for a sales order line (one per offering).',\n labelField: 'id',\n showInSidebar: false,\n fields: [],\n },\n]\n\nexport { entities }\nexport default entities\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/modules/cpq/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
2
2
|
import { seedCpqDemo } from "./lib/seeds/demo.js";
|
|
3
|
+
import { seedPriceAdjustmentDefaults } from "./lib/priceAdjustmentDefaults.js";
|
|
3
4
|
function parseArgs(rest) {
|
|
4
5
|
const out = {};
|
|
5
6
|
for (let i = 0; i < rest.length; i++) {
|
|
@@ -58,7 +59,39 @@ const seed = {
|
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
61
|
};
|
|
61
|
-
|
|
62
|
+
const seedPriceAdjustments = {
|
|
63
|
+
command: "seed-price-adjustments",
|
|
64
|
+
async run(rest) {
|
|
65
|
+
const args = parseArgs(rest);
|
|
66
|
+
const container = await createRequestContainer();
|
|
67
|
+
const em = container.resolve("em");
|
|
68
|
+
const scopes = await resolveAllScopes(em, args);
|
|
69
|
+
if (scopes.length === 0) {
|
|
70
|
+
console.error("[cpq seed-price-adjustments] No matching organization found.");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
let failed = 0;
|
|
74
|
+
for (const scope of scopes) {
|
|
75
|
+
try {
|
|
76
|
+
await seedPriceAdjustmentDefaults(em.fork(), scope);
|
|
77
|
+
console.log(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} \u2713`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
failed++;
|
|
80
|
+
console.error(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} failed`, error);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (failed) process.exitCode = 1;
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
async function resolveAllScopes(em, args) {
|
|
87
|
+
const { Organization } = await import("@open-mercato/core/modules/directory/data/entities");
|
|
88
|
+
const where = { isActive: true, deletedAt: null };
|
|
89
|
+
if (typeof args.org === "string") where.id = args.org;
|
|
90
|
+
if (typeof args.tenant === "string") where.tenant = args.tenant;
|
|
91
|
+
const orgs = await em.find(Organization, where, { orderBy: { createdAt: "asc" } });
|
|
92
|
+
return orgs.map((org) => ({ organizationId: org.id, tenantId: org.tenant.id }));
|
|
93
|
+
}
|
|
94
|
+
var cli_default = [seed, seedPriceAdjustments];
|
|
62
95
|
export {
|
|
63
96
|
cli_default as default
|
|
64
97
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/cpq/cli.ts"],
|
|
4
|
-
"sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { seedCpqDemo } from './lib/seeds/demo'\n\n/**\n * `mercato cpq seed [--reset] [--tenant <id>] [--org <id>]`\n *\n * Loads a minimal, neutral CPQ test catalog (product + specification +\n * offering + charge) into a single tenant. With no `--tenant`/`--org` it\n * resolves the first (primary) organization created by `mercato init`.\n * `--reset` clears the seed's own rows first.\n *\n * For rich, vertical demo catalogs use `mercato demo_tenants seed --all`.\n */\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const token = rest[i]\n if (!token || !token.startsWith('--')) continue\n const key = token.slice(2)\n const next = rest[i + 1]\n if (next === undefined || next.startsWith('--')) out[key] = true\n else {\n out[key] = next\n i++\n }\n }\n return out\n}\n\nasync function resolveScope(\n em: EntityManager,\n args: Record<string, string | boolean>,\n): Promise<{ tenantId: string; organizationId: string } | null> {\n const orgArg = typeof args.org === 'string' ? args.org : undefined\n const tenantArg = typeof args.tenant === 'string' ? args.tenant : undefined\n\n const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')\n\n if (orgArg) {\n const org = await em.findOne(Organization, { id: orgArg } as never)\n if (!org) return null\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n }\n\n const orgs = await em.find(\n Organization,\n { isActive: true } as never,\n { orderBy: { createdAt: 'asc' } as never, limit: 5 },\n )\n if (orgs.length === 0) return null\n const org = orgs[0]\n if (orgs.length > 1) {\n console.warn(\n `[cpq seed] Multiple organizations found \u2014 defaulting to \"${org.name}\" (${org.id}). ` +\n `Pass --org <id> to target another.`,\n )\n }\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n}\n\nconst seed: ModuleCli = {\n command: 'seed',\n async run(rest) {\n const args = parseArgs(rest)\n const reset = Boolean(args.reset)\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n const scope = await resolveScope(em, args)\n if (!scope) {\n console.error('[cpq seed] No organization found. Run \"yarn initialize\" first, or pass --org <id>.')\n return\n }\n\n const res = await seedCpqDemo(em, scope, { reset })\n console.log(\n `[cpq seed] tenant=${scope.tenantId} org=${scope.organizationId}${reset ? ' (reset)' : ''}\\n` +\n ` \u2713 products +${res.productsCreated}, specs +${res.specsCreated}, ` +\n `offerings +${res.offeringsCreated}, charges +${res.chargesCreated} (existing rows left untouched)\\n` +\n ` \u2192 Backend \u00B7 CPQ \u00B7 Product Specifications / Product Offerings.`,\n )\n },\n}\n\nexport default [seed]\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,8BAA8B;AAEvC,SAAS,mBAAmB;
|
|
4
|
+
"sourcesContent": ["import type { ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { seedCpqDemo } from './lib/seeds/demo'\nimport { seedPriceAdjustmentDefaults } from './lib/priceAdjustmentDefaults'\n\n/**\n * `mercato cpq seed [--reset] [--tenant <id>] [--org <id>]`\n *\n * Loads a minimal, neutral CPQ test catalog (product + specification +\n * offering + charge) into a single tenant. With no `--tenant`/`--org` it\n * resolves the first (primary) organization created by `mercato init`.\n * `--reset` clears the seed's own rows first.\n *\n * For rich, vertical demo catalogs use `mercato demo_tenants seed --all`.\n */\n\nfunction parseArgs(rest: string[]): Record<string, string | boolean> {\n const out: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const token = rest[i]\n if (!token || !token.startsWith('--')) continue\n const key = token.slice(2)\n const next = rest[i + 1]\n if (next === undefined || next.startsWith('--')) out[key] = true\n else {\n out[key] = next\n i++\n }\n }\n return out\n}\n\nasync function resolveScope(\n em: EntityManager,\n args: Record<string, string | boolean>,\n): Promise<{ tenantId: string; organizationId: string } | null> {\n const orgArg = typeof args.org === 'string' ? args.org : undefined\n const tenantArg = typeof args.tenant === 'string' ? args.tenant : undefined\n\n const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')\n\n if (orgArg) {\n const org = await em.findOne(Organization, { id: orgArg } as never)\n if (!org) return null\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n }\n\n const orgs = await em.find(\n Organization,\n { isActive: true } as never,\n { orderBy: { createdAt: 'asc' } as never, limit: 5 },\n )\n if (orgs.length === 0) return null\n const org = orgs[0]\n if (orgs.length > 1) {\n console.warn(\n `[cpq seed] Multiple organizations found \u2014 defaulting to \"${org.name}\" (${org.id}). ` +\n `Pass --org <id> to target another.`,\n )\n }\n return { organizationId: org.id, tenantId: tenantArg ?? org.tenant.id }\n}\n\nconst seed: ModuleCli = {\n command: 'seed',\n async run(rest) {\n const args = parseArgs(rest)\n const reset = Boolean(args.reset)\n\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n\n const scope = await resolveScope(em, args)\n if (!scope) {\n console.error('[cpq seed] No organization found. Run \"yarn initialize\" first, or pass --org <id>.')\n return\n }\n\n const res = await seedCpqDemo(em, scope, { reset })\n console.log(\n `[cpq seed] tenant=${scope.tenantId} org=${scope.organizationId}${reset ? ' (reset)' : ''}\\n` +\n ` \u2713 products +${res.productsCreated}, specs +${res.specsCreated}, ` +\n `offerings +${res.offeringsCreated}, charges +${res.chargesCreated} (existing rows left untouched)\\n` +\n ` \u2192 Backend \u00B7 CPQ \u00B7 Product Specifications / Product Offerings.`,\n )\n },\n}\n\n/**\n * `mercato cpq seed-price-adjustments [--tenant <id>] [--org <id>]`\n *\n * Upgrade step for tenants created before negotiated prices shipped: adds the\n * `cpq.manual_price_adjustments` toggle and the `cpq.price_adjustment_reason`\n * dictionary for every active organization (or one, with `--org` /\n * `--tenant`). Idempotent \u2014 existing labels and toggle values are kept.\n * Pair with `mercato auth sync-role-acls` for the `cpq.quotes.adjust_price` grant.\n */\nconst seedPriceAdjustments: ModuleCli = {\n command: 'seed-price-adjustments',\n async run(rest) {\n const args = parseArgs(rest)\n const container = await createRequestContainer()\n const em = container.resolve<EntityManager>('em')\n const scopes = await resolveAllScopes(em, args)\n if (scopes.length === 0) {\n console.error('[cpq seed-price-adjustments] No matching organization found.')\n return\n }\n let failed = 0\n for (const scope of scopes) {\n try {\n await seedPriceAdjustmentDefaults(em.fork(), scope)\n console.log(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} \u2713`)\n } catch (error) {\n failed++\n console.error(`[cpq seed-price-adjustments] tenant=${scope.tenantId} org=${scope.organizationId} failed`, error)\n }\n }\n if (failed) process.exitCode = 1\n },\n}\n\nasync function resolveAllScopes(\n em: EntityManager,\n args: Record<string, string | boolean>,\n): Promise<Array<{ tenantId: string; organizationId: string }>> {\n const { Organization } = await import('@open-mercato/core/modules/directory/data/entities')\n const where: Record<string, unknown> = { isActive: true, deletedAt: null }\n if (typeof args.org === 'string') where.id = args.org\n if (typeof args.tenant === 'string') where.tenant = args.tenant\n const orgs = await em.find(Organization, where as never, { orderBy: { createdAt: 'asc' } as never })\n return orgs.map((org) => ({ organizationId: org.id, tenantId: org.tenant.id }))\n}\n\nexport default [seed, seedPriceAdjustments]\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["org"]
|
|
7
7
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveAuditActor } from "../lib/auditActor.js";
|
|
3
|
+
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
4
|
+
import { extractUndoPayload } from "@open-mercato/shared/lib/commands/undo";
|
|
5
|
+
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
6
|
+
import { cpqPriceAdjustmentInputSchema, cpqPriceAdjustmentPatchSchema } from "../data/price-adjustments.js";
|
|
7
|
+
import { QuotingError } from "../services/cpqQuotingService.js";
|
|
8
|
+
const id = z.string().uuid();
|
|
9
|
+
const createSchema = z.object({ quoteId: id, adjustment: cpqPriceAdjustmentInputSchema }).strict();
|
|
10
|
+
const updateSchema = z.object({ quoteId: id, adjustmentId: id, patch: cpqPriceAdjustmentPatchSchema }).strict();
|
|
11
|
+
const deleteSchema = z.object({ quoteId: id, adjustmentId: id, expectedRevision: z.number().int().min(0).optional() }).strict();
|
|
12
|
+
const snapshotSchema = z.object({
|
|
13
|
+
id,
|
|
14
|
+
quoteConfigurationId: id,
|
|
15
|
+
quoteId: id,
|
|
16
|
+
tenantId: id,
|
|
17
|
+
organizationId: id,
|
|
18
|
+
createdByUserId: id,
|
|
19
|
+
createdAt: z.string().datetime(),
|
|
20
|
+
updatedAt: z.string().datetime(),
|
|
21
|
+
deletedAt: z.string().datetime().nullable(),
|
|
22
|
+
isActive: z.boolean(),
|
|
23
|
+
level: z.enum(["charge", "line"]),
|
|
24
|
+
quoteLineId: id,
|
|
25
|
+
chargeCode: z.string().nullish(),
|
|
26
|
+
chargeTypeFilter: z.enum(["nrc", "mrc"]).nullish(),
|
|
27
|
+
adjustmentType: z.enum(["discount_percent", "discount_absolute", "price_override"]),
|
|
28
|
+
value: z.number(),
|
|
29
|
+
currencyCode: z.string().nullish(),
|
|
30
|
+
reasonCode: z.string(),
|
|
31
|
+
reasonNote: z.string().nullish()
|
|
32
|
+
});
|
|
33
|
+
const undoSchema = z.object({ quoteId: id, before: snapshotSchema.nullable(), after: snapshotSchema });
|
|
34
|
+
async function context(ctx) {
|
|
35
|
+
const subject = ctx.auth?.sub;
|
|
36
|
+
const actor = resolveAuditActor(ctx.auth);
|
|
37
|
+
const tenantId = ctx.organizationScope?.tenantId ?? ctx.auth?.tenantId;
|
|
38
|
+
const organizationId = ctx.selectedOrganizationId;
|
|
39
|
+
if (!subject || !tenantId || !organizationId) throw new QuotingError(403, "An authenticated tenant and organization are required");
|
|
40
|
+
if (!actor) throw new QuotingError(403, "Price adjustments require a user or a user-bound API key");
|
|
41
|
+
const scope = { tenantId, organizationId };
|
|
42
|
+
const rbac = ctx.container.resolve("rbacService");
|
|
43
|
+
if (!await rbac.userHasAllFeatures(subject, ["cpq.quotes.manage", "cpq.quotes.adjust_price"], scope)) throw new QuotingError(403, "Price adjustment permission required");
|
|
44
|
+
return { actor, scope, service: ctx.container.resolve("cpqAdjustmentService") };
|
|
45
|
+
}
|
|
46
|
+
async function buildLog(quoteId, result) {
|
|
47
|
+
const { translate } = await resolveTranslations();
|
|
48
|
+
return {
|
|
49
|
+
actionLabel: translate("cpq.priceAdjustments.audit.adjust", "Adjust quote price"),
|
|
50
|
+
resourceKind: "cpq.quote_price_adjustment",
|
|
51
|
+
resourceId: result.adjustment.id,
|
|
52
|
+
tenantId: result.adjustment.tenantId,
|
|
53
|
+
organizationId: result.adjustment.organizationId,
|
|
54
|
+
snapshotBefore: result.before,
|
|
55
|
+
snapshotAfter: result.adjustment,
|
|
56
|
+
payload: { undo: { quoteId, before: result.before, after: result.adjustment } }
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
async function undo({ logEntry, ctx }) {
|
|
60
|
+
const envelope = undoSchema.safeParse(extractUndoPayload(logEntry));
|
|
61
|
+
if (!envelope.success) throw new QuotingError(422, "Invalid adjustment undo data");
|
|
62
|
+
const { actor, scope, service } = await context(ctx);
|
|
63
|
+
const { quoteId, before, after } = envelope.data;
|
|
64
|
+
await service.undo(quoteId, after.id, before, after, actor, scope);
|
|
65
|
+
}
|
|
66
|
+
const createAdjustmentCommand = {
|
|
67
|
+
id: "cpq.quote_price_adjustment.create",
|
|
68
|
+
isUndoable: true,
|
|
69
|
+
async execute(input, ctx) {
|
|
70
|
+
const parsed = createSchema.parse(input);
|
|
71
|
+
const { actor, scope, service } = await context(ctx);
|
|
72
|
+
return service.create(parsed.quoteId, parsed.adjustment, actor, scope);
|
|
73
|
+
},
|
|
74
|
+
buildLog: ({ input, result }) => buildLog(input.quoteId, result),
|
|
75
|
+
undo
|
|
76
|
+
};
|
|
77
|
+
const updateAdjustmentCommand = {
|
|
78
|
+
id: "cpq.quote_price_adjustment.update",
|
|
79
|
+
isUndoable: true,
|
|
80
|
+
async execute(input, ctx) {
|
|
81
|
+
const parsed = updateSchema.parse(input);
|
|
82
|
+
const { actor, scope, service } = await context(ctx);
|
|
83
|
+
return service.update(parsed.quoteId, parsed.adjustmentId, parsed.patch, actor, scope);
|
|
84
|
+
},
|
|
85
|
+
buildLog: ({ input, result }) => buildLog(input.quoteId, result),
|
|
86
|
+
undo
|
|
87
|
+
};
|
|
88
|
+
const deleteAdjustmentCommand = {
|
|
89
|
+
id: "cpq.quote_price_adjustment.delete",
|
|
90
|
+
isUndoable: true,
|
|
91
|
+
async execute(input, ctx) {
|
|
92
|
+
const parsed = deleteSchema.parse(input);
|
|
93
|
+
const { actor, scope, service } = await context(ctx);
|
|
94
|
+
return service.remove(parsed.quoteId, parsed.adjustmentId, actor, scope, parsed.expectedRevision);
|
|
95
|
+
},
|
|
96
|
+
buildLog: ({ input, result }) => buildLog(input.quoteId, result),
|
|
97
|
+
undo
|
|
98
|
+
};
|
|
99
|
+
registerCommand(createAdjustmentCommand);
|
|
100
|
+
registerCommand(updateAdjustmentCommand);
|
|
101
|
+
registerCommand(deleteAdjustmentCommand);
|
|
102
|
+
export {
|
|
103
|
+
createAdjustmentCommand,
|
|
104
|
+
deleteAdjustmentCommand,
|
|
105
|
+
updateAdjustmentCommand
|
|
106
|
+
};
|
|
107
|
+
//# sourceMappingURL=price-adjustments.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/commands/price-adjustments.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { resolveAuditActor } from '../lib/auditActor'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { extractUndoPayload } from '@open-mercato/shared/lib/commands/undo'\nimport { registerCommand, type CommandHandler, type CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { cpqPriceAdjustmentInputSchema, cpqPriceAdjustmentPatchSchema } from '../data/price-adjustments'\nimport { QuotingError } from '../services/cpqQuotingService'\nimport type { CpqAdjustmentService, AdjustmentWriteResult, AdjustmentSnapshot } from '../services/priceAdjustments/adjustmentService'\n\nconst id = z.string().uuid()\nconst createSchema = z.object({ quoteId: id, adjustment: cpqPriceAdjustmentInputSchema }).strict()\nconst updateSchema = z.object({ quoteId: id, adjustmentId: id, patch: cpqPriceAdjustmentPatchSchema }).strict()\nconst deleteSchema = z.object({ quoteId: id, adjustmentId: id, expectedRevision: z.number().int().min(0).optional() }).strict()\n// Undo data is parsed independently of the original request and never trusted\n// to supply actor/scope. Its target is checked again under the aggregate lock.\nconst snapshotSchema = z.object({\n id, quoteConfigurationId: id, quoteId: id, tenantId: id, organizationId: id,\n createdByUserId: id, createdAt: z.string().datetime(), updatedAt: z.string().datetime(),\n deletedAt: z.string().datetime().nullable(), isActive: z.boolean(),\n level: z.enum(['charge', 'line']), quoteLineId: id, chargeCode: z.string().nullish(),\n chargeTypeFilter: z.enum(['nrc', 'mrc']).nullish(), adjustmentType: z.enum(['discount_percent', 'discount_absolute', 'price_override']),\n value: z.number(), currencyCode: z.string().nullish(), reasonCode: z.string(), reasonNote: z.string().nullish(),\n})\nconst undoSchema = z.object({ quoteId: id, before: snapshotSchema.nullable(), after: snapshotSchema })\n\nasync function context(ctx: CommandRuntimeContext) {\n // RBAC is checked for the authenticated subject (user or API key); the\n // stored author must be a user id (uuid columns).\n const subject = ctx.auth?.sub\n const actor = resolveAuditActor(ctx.auth)\n const tenantId = ctx.organizationScope?.tenantId ?? ctx.auth?.tenantId\n const organizationId = ctx.selectedOrganizationId\n if (!subject || !tenantId || !organizationId) throw new QuotingError(403, 'An authenticated tenant and organization are required')\n if (!actor) throw new QuotingError(403, 'Price adjustments require a user or a user-bound API key')\n const scope = { tenantId, organizationId }\n const rbac = ctx.container.resolve<RbacService>('rbacService')\n if (!(await rbac.userHasAllFeatures(subject, ['cpq.quotes.manage', 'cpq.quotes.adjust_price'], scope))) throw new QuotingError(403, 'Price adjustment permission required')\n return { actor, scope, service: ctx.container.resolve<CpqAdjustmentService>('cpqAdjustmentService') }\n}\n\nasync function buildLog(quoteId: string, result: AdjustmentWriteResult) {\n const { translate } = await resolveTranslations()\n return { actionLabel: translate('cpq.priceAdjustments.audit.adjust', 'Adjust quote price'), resourceKind: 'cpq.quote_price_adjustment', resourceId: result.adjustment.id,\n tenantId: result.adjustment.tenantId, organizationId: result.adjustment.organizationId,\n snapshotBefore: result.before, snapshotAfter: result.adjustment,\n payload: { undo: { quoteId, before: result.before, after: result.adjustment } } }\n}\n\nasync function undo({ logEntry, ctx }: Parameters<NonNullable<CommandHandler['undo']>>[0]) {\n const envelope = undoSchema.safeParse(extractUndoPayload<unknown>(logEntry))\n if (!envelope.success) throw new QuotingError(422, 'Invalid adjustment undo data')\n const { actor, scope, service } = await context(ctx)\n const { quoteId, before, after } = envelope.data\n await service.undo(quoteId, after.id, before as AdjustmentSnapshot | null, after as AdjustmentSnapshot, actor, scope)\n}\n\nexport const createAdjustmentCommand: CommandHandler<z.infer<typeof createSchema>, AdjustmentWriteResult> = {\n id: 'cpq.quote_price_adjustment.create', isUndoable: true,\n async execute(input, ctx) {\n const parsed = createSchema.parse(input)\n const { actor, scope, service } = await context(ctx)\n return service.create(parsed.quoteId, parsed.adjustment, actor, scope)\n },\n buildLog: ({ input, result }) => buildLog(input.quoteId, result),\n undo,\n}\nexport const updateAdjustmentCommand: CommandHandler<z.infer<typeof updateSchema>, AdjustmentWriteResult> = {\n id: 'cpq.quote_price_adjustment.update', isUndoable: true,\n async execute(input, ctx) {\n const parsed = updateSchema.parse(input)\n const { actor, scope, service } = await context(ctx)\n return service.update(parsed.quoteId, parsed.adjustmentId, parsed.patch, actor, scope)\n },\n buildLog: ({ input, result }) => buildLog(input.quoteId, result),\n undo,\n}\nexport const deleteAdjustmentCommand: CommandHandler<z.infer<typeof deleteSchema>, AdjustmentWriteResult> = {\n id: 'cpq.quote_price_adjustment.delete', isUndoable: true,\n async execute(input, ctx) {\n const parsed = deleteSchema.parse(input)\n const { actor, scope, service } = await context(ctx)\n return service.remove(parsed.quoteId, parsed.adjustmentId, actor, scope, parsed.expectedRevision)\n },\n buildLog: ({ input, result }) => buildLog(input.quoteId, result),\n undo,\n}\nregisterCommand(createAdjustmentCommand)\nregisterCommand(updateAdjustmentCommand)\nregisterCommand(deleteAdjustmentCommand)\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
5
|
+
import { formatMoney } from "../lib/formatMoney.js";
|
|
6
|
+
const ACTION_VARIANTS = {
|
|
7
|
+
add: "info",
|
|
8
|
+
modify: "warning",
|
|
9
|
+
cancel: "error"
|
|
10
|
+
};
|
|
11
|
+
function LineActionBadge({ action, label, title }) {
|
|
12
|
+
const variant = ACTION_VARIANTS[action] ?? "info";
|
|
13
|
+
return /* @__PURE__ */ jsx(Tag, { variant, className: "px-1.5 text-xs", title, children: label ?? action });
|
|
14
|
+
}
|
|
15
|
+
function ArcLineDiff({ line, currency }) {
|
|
16
|
+
const t = useT();
|
|
17
|
+
const src = line.arcSource;
|
|
18
|
+
if (!src) return null;
|
|
19
|
+
const isCancel = line.action === "cancel";
|
|
20
|
+
const rows = [
|
|
21
|
+
{ label: t("cpq.arc.diff.quantity", "Quantity"), before: src.quantity, after: isCancel ? 0 : line.quantity, asCurrency: false },
|
|
22
|
+
{ label: "MRC", before: src.mrcAmount, after: isCancel ? 0 : line.mrcTotal, asCurrency: true },
|
|
23
|
+
{ label: "NRC", before: src.nrcAmount, after: isCancel ? 0 : line.nrcTotal, asCurrency: true }
|
|
24
|
+
];
|
|
25
|
+
const fmtVal = (value, asCurrency) => asCurrency ? formatMoney(value, currency) : String(value);
|
|
26
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", "data-testid": "cpq-arc-line-diff", children: [
|
|
27
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: t("cpq.arc.diff.title", "Change Preview") }),
|
|
28
|
+
/* @__PURE__ */ jsx("div", { className: "rounded border bg-background overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
|
|
29
|
+
/* @__PURE__ */ jsx("thead", { className: "bg-muted/40", children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
|
|
30
|
+
/* @__PURE__ */ jsx("th", { className: "text-left px-3 py-1.5 font-medium" }),
|
|
31
|
+
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: t("cpq.arc.diff.before", "Before") }),
|
|
32
|
+
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: t("cpq.arc.diff.after", "After") }),
|
|
33
|
+
/* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "\u0394" })
|
|
34
|
+
] }) }),
|
|
35
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((r) => {
|
|
36
|
+
const delta = r.after - r.before;
|
|
37
|
+
const showDelta = delta !== 0;
|
|
38
|
+
return /* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/40", children: [
|
|
39
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-muted-foreground", children: r.label }),
|
|
40
|
+
/* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-right font-mono", children: fmtVal(r.before, r.asCurrency) }),
|
|
41
|
+
/* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono ${isCancel ? "text-muted-foreground line-through" : ""}`, children: fmtVal(r.after, r.asCurrency) }),
|
|
42
|
+
/* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono font-medium ${showDelta ? delta > 0 ? "text-status-success-text" : "text-destructive" : "text-muted-foreground"}`, children: showDelta ? `${delta > 0 ? "+" : ""}${fmtVal(delta, r.asCurrency)}` : "\u2014" })
|
|
43
|
+
] }, r.label);
|
|
44
|
+
}) })
|
|
45
|
+
] }) }),
|
|
46
|
+
isCancel && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive italic", children: t("cpq.arc.diff.cancelNote", "Cancellation \u2014 item will be terminated on activation.") })
|
|
47
|
+
] });
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
ArcLineDiff,
|
|
51
|
+
LineActionBadge
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=ArcLineDiff.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/cpq/components/ArcLineDiff.tsx"],
|
|
4
|
+
"sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { formatMoney } from '../lib/formatMoney'\n\nexport type LineAction = 'add' | 'modify' | 'cancel'\n\nconst ACTION_VARIANTS: Record<LineAction, 'info' | 'warning' | 'error'> = {\n add: 'info',\n modify: 'warning',\n cancel: 'error',\n}\n\n/** Coloured add / modify / cancel tag on quote and order lines. */\nexport function LineActionBadge({ action, label, title }: { action: string; label?: string; title?: string }) {\n const variant = ACTION_VARIANTS[action as LineAction] ?? 'info'\n return (\n <Tag variant={variant} className=\"px-1.5 text-xs\" title={title}>\n {label ?? action}\n </Tag>\n )\n}\n\nexport type ArcDiffLine = {\n action: string\n quantity: number\n nrcTotal: number\n mrcTotal: number\n arcSource: { mrcAmount: number; nrcAmount: number; quantity: number } | null\n}\n\n/** Before / after / \u0394 preview for a line that changes an existing subscription item. */\nexport function ArcLineDiff({ line, currency }: { line: ArcDiffLine; currency: string }) {\n const t = useT()\n const src = line.arcSource\n if (!src) return null\n const isCancel = line.action === 'cancel'\n const rows: Array<{ label: string; before: number; after: number; asCurrency: boolean }> = [\n { label: t('cpq.arc.diff.quantity', 'Quantity'), before: src.quantity, after: isCancel ? 0 : line.quantity, asCurrency: false },\n { label: 'MRC', before: src.mrcAmount, after: isCancel ? 0 : line.mrcTotal, asCurrency: true },\n { label: 'NRC', before: src.nrcAmount, after: isCancel ? 0 : line.nrcTotal, asCurrency: true },\n ]\n const fmtVal = (value: number, asCurrency: boolean) => (asCurrency ? formatMoney(value, currency) : String(value))\n\n return (\n <div className=\"space-y-1.5\" data-testid=\"cpq-arc-line-diff\">\n <p className=\"text-xs font-medium text-muted-foreground\">{t('cpq.arc.diff.title', 'Change Preview')}</p>\n <div className=\"rounded border bg-background overflow-hidden\">\n <table className=\"w-full text-xs\">\n <thead className=\"bg-muted/40\">\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left px-3 py-1.5 font-medium\" />\n <th className=\"text-right px-3 py-1.5 font-medium\">{t('cpq.arc.diff.before', 'Before')}</th>\n <th className=\"text-right px-3 py-1.5 font-medium\">{t('cpq.arc.diff.after', 'After')}</th>\n <th className=\"text-right px-3 py-1.5 font-medium\">\u0394</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((r) => {\n const delta = r.after - r.before\n const showDelta = delta !== 0\n return (\n <tr key={r.label} className=\"border-t border-dashed border-border/40\">\n <td className=\"px-3 py-1 text-muted-foreground\">{r.label}</td>\n <td className=\"px-3 py-1 text-right font-mono\">{fmtVal(r.before, r.asCurrency)}</td>\n <td className={`px-3 py-1 text-right font-mono ${isCancel ? 'text-muted-foreground line-through' : ''}`}>\n {fmtVal(r.after, r.asCurrency)}\n </td>\n <td className={`px-3 py-1 text-right font-mono font-medium ${showDelta ? (delta > 0 ? 'text-status-success-text' : 'text-destructive') : 'text-muted-foreground'}`}>\n {showDelta ? `${delta > 0 ? '+' : ''}${fmtVal(delta, r.asCurrency)}` : '\u2014'}\n </td>\n </tr>\n )\n })}\n </tbody>\n </table>\n </div>\n {isCancel && (\n <p className=\"text-xs text-destructive italic\">\n {t('cpq.arc.diff.cancelNote', 'Cancellation \u2014 item will be terminated on activation.')}\n </p>\n )}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAmBI,cAiCQ,YAjCR;AAhBJ,SAAS,YAAY;AACrB,SAAS,WAAW;AACpB,SAAS,mBAAmB;AAI5B,MAAM,kBAAoE;AAAA,EACxE,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,QAAQ;AACV;AAGO,SAAS,gBAAgB,EAAE,QAAQ,OAAO,MAAM,GAAuD;AAC5G,QAAM,UAAU,gBAAgB,MAAoB,KAAK;AACzD,SACE,oBAAC,OAAI,SAAkB,WAAU,kBAAiB,OAC/C,mBAAS,QACZ;AAEJ;AAWO,SAAS,YAAY,EAAE,MAAM,SAAS,GAA4C;AACvF,QAAM,IAAI,KAAK;AACf,QAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,WAAW,KAAK,WAAW;AACjC,QAAM,OAAqF;AAAA,IACzF,EAAE,OAAO,EAAE,yBAAyB,UAAU,GAAG,QAAQ,IAAI,UAAU,OAAO,WAAW,IAAI,KAAK,UAAU,YAAY,MAAM;AAAA,IAC9H,EAAE,OAAO,OAAO,QAAQ,IAAI,WAAW,OAAO,WAAW,IAAI,KAAK,UAAU,YAAY,KAAK;AAAA,IAC7F,EAAE,OAAO,OAAO,QAAQ,IAAI,WAAW,OAAO,WAAW,IAAI,KAAK,UAAU,YAAY,KAAK;AAAA,EAC/F;AACA,QAAM,SAAS,CAAC,OAAe,eAAyB,aAAa,YAAY,OAAO,QAAQ,IAAI,OAAO,KAAK;AAEhH,SACE,qBAAC,SAAI,WAAU,eAAc,eAAY,qBACvC;AAAA,wBAAC,OAAE,WAAU,6CAA6C,YAAE,sBAAsB,gBAAgB,GAAE;AAAA,IACpG,oBAAC,SAAI,WAAU,gDACb,+BAAC,WAAM,WAAU,kBACf;AAAA,0BAAC,WAAM,WAAU,eACf,+BAAC,QAAG,WAAU,yBACZ;AAAA,4BAAC,QAAG,WAAU,qCAAoC;AAAA,QAClD,oBAAC,QAAG,WAAU,sCAAsC,YAAE,uBAAuB,QAAQ,GAAE;AAAA,QACvF,oBAAC,QAAG,WAAU,sCAAsC,YAAE,sBAAsB,OAAO,GAAE;AAAA,QACrF,oBAAC,QAAG,WAAU,sCAAqC,oBAAC;AAAA,SACtD,GACF;AAAA,MACA,oBAAC,WACE,eAAK,IAAI,CAAC,MAAM;AACf,cAAM,QAAQ,EAAE,QAAQ,EAAE;AAC1B,cAAM,YAAY,UAAU;AAC5B,eACE,qBAAC,QAAiB,WAAU,2CAC1B;AAAA,8BAAC,QAAG,WAAU,mCAAmC,YAAE,OAAM;AAAA,UACzD,oBAAC,QAAG,WAAU,kCAAkC,iBAAO,EAAE,QAAQ,EAAE,UAAU,GAAE;AAAA,UAC/E,oBAAC,QAAG,WAAW,kCAAkC,WAAW,uCAAuC,EAAE,IAClG,iBAAO,EAAE,OAAO,EAAE,UAAU,GAC/B;AAAA,UACA,oBAAC,QAAG,WAAW,8CAA8C,YAAa,QAAQ,IAAI,6BAA6B,qBAAsB,uBAAuB,IAC7J,sBAAY,GAAG,QAAQ,IAAI,MAAM,EAAE,GAAG,OAAO,OAAO,EAAE,UAAU,CAAC,KAAK,UACzE;AAAA,aARO,EAAE,KASX;AAAA,MAEJ,CAAC,GACH;AAAA,OACF,GACF;AAAA,IACC,YACC,oBAAC,OAAE,WAAU,mCACV,YAAE,2BAA2B,4DAAuD,GACvF;AAAA,KAEJ;AAEJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|