@dainamite/cpq 0.2.2
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/LICENSE +21 -0
- package/MIGRATION.md +236 -0
- package/README.md +108 -0
- package/dist/modules/cpq/acl.js +30 -0
- package/dist/modules/cpq/acl.js.map +7 -0
- package/dist/modules/cpq/api/_helpers/handleArcError.js +37 -0
- package/dist/modules/cpq/api/_helpers/handleArcError.js.map +7 -0
- package/dist/modules/cpq/api/attribute-options/route.js +47 -0
- package/dist/modules/cpq/api/attribute-options/route.js.map +7 -0
- package/dist/modules/cpq/api/bundle-slots/route.js +101 -0
- package/dist/modules/cpq/api/bundle-slots/route.js.map +7 -0
- package/dist/modules/cpq/api/bundle-tree/route.js +28 -0
- package/dist/modules/cpq/api/bundle-tree/route.js.map +7 -0
- package/dist/modules/cpq/api/context.js +29 -0
- package/dist/modules/cpq/api/context.js.map +7 -0
- package/dist/modules/cpq/api/inventory/assets/route.js +113 -0
- package/dist/modules/cpq/api/inventory/assets/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/assets/status/route.js +35 -0
- package/dist/modules/cpq/api/inventory/assets/status/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js +30 -0
- package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js +31 -0
- package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/subscription-items/route.js +99 -0
- package/dist/modules/cpq/api/inventory/subscription-items/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js +57 -0
- package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js +62 -0
- package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/subscriptions/route.js +116 -0
- package/dist/modules/cpq/api/inventory/subscriptions/route.js.map +7 -0
- package/dist/modules/cpq/api/inventory/subscriptions/status/route.js +35 -0
- package/dist/modules/cpq/api/inventory/subscriptions/status/route.js.map +7 -0
- package/dist/modules/cpq/api/offering-components/route.js +91 -0
- package/dist/modules/cpq/api/offering-components/route.js.map +7 -0
- package/dist/modules/cpq/api/orders/[orderId]/activate/route.js +39 -0
- package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +7 -0
- package/dist/modules/cpq/api/orders/[orderId]/route.js +28 -0
- package/dist/modules/cpq/api/orders/[orderId]/route.js.map +7 -0
- package/dist/modules/cpq/api/orders/[orderId]/status/route.js +38 -0
- package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +7 -0
- package/dist/modules/cpq/api/orders/route.js +71 -0
- package/dist/modules/cpq/api/orders/route.js.map +7 -0
- package/dist/modules/cpq/api/price-rules/route.js +180 -0
- package/dist/modules/cpq/api/price-rules/route.js.map +7 -0
- package/dist/modules/cpq/api/pricing-table-entries/route.js +142 -0
- package/dist/modules/cpq/api/pricing-table-entries/route.js.map +7 -0
- package/dist/modules/cpq/api/pricing-tables/route.js +179 -0
- package/dist/modules/cpq/api/pricing-tables/route.js.map +7 -0
- package/dist/modules/cpq/api/product-attributes/route.js +160 -0
- package/dist/modules/cpq/api/product-attributes/route.js.map +7 -0
- package/dist/modules/cpq/api/product-charges/route.js +166 -0
- package/dist/modules/cpq/api/product-charges/route.js.map +7 -0
- package/dist/modules/cpq/api/product-offerings/route.js +294 -0
- package/dist/modules/cpq/api/product-offerings/route.js.map +7 -0
- package/dist/modules/cpq/api/product-relationships/route.js +144 -0
- package/dist/modules/cpq/api/product-relationships/route.js.map +7 -0
- package/dist/modules/cpq/api/product-specifications/route.js +292 -0
- package/dist/modules/cpq/api/product-specifications/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js +71 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +29 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +54 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js +72 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +35 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/route.js +102 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +27 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js +79 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js +95 -0
- package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/attributes/route.js +52 -0
- package/dist/modules/cpq/api/quotes/attributes/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/from-subscription/route.js +72 -0
- package/dist/modules/cpq/api/quotes/from-subscription/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/price/route.js +69 -0
- package/dist/modules/cpq/api/quotes/price/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/route.js +97 -0
- package/dist/modules/cpq/api/quotes/route.js.map +7 -0
- package/dist/modules/cpq/api/quotes/validate-relationships/route.js +86 -0
- package/dist/modules/cpq/api/quotes/validate-relationships/route.js.map +7 -0
- package/dist/modules/cpq/api/resolveQuotingService.js +7 -0
- package/dist/modules/cpq/api/resolveQuotingService.js.map +7 -0
- package/dist/modules/cpq/api/wizards/resolve/route.js +38 -0
- package/dist/modules/cpq/api/wizards/resolve/route.js.map +7 -0
- package/dist/modules/cpq/api/wizards/route.js +111 -0
- package/dist/modules/cpq/api/wizards/route.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +136 -0
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +185 -0
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +343 -0
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +13 -0
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/page.js +35 -0
- package/dist/modules/cpq/backend/cpq/inventory/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/shared.js +20 -0
- package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +794 -0
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +13 -0
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +917 -0
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/offerings/page.js +405 -0
- package/dist/modules/cpq/backend/cpq/offerings/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/offerings/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/offerings/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +677 -0
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/orders/page.js +134 -0
- package/dist/modules/cpq/backend/cpq/orders/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +272 -0
- package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +407 -0
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +13 -0
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/price-rules/page.js +211 -0
- package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +275 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +112 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +492 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js +50 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +249 -0
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/pricing/page.js +87 -0
- package/dist/modules/cpq/backend/cpq/pricing/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/pricing/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/pricing/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +652 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +1289 -0
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/page.js +283 -0
- package/dist/modules/cpq/backend/cpq/quotes/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +214 -0
- package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +1399 -0
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/specifications/page.js +148 -0
- package/dist/modules/cpq/backend/cpq/specifications/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/specifications/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/specifications/page.meta.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +204 -0
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +191 -0
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/wizards/page.js +119 -0
- package/dist/modules/cpq/backend/cpq/wizards/page.js.map +7 -0
- package/dist/modules/cpq/backend/cpq/wizards/page.meta.js +18 -0
- package/dist/modules/cpq/backend/cpq/wizards/page.meta.js.map +7 -0
- package/dist/modules/cpq/ce.js +151 -0
- package/dist/modules/cpq/ce.js.map +7 -0
- package/dist/modules/cpq/cli.js +11 -0
- package/dist/modules/cpq/cli.js.map +7 -0
- package/dist/modules/cpq/components/CpqListView.js +265 -0
- package/dist/modules/cpq/components/CpqListView.js.map +7 -0
- package/dist/modules/cpq/components/NumberInput.js +95 -0
- package/dist/modules/cpq/components/NumberInput.js.map +7 -0
- package/dist/modules/cpq/components/StatusPath.js +60 -0
- package/dist/modules/cpq/components/StatusPath.js.map +7 -0
- package/dist/modules/cpq/components/statusMaps.js +66 -0
- package/dist/modules/cpq/components/statusMaps.js.map +7 -0
- package/dist/modules/cpq/components/useCpqRowActions.js +125 -0
- package/dist/modules/cpq/components/useCpqRowActions.js.map +7 -0
- package/dist/modules/cpq/data/entities.js +1556 -0
- package/dist/modules/cpq/data/entities.js.map +7 -0
- package/dist/modules/cpq/data/validators.js +575 -0
- package/dist/modules/cpq/data/validators.js.map +7 -0
- package/dist/modules/cpq/di.js +57 -0
- package/dist/modules/cpq/di.js.map +7 -0
- package/dist/modules/cpq/events.js +50 -0
- package/dist/modules/cpq/events.js.map +7 -0
- package/dist/modules/cpq/index.js +16 -0
- package/dist/modules/cpq/index.js.map +7 -0
- package/dist/modules/cpq/lib/seeds/api.js +32 -0
- package/dist/modules/cpq/lib/seeds/api.js.map +7 -0
- package/dist/modules/cpq/lib/seeds/tenant-provisioning.js +158 -0
- package/dist/modules/cpq/lib/seeds/tenant-provisioning.js.map +7 -0
- package/dist/modules/cpq/lib/seeds.js +6 -0
- package/dist/modules/cpq/lib/seeds.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js +40 -0
- package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js +54 -0
- package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js +15 -0
- package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js +62 -0
- package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js +56 -0
- package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js +26 -0
- package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js +37 -0
- package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js +35 -0
- package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js +21 -0
- package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js +46 -0
- package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js +118 -0
- package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js +63 -0
- package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js +28 -0
- package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js +36 -0
- package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js +14 -0
- package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js +14 -0
- package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js +58 -0
- package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js.map +7 -0
- package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js +29 -0
- package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js.map +7 -0
- package/dist/modules/cpq/services/cpqBundleService.js +447 -0
- package/dist/modules/cpq/services/cpqBundleService.js.map +7 -0
- package/dist/modules/cpq/services/cpqInventoryService.js +1371 -0
- package/dist/modules/cpq/services/cpqInventoryService.js.map +7 -0
- package/dist/modules/cpq/services/cpqOrderService.js +901 -0
- package/dist/modules/cpq/services/cpqOrderService.js.map +7 -0
- package/dist/modules/cpq/services/cpqPricingService.js +289 -0
- package/dist/modules/cpq/services/cpqPricingService.js.map +7 -0
- package/dist/modules/cpq/services/cpqProductService.js +204 -0
- package/dist/modules/cpq/services/cpqProductService.js.map +7 -0
- package/dist/modules/cpq/services/cpqQuotingService.js +1867 -0
- package/dist/modules/cpq/services/cpqQuotingService.js.map +7 -0
- package/dist/modules/cpq/services/cpqValidationService.js +234 -0
- package/dist/modules/cpq/services/cpqValidationService.js.map +7 -0
- package/dist/modules/cpq/services/cpqWizardService.js +137 -0
- package/dist/modules/cpq/services/cpqWizardService.js.map +7 -0
- package/dist/modules/cpq/services/types.js +114 -0
- package/dist/modules/cpq/services/types.js.map +7 -0
- package/dist/modules/cpq/setup.js +49 -0
- package/dist/modules/cpq/setup.js.map +7 -0
- package/dist/modules/cpq/subscribers/subscription-amended-log.js +18 -0
- package/dist/modules/cpq/subscribers/subscription-amended-log.js.map +7 -0
- package/dist/modules/cpq/subscribers/subscription-cancelled-log.js +19 -0
- package/dist/modules/cpq/subscribers/subscription-cancelled-log.js.map +7 -0
- package/dist/modules/cpq/subscribers/subscription-merged-log.js +20 -0
- package/dist/modules/cpq/subscribers/subscription-merged-log.js.map +7 -0
- package/dist/modules/cpq/subscribers/subscription-renewed-log.js +18 -0
- package/dist/modules/cpq/subscribers/subscription-renewed-log.js.map +7 -0
- package/dist/modules/cpq/subscribers/subscription-superseded-log.js +18 -0
- package/dist/modules/cpq/subscribers/subscription-superseded-log.js.map +7 -0
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +192 -0
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +7 -0
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js +17 -0
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js.map +7 -0
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +79 -0
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +7 -0
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js +17 -0
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js.map +7 -0
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +181 -0
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +7 -0
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js +17 -0
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js.map +7 -0
- package/dist/modules/cpq/widgets/injection-table.js +44 -0
- package/dist/modules/cpq/widgets/injection-table.js.map +7 -0
- package/dist/modules/cpq/workflows/WizardRunner.js +113 -0
- package/dist/modules/cpq/workflows/WizardRunner.js.map +7 -0
- package/dist/modules/cpq/workflows/WizardTrigger.js +116 -0
- package/dist/modules/cpq/workflows/WizardTrigger.js.map +7 -0
- package/dist/modules/cpq/workflows/engine.js +188 -0
- package/dist/modules/cpq/workflows/engine.js.map +7 -0
- package/dist/modules/cpq/workflows/registry.js +16 -0
- package/dist/modules/cpq/workflows/registry.js.map +7 -0
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +119 -0
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +7 -0
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +24 -0
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +7 -0
- package/dist/modules/cpq/workflows/steps/ItemListStep.js +115 -0
- package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +7 -0
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +327 -0
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +7 -0
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +231 -0
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +7 -0
- package/dist/modules/cpq/workflows/steps/ReviewStep.js +152 -0
- package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +7 -0
- package/dist/modules/cpq/workflows/steps/index.js +49 -0
- package/dist/modules/cpq/workflows/steps/index.js.map +7 -0
- package/dist/modules/cpq/workflows/types.js +1 -0
- package/dist/modules/cpq/workflows/types.js.map +7 -0
- package/package.json +150 -0
- package/src/modules/cpq/__integration__/TC-CPQ-001.spec.ts +177 -0
- package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +123 -0
- package/src/modules/cpq/__integration__/TC-CPQ-003.spec.ts +115 -0
- package/src/modules/cpq/__integration__/TC-CPQ-004.spec.ts +184 -0
- package/src/modules/cpq/__integration__/TC-CPQ-005.spec.ts +43 -0
- package/src/modules/cpq/__integration__/TC-CPQ-006.spec.ts +87 -0
- package/src/modules/cpq/__integration__/TC-CPQ-007.spec.ts +79 -0
- package/src/modules/cpq/__integration__/TC-CPQ-008.spec.ts +55 -0
- package/src/modules/cpq/__integration__/TC-CPQ-009.spec.ts +45 -0
- package/src/modules/cpq/__integration__/TC-CPQ-010.spec.ts +54 -0
- package/src/modules/cpq/__integration__/TC-CPQ-011.spec.ts +24 -0
- package/src/modules/cpq/__integration__/TC-CPQ-012.spec.ts +62 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-001.spec.ts +52 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-002.spec.ts +65 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-003.spec.ts +107 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-004.spec.ts +74 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-005.spec.ts +86 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-006.spec.ts +79 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-007.spec.ts +63 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-008.spec.ts +62 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-009.spec.ts +62 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-010.spec.ts +59 -0
- package/src/modules/cpq/__integration__/TC-CPQ-ARC-011.spec.ts +128 -0
- package/src/modules/cpq/__integration__/_helpers/cpqArcFixtures.ts +208 -0
- package/src/modules/cpq/__tests__/package-isolation.test.ts +232 -0
- package/src/modules/cpq/acl.ts +26 -0
- package/src/modules/cpq/api/_helpers/__tests__/handleArcError.test.ts +117 -0
- package/src/modules/cpq/api/_helpers/handleArcError.ts +81 -0
- package/src/modules/cpq/api/attribute-options/route.ts +55 -0
- package/src/modules/cpq/api/bundle-slots/route.ts +112 -0
- package/src/modules/cpq/api/bundle-tree/route.ts +31 -0
- package/src/modules/cpq/api/context.ts +39 -0
- package/src/modules/cpq/api/inventory/assets/route.ts +123 -0
- package/src/modules/cpq/api/inventory/assets/status/route.ts +34 -0
- package/src/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.ts +30 -0
- package/src/modules/cpq/api/inventory/customer/[customerId]/route.ts +31 -0
- package/src/modules/cpq/api/inventory/subscription-items/route.ts +107 -0
- package/src/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.ts +64 -0
- package/src/modules/cpq/api/inventory/subscriptions/expiring/route.ts +66 -0
- package/src/modules/cpq/api/inventory/subscriptions/route.ts +126 -0
- package/src/modules/cpq/api/inventory/subscriptions/status/route.ts +34 -0
- package/src/modules/cpq/api/offering-components/route.ts +103 -0
- package/src/modules/cpq/api/orders/[orderId]/activate/route.ts +48 -0
- package/src/modules/cpq/api/orders/[orderId]/route.ts +28 -0
- package/src/modules/cpq/api/orders/[orderId]/status/route.ts +38 -0
- package/src/modules/cpq/api/orders/route.ts +77 -0
- package/src/modules/cpq/api/price-rules/route.ts +210 -0
- package/src/modules/cpq/api/pricing-table-entries/route.ts +159 -0
- package/src/modules/cpq/api/pricing-tables/route.ts +202 -0
- package/src/modules/cpq/api/product-attributes/route.ts +175 -0
- package/src/modules/cpq/api/product-charges/route.ts +181 -0
- package/src/modules/cpq/api/product-offerings/route.ts +329 -0
- package/src/modules/cpq/api/product-relationships/route.ts +156 -0
- package/src/modules/cpq/api/product-specifications/route.ts +325 -0
- package/src/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.ts +77 -0
- package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +28 -0
- package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +55 -0
- package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +26 -0
- package/src/modules/cpq/api/quotes/[quoteId]/merge-meta/route.ts +78 -0
- package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +34 -0
- package/src/modules/cpq/api/quotes/[quoteId]/route.ts +109 -0
- package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +26 -0
- package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.ts +91 -0
- package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.ts +107 -0
- package/src/modules/cpq/api/quotes/attributes/route.ts +61 -0
- package/src/modules/cpq/api/quotes/from-subscription/route.ts +85 -0
- package/src/modules/cpq/api/quotes/price/route.ts +81 -0
- package/src/modules/cpq/api/quotes/route.ts +114 -0
- package/src/modules/cpq/api/quotes/validate-relationships/route.ts +100 -0
- package/src/modules/cpq/api/resolveQuotingService.ts +11 -0
- package/src/modules/cpq/api/wizards/resolve/route.ts +39 -0
- package/src/modules/cpq/api/wizards/route.ts +127 -0
- package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +166 -0
- package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +251 -0
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +9 -0
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +440 -0
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/inventory/page.tsx +49 -0
- package/src/modules/cpq/backend/cpq/inventory/shared.tsx +32 -0
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +9 -0
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +1085 -0
- package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +1165 -0
- package/src/modules/cpq/backend/cpq/offerings/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/offerings/page.tsx +468 -0
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +921 -0
- package/src/modules/cpq/backend/cpq/orders/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/orders/page.tsx +182 -0
- package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +310 -0
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +9 -0
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +472 -0
- package/src/modules/cpq/backend/cpq/price-rules/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/price-rules/page.tsx +253 -0
- package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +431 -0
- package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +122 -0
- package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +588 -0
- package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.ts +90 -0
- package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +338 -0
- package/src/modules/cpq/backend/cpq/pricing/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/pricing/page.tsx +111 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +737 -0
- package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +1721 -0
- package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/quotes/page.tsx +370 -0
- package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +300 -0
- package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +1722 -0
- package/src/modules/cpq/backend/cpq/specifications/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/specifications/page.tsx +191 -0
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +319 -0
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +234 -0
- package/src/modules/cpq/backend/cpq/wizards/page.meta.ts +16 -0
- package/src/modules/cpq/backend/cpq/wizards/page.tsx +141 -0
- package/src/modules/cpq/ce.ts +150 -0
- package/src/modules/cpq/cli.ts +10 -0
- package/src/modules/cpq/components/CpqListView.tsx +431 -0
- package/src/modules/cpq/components/NumberInput.tsx +166 -0
- package/src/modules/cpq/components/StatusPath.tsx +116 -0
- package/src/modules/cpq/components/statusMaps.ts +148 -0
- package/src/modules/cpq/components/useCpqRowActions.tsx +183 -0
- package/src/modules/cpq/data/__tests__/validators.arc.test.ts +271 -0
- package/src/modules/cpq/data/__tests__/validators.test.ts +782 -0
- package/src/modules/cpq/data/entities.ts +1467 -0
- package/src/modules/cpq/data/validators.ts +663 -0
- package/src/modules/cpq/di.ts +70 -0
- package/src/modules/cpq/events.ts +62 -0
- package/src/modules/cpq/index.ts +15 -0
- package/src/modules/cpq/lib/seeds/__tests__/api.test.ts +86 -0
- package/src/modules/cpq/lib/seeds/__tests__/tenant-provisioning.test.ts +349 -0
- package/src/modules/cpq/lib/seeds/api.ts +119 -0
- package/src/modules/cpq/lib/seeds/tenant-provisioning.ts +239 -0
- package/src/modules/cpq/lib/seeds.ts +18 -0
- package/src/modules/cpq/migrations/.snapshot-open-mercato.json +7473 -0
- package/src/modules/cpq/migrations/Migration20260220220000_cpq.ts +51 -0
- package/src/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.ts +70 -0
- package/src/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.ts +15 -0
- package/src/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.ts +73 -0
- package/src/modules/cpq/migrations/Migration20260402000000_cpq_workflows.ts +59 -0
- package/src/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.ts +30 -0
- package/src/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.ts +36 -0
- package/src/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.ts +33 -0
- package/src/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.ts +22 -0
- package/src/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.ts +56 -0
- package/src/modules/cpq/migrations/Migration20260410100000_cpq_inventory.ts +118 -0
- package/src/modules/cpq/migrations/Migration20260411000000_cpq_orders.ts +62 -0
- package/src/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.ts +33 -0
- package/src/modules/cpq/migrations/Migration20260504064621_cpq_arc.ts +68 -0
- package/src/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.ts +30 -0
- package/src/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.ts +27 -0
- package/src/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.ts +82 -0
- package/src/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.ts +52 -0
- package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +59 -0
- package/src/modules/cpq/services/__tests__/cpqBundleService.test.ts +661 -0
- package/src/modules/cpq/services/__tests__/cpqInventoryService.arc.test.ts +357 -0
- package/src/modules/cpq/services/__tests__/cpqInventoryService.test.ts +718 -0
- package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +603 -0
- package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +1012 -0
- package/src/modules/cpq/services/__tests__/cpqProductService.test.ts +603 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +143 -0
- package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +658 -0
- package/src/modules/cpq/services/__tests__/cpqValidationService.test.ts +811 -0
- package/src/modules/cpq/services/__tests__/cpqWizardService.test.ts +389 -0
- package/src/modules/cpq/services/__tests__/types.test.ts +359 -0
- package/src/modules/cpq/services/cpqBundleService.ts +671 -0
- package/src/modules/cpq/services/cpqInventoryService.ts +1929 -0
- package/src/modules/cpq/services/cpqOrderService.ts +1230 -0
- package/src/modules/cpq/services/cpqPricingService.ts +412 -0
- package/src/modules/cpq/services/cpqProductService.ts +311 -0
- package/src/modules/cpq/services/cpqQuotingService.ts +2638 -0
- package/src/modules/cpq/services/cpqValidationService.ts +323 -0
- package/src/modules/cpq/services/cpqWizardService.ts +204 -0
- package/src/modules/cpq/services/types.ts +303 -0
- package/src/modules/cpq/setup.ts +63 -0
- package/src/modules/cpq/subscribers/subscription-amended-log.ts +20 -0
- package/src/modules/cpq/subscribers/subscription-cancelled-log.ts +19 -0
- package/src/modules/cpq/subscribers/subscription-merged-log.ts +22 -0
- package/src/modules/cpq/subscribers/subscription-renewed-log.ts +18 -0
- package/src/modules/cpq/subscribers/subscription-superseded-log.ts +19 -0
- package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +339 -0
- package/src/modules/cpq/widgets/injection/customer-inventory/widget.ts +16 -0
- package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +96 -0
- package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.ts +16 -0
- package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +265 -0
- package/src/modules/cpq/widgets/injection/quote-configurator/widget.ts +16 -0
- package/src/modules/cpq/widgets/injection-table.ts +42 -0
- package/src/modules/cpq/workflows/WizardRunner.tsx +158 -0
- package/src/modules/cpq/workflows/WizardTrigger.tsx +142 -0
- package/src/modules/cpq/workflows/engine.tsx +278 -0
- package/src/modules/cpq/workflows/registry.ts +15 -0
- package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +136 -0
- package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +34 -0
- package/src/modules/cpq/workflows/steps/ItemListStep.tsx +146 -0
- package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +438 -0
- package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +287 -0
- package/src/modules/cpq/workflows/steps/ReviewStep.tsx +219 -0
- package/src/modules/cpq/workflows/steps/index.ts +51 -0
- package/src/modules/cpq/workflows/types.ts +102 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package isolation guard for @dainamite/cpq.
|
|
3
|
+
*
|
|
4
|
+
* Proves the package can be loaded and described without any sibling
|
|
5
|
+
* `src/modules/<x>` modules being present. Catches three classes of leak:
|
|
6
|
+
*
|
|
7
|
+
* 1. import paths that escape the package boundary (relative `../../../../<other>`,
|
|
8
|
+
* absolute `@/modules/<other>`, deep `src/modules/<other>`)
|
|
9
|
+
* 2. deep imports into `@open-mercato/core/modules/<x>` for modules that aren't
|
|
10
|
+
* declared in `metadata.requires`
|
|
11
|
+
* 3. side-effect-only imports that pull in non-CPQ entities at module load time
|
|
12
|
+
*
|
|
13
|
+
* These checks are static (read package source) — no DI bootstrap is
|
|
14
|
+
* attempted, so the test is fast and runs in plain Node + jest.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { promises as fs } from 'node:fs'
|
|
18
|
+
import path from 'node:path'
|
|
19
|
+
|
|
20
|
+
import { metadata } from '../index'
|
|
21
|
+
|
|
22
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..')
|
|
23
|
+
|
|
24
|
+
// Must mirror peerDependencies in packages/cpq/package.json (plus relatives + node builtins).
|
|
25
|
+
// Adding a prefix here without adding it to peerDependencies = silent runtime failure for
|
|
26
|
+
// consumers that don't happen to have the dep transitively.
|
|
27
|
+
const ALLOWED_IMPORT_PREFIXES = [
|
|
28
|
+
'./',
|
|
29
|
+
'../',
|
|
30
|
+
'@open-mercato/',
|
|
31
|
+
'@mikro-orm/',
|
|
32
|
+
'awilix',
|
|
33
|
+
'bcryptjs',
|
|
34
|
+
'lucide-react',
|
|
35
|
+
'next',
|
|
36
|
+
'next/',
|
|
37
|
+
'react',
|
|
38
|
+
'react/',
|
|
39
|
+
'react-dom',
|
|
40
|
+
'react-dom/',
|
|
41
|
+
'zod',
|
|
42
|
+
'@dainamite/',
|
|
43
|
+
'node:',
|
|
44
|
+
// UI table primitive — listed across @tanstack so future submodules
|
|
45
|
+
// (e.g. @tanstack/react-virtual) also pass without revisits.
|
|
46
|
+
'@tanstack/',
|
|
47
|
+
// Excel import/export on the pricing-tables detail page.
|
|
48
|
+
'xlsx',
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
const NODE_BUILTINS = new Set([
|
|
52
|
+
'crypto',
|
|
53
|
+
'fs',
|
|
54
|
+
'path',
|
|
55
|
+
'os',
|
|
56
|
+
'util',
|
|
57
|
+
'stream',
|
|
58
|
+
'buffer',
|
|
59
|
+
'http',
|
|
60
|
+
'https',
|
|
61
|
+
'url',
|
|
62
|
+
'querystring',
|
|
63
|
+
'child_process',
|
|
64
|
+
'zlib',
|
|
65
|
+
'events',
|
|
66
|
+
'assert',
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
const FORBIDDEN_PREFIXES = [
|
|
70
|
+
'@/modules/',
|
|
71
|
+
'src/modules/',
|
|
72
|
+
'../../../../catalog/',
|
|
73
|
+
'../../../../sales/',
|
|
74
|
+
'../../../../customers/',
|
|
75
|
+
'../../../../dictionaries/',
|
|
76
|
+
'../../../../auth/',
|
|
77
|
+
'../../../../directory/',
|
|
78
|
+
'../../../../demo_',
|
|
79
|
+
'../../../../portal/',
|
|
80
|
+
'../../../../customer_accounts/',
|
|
81
|
+
'../../../../example/',
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
async function walk(dir: string, acc: string[] = []): Promise<string[]> {
|
|
85
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (entry.name === 'node_modules' || entry.name === 'dist') continue
|
|
88
|
+
if (entry.name === '__tests__' || entry.name === '__integration__') continue
|
|
89
|
+
const full = path.join(dir, entry.name)
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
await walk(full, acc)
|
|
92
|
+
} else if (/\.(ts|tsx|mts|cts)$/.test(entry.name) && !/\.d\.ts$/.test(entry.name)) {
|
|
93
|
+
acc.push(full)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return acc
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const IMPORT_RE = /(?:^|\n)\s*(?:import\s+(?:[^'"]+\s+from\s+)?|export\s+(?:[^'"]+\s+from\s+)|import\s*\(\s*)["']([^"']+)["']/g
|
|
100
|
+
|
|
101
|
+
function extractImports(source: string): string[] {
|
|
102
|
+
const out: string[] = []
|
|
103
|
+
let m: RegExpExecArray | null
|
|
104
|
+
while ((m = IMPORT_RE.exec(source)) !== null) {
|
|
105
|
+
out.push(m[1])
|
|
106
|
+
}
|
|
107
|
+
return out
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isAllowedSpecifier(spec: string): boolean {
|
|
111
|
+
if (NODE_BUILTINS.has(spec)) return true
|
|
112
|
+
return ALLOWED_IMPORT_PREFIXES.some((p) => spec === p || spec.startsWith(p))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isForbiddenSpecifier(spec: string): boolean {
|
|
116
|
+
return FORBIDDEN_PREFIXES.some((p) => spec.startsWith(p))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function extractCoreModuleSubpath(spec: string): string | null {
|
|
120
|
+
const prefix = '@open-mercato/core/modules/'
|
|
121
|
+
if (!spec.startsWith(prefix)) return null
|
|
122
|
+
const rest = spec.slice(prefix.length)
|
|
123
|
+
const slash = rest.indexOf('/')
|
|
124
|
+
return slash === -1 ? rest : rest.slice(0, slash)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
describe('@dainamite/cpq package isolation', () => {
|
|
128
|
+
let allFiles: string[] = []
|
|
129
|
+
let importsByFile = new Map<string, string[]>()
|
|
130
|
+
|
|
131
|
+
beforeAll(async () => {
|
|
132
|
+
allFiles = await walk(PACKAGE_ROOT)
|
|
133
|
+
importsByFile = new Map()
|
|
134
|
+
for (const file of allFiles) {
|
|
135
|
+
const source = await fs.readFile(file, 'utf-8')
|
|
136
|
+
importsByFile.set(file, extractImports(source))
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('declares all required modules in metadata.requires', () => {
|
|
141
|
+
expect(metadata.requires).toEqual(
|
|
142
|
+
expect.arrayContaining(['auth', 'directory', 'catalog', 'sales', 'customers', 'dictionaries']),
|
|
143
|
+
)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('does not import from sibling app modules', () => {
|
|
147
|
+
const violations: Array<{ file: string; spec: string }> = []
|
|
148
|
+
for (const [file, specs] of importsByFile) {
|
|
149
|
+
for (const spec of specs) {
|
|
150
|
+
if (isForbiddenSpecifier(spec)) {
|
|
151
|
+
violations.push({ file: path.relative(PACKAGE_ROOT, file), spec })
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (violations.length > 0) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
`Forbidden cross-module imports found:\n${violations
|
|
158
|
+
.map((v) => ` ${v.file} → ${v.spec}`)
|
|
159
|
+
.join('\n')}`,
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
it('only uses allowed import prefixes (no surprise package leaks)', () => {
|
|
165
|
+
const violations: Array<{ file: string; spec: string }> = []
|
|
166
|
+
for (const [file, specs] of importsByFile) {
|
|
167
|
+
for (const spec of specs) {
|
|
168
|
+
if (!isAllowedSpecifier(spec)) {
|
|
169
|
+
violations.push({ file: path.relative(PACKAGE_ROOT, file), spec })
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (violations.length > 0) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Imports outside the allowed prefix set:\n${violations
|
|
176
|
+
.map((v) => ` ${v.file} → ${v.spec}`)
|
|
177
|
+
.join('\n')}\n` +
|
|
178
|
+
`If a new prefix is legitimate, add it to ALLOWED_IMPORT_PREFIXES in this test.`,
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
|
|
183
|
+
it('only deep-imports from core modules declared in metadata.requires', () => {
|
|
184
|
+
const declared = new Set<string>(metadata.requires ?? [])
|
|
185
|
+
const violations: Array<{ file: string; spec: string; module: string }> = []
|
|
186
|
+
for (const [file, specs] of importsByFile) {
|
|
187
|
+
for (const spec of specs) {
|
|
188
|
+
const subModule = extractCoreModuleSubpath(spec)
|
|
189
|
+
if (subModule && !declared.has(subModule)) {
|
|
190
|
+
violations.push({ file: path.relative(PACKAGE_ROOT, file), spec, module: subModule })
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (violations.length > 0) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Deep imports into @open-mercato/core/modules/<x> for modules not in metadata.requires:\n${violations
|
|
197
|
+
.map((v) => ` ${v.file} → ${v.spec} (add '${v.module}' to requires)`)
|
|
198
|
+
.join('\n')}`,
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('contains no @ManyToOne / @OneToMany / @ManyToMany / @OneToOne ORM relations', async () => {
|
|
204
|
+
const relRe = /@(ManyToOne|OneToMany|ManyToMany|OneToOne)\s*\(/g
|
|
205
|
+
const violations: Array<{ file: string; line: number; match: string }> = []
|
|
206
|
+
for (const file of allFiles) {
|
|
207
|
+
const source = await fs.readFile(file, 'utf-8')
|
|
208
|
+
const lines = source.split('\n')
|
|
209
|
+
lines.forEach((line, idx) => {
|
|
210
|
+
const m = line.match(relRe)
|
|
211
|
+
if (m) {
|
|
212
|
+
violations.push({ file: path.relative(PACKAGE_ROOT, file), line: idx + 1, match: m.join(', ') })
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
if (violations.length > 0) {
|
|
217
|
+
throw new Error(
|
|
218
|
+
`Cross-entity ORM relations forbidden in @dainamite/cpq (use FK string columns):\n${violations
|
|
219
|
+
.map((v) => ` ${v.file}:${v.line} — ${v.match}`)
|
|
220
|
+
.join('\n')}`,
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('module index.ts loads without throwing', async () => {
|
|
226
|
+
const mod = await import('../index')
|
|
227
|
+
expect(mod.metadata).toBeDefined()
|
|
228
|
+
expect(mod.metadata.name).toBe('cpq')
|
|
229
|
+
expect(mod.features).toBeDefined()
|
|
230
|
+
expect(Array.isArray(mod.features)).toBe(true)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const features = [
|
|
2
|
+
{ id: 'cpq.quotes.view', title: 'View CPQ Quotes', module: 'cpq' },
|
|
3
|
+
{ id: 'cpq.quotes.manage', title: 'Manage CPQ Quotes', module: 'cpq' },
|
|
4
|
+
{ id: 'cpq.pricing.view', title: 'View Pricing', module: 'cpq' },
|
|
5
|
+
{ id: 'cpq.pricing.manage', title: 'Manage Pricing', module: 'cpq' },
|
|
6
|
+
{ id: 'cpq.products.view', title: 'View CPQ Product Config', module: 'cpq' },
|
|
7
|
+
{ id: 'cpq.products.manage', title: 'Manage CPQ Product Config', module: 'cpq' },
|
|
8
|
+
{ id: 'cpq.specifications.view', title: 'View Product Specifications', module: 'cpq' },
|
|
9
|
+
{ id: 'cpq.specifications.manage', title: 'Manage Product Specifications', module: 'cpq' },
|
|
10
|
+
{ id: 'cpq.offerings.view', title: 'View Product Offerings', module: 'cpq' },
|
|
11
|
+
{ id: 'cpq.offerings.manage', title: 'Manage Product Offerings', module: 'cpq' },
|
|
12
|
+
{ id: 'cpq.wizards.view', title: 'View Wizards', module: 'cpq' },
|
|
13
|
+
{ id: 'cpq.wizards.manage', title: 'Manage Wizards', module: 'cpq' },
|
|
14
|
+
{ id: 'cpq.orders.view', title: 'View CPQ Orders', module: 'cpq' },
|
|
15
|
+
{ id: 'cpq.orders.manage', title: 'Manage CPQ Orders', module: 'cpq' },
|
|
16
|
+
// XD-250 ARC (Amend / Renew / Cancel)
|
|
17
|
+
{ id: 'cpq.arc.amend.manage', title: 'Manage Amend Quotes', module: 'cpq' },
|
|
18
|
+
{ id: 'cpq.arc.renew.manage', title: 'Manage Renew Quotes', module: 'cpq' },
|
|
19
|
+
{ id: 'cpq.arc.cancel.manage', title: 'Manage Cancel Quotes', module: 'cpq' },
|
|
20
|
+
{ id: 'cpq.arc.changelog.view', title: 'View Subscription Change History', module: 'cpq' },
|
|
21
|
+
// Expiring-subscriptions feature retired — the dedicated /expiring page
|
|
22
|
+
// was replaced by an "Expiring within (days)" filter + saved perspective
|
|
23
|
+
// on the main subscriptions list, which uses `cpq.inventory.view`.
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
export default features
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { OptimisticLockError } from '@mikro-orm/core'
|
|
3
|
+
import { handleArcError } from '../handleArcError'
|
|
4
|
+
import { QuotingError } from '../../../services/cpqQuotingService'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Unit tests for the shared ARC error handler. Each branch of the matcher
|
|
8
|
+
* pyramid gets a representative input + asserts on the resulting status code
|
|
9
|
+
* and body shape.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
async function readBody(res: Response): Promise<Record<string, unknown>> {
|
|
13
|
+
return (await res.json()) as Record<string, unknown>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('handleArcError', () => {
|
|
17
|
+
it('400 + details on a Zod validation error', async () => {
|
|
18
|
+
const schema = z.object({ x: z.string() })
|
|
19
|
+
const parse = schema.safeParse({ x: 1 })
|
|
20
|
+
expect(parse.success).toBe(false)
|
|
21
|
+
const err = !parse.success ? parse.error : new Error('unreachable')
|
|
22
|
+
|
|
23
|
+
const res = handleArcError(err, 'test')
|
|
24
|
+
expect(res.status).toBe(400)
|
|
25
|
+
const body = await readBody(res)
|
|
26
|
+
expect(body.error).toBe('Validation error')
|
|
27
|
+
expect(Array.isArray(body.details)).toBe(true)
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
it('propagates QuotingError status + message + cpqStatus', async () => {
|
|
31
|
+
const err = new QuotingError(409, 'Type immutable', 'in_approval')
|
|
32
|
+
const res = handleArcError(err, 'test')
|
|
33
|
+
expect(res.status).toBe(409)
|
|
34
|
+
const body = await readBody(res)
|
|
35
|
+
expect(body.error).toBe('Type immutable')
|
|
36
|
+
expect(body.cpqStatus).toBe('in_approval')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('translates OptimisticLockError to 409 "Concurrent modification detected"', async () => {
|
|
40
|
+
const err = OptimisticLockError.lockFailedVersionMismatch(
|
|
41
|
+
{ id: 'sub-1' } as never,
|
|
42
|
+
1,
|
|
43
|
+
2,
|
|
44
|
+
)
|
|
45
|
+
const res = handleArcError(err, 'test')
|
|
46
|
+
expect(res.status).toBe(409)
|
|
47
|
+
const body = await readBody(res)
|
|
48
|
+
expect(body.error).toBe('Concurrent modification detected')
|
|
49
|
+
expect(typeof body.details).toBe('string')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('matches OptimisticLockError via constructor name (Turbopack drift fallback)', async () => {
|
|
53
|
+
// Synthesize an object whose class is locally-defined but shares the
|
|
54
|
+
// canonical name. Mirrors what hot-reload produces at runtime.
|
|
55
|
+
class OptimisticLockError {
|
|
56
|
+
constructor(public message: string) {}
|
|
57
|
+
}
|
|
58
|
+
const err = new OptimisticLockError('synthetic mismatch')
|
|
59
|
+
const res = handleArcError(err, 'test')
|
|
60
|
+
expect(res.status).toBe(409)
|
|
61
|
+
const body = await readBody(res)
|
|
62
|
+
expect(body.error).toBe('Concurrent modification detected')
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('propagates inventory-service plain {status, error} throw shape', async () => {
|
|
66
|
+
const err = { status: 404, error: 'Subscription not found' }
|
|
67
|
+
const res = handleArcError(err, 'test')
|
|
68
|
+
expect(res.status).toBe(404)
|
|
69
|
+
const body = await readBody(res)
|
|
70
|
+
expect(body.error).toBe('Subscription not found')
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('matches duck-typed QuotingError (Turbopack instanceof drift)', async () => {
|
|
74
|
+
const err = { name: 'QuotingError', status: 409, message: 'something conflicting' }
|
|
75
|
+
const res = handleArcError(err, 'test')
|
|
76
|
+
expect(res.status).toBe(409)
|
|
77
|
+
const body = await readBody(res)
|
|
78
|
+
expect(body.error).toBe('something conflicting')
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('propagates `details` from duck-typed QuotingError into response body', async () => {
|
|
82
|
+
const err = {
|
|
83
|
+
name: 'QuotingError',
|
|
84
|
+
status: 409,
|
|
85
|
+
message: 'Subscription has another ARC quote in progress',
|
|
86
|
+
details: { existingQuoteId: 'q-existing-123' },
|
|
87
|
+
}
|
|
88
|
+
const res = handleArcError(err, 'test')
|
|
89
|
+
expect(res.status).toBe(409)
|
|
90
|
+
const body = await readBody(res)
|
|
91
|
+
expect(body.error).toBe('Subscription has another ARC quote in progress')
|
|
92
|
+
expect(body.existingQuoteId).toBe('q-existing-123')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('propagates `details` from typed QuotingError into response body', async () => {
|
|
96
|
+
const err = new QuotingError(409, 'Conflict', undefined)
|
|
97
|
+
err.details = { existingQuoteId: 'q-existing-typed' }
|
|
98
|
+
const res = handleArcError(err, 'test')
|
|
99
|
+
expect(res.status).toBe(409)
|
|
100
|
+
const body = await readBody(res)
|
|
101
|
+
expect(body.existingQuoteId).toBe('q-existing-typed')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('falls back to 500 + logs for unexpected errors', async () => {
|
|
105
|
+
const consoleErr = jest.spyOn(console, 'error').mockImplementation(() => undefined)
|
|
106
|
+
try {
|
|
107
|
+
const err = new Error('something internal')
|
|
108
|
+
const res = handleArcError(err, 'test/ctx')
|
|
109
|
+
expect(res.status).toBe(500)
|
|
110
|
+
const body = await readBody(res)
|
|
111
|
+
expect(body.error).toBe('Internal error')
|
|
112
|
+
expect(consoleErr).toHaveBeenCalledWith('[test/ctx]', err)
|
|
113
|
+
} finally {
|
|
114
|
+
consoleErr.mockRestore()
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
})
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { OptimisticLockError } from '@mikro-orm/core'
|
|
4
|
+
import { QuotingError } from '../../services/cpqQuotingService'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Shared error → JSON-response converter for ARC routes (XD-250).
|
|
8
|
+
*
|
|
9
|
+
* Handles three families:
|
|
10
|
+
* 1. Zod validation errors → 400 with `details` array.
|
|
11
|
+
* 2. Service-layer `QuotingError` (matched both via `instanceof` AND via
|
|
12
|
+
* duck-typing on `name === 'QuotingError'`) → propagates `err.status`.
|
|
13
|
+
* The duck-typing fallback exists because Turbopack hot-reload can
|
|
14
|
+
* produce two distinct class instances for the same source file
|
|
15
|
+
* across module boundaries, breaking `instanceof` checks at runtime.
|
|
16
|
+
* 3. cpqInventoryService throws plain `{ status, error }` objects →
|
|
17
|
+
* propagate the embedded status.
|
|
18
|
+
* 4. Anything else → 500 + console.error with the route context.
|
|
19
|
+
*
|
|
20
|
+
* Returning the response keeps route handlers terse:
|
|
21
|
+
* `} catch (err) { return handleArcError(err, 'cpq/quotes/from-subscription.POST') }`
|
|
22
|
+
*/
|
|
23
|
+
export function handleArcError(err: unknown, ctx: string): NextResponse {
|
|
24
|
+
if (err instanceof z.ZodError) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: 'Validation error', details: err.issues },
|
|
27
|
+
{ status: 400 },
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (err instanceof QuotingError) {
|
|
32
|
+
const body: Record<string, unknown> = { error: err.message }
|
|
33
|
+
if (err.cpqStatus) body.cpqStatus = err.cpqStatus
|
|
34
|
+
if (err.details) Object.assign(body, err.details)
|
|
35
|
+
return NextResponse.json(body, { status: err.status })
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// MikroORM optimistic-lock collision on subscription.version (XD-250 spec
|
|
39
|
+
// § Risks "Order activation racing with another mutation"). Both
|
|
40
|
+
// `instanceof OptimisticLockError` AND constructor-name fallback to
|
|
41
|
+
// survive Turbopack class drift.
|
|
42
|
+
if (
|
|
43
|
+
err instanceof OptimisticLockError ||
|
|
44
|
+
(err &&
|
|
45
|
+
typeof err === 'object' &&
|
|
46
|
+
typeof (err as { constructor?: { name?: string } }).constructor?.name === 'string' &&
|
|
47
|
+
(err as { constructor: { name: string } }).constructor.name === 'OptimisticLockError')
|
|
48
|
+
) {
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{ error: 'Concurrent modification detected', details: (err as Error).message },
|
|
51
|
+
{ status: 409 },
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Duck-typed QuotingError (Turbopack instanceof drift) and inventory-service
|
|
56
|
+
// plain throw shape `{ status, error }`. Both surface a numeric `status`.
|
|
57
|
+
// Also propagates `details` (object) into response body — preserves the
|
|
58
|
+
// structured payload (e.g. `existingQuoteId`) that the typed branch above
|
|
59
|
+
// would have surfaced if instanceof had matched.
|
|
60
|
+
if (
|
|
61
|
+
err &&
|
|
62
|
+
typeof err === 'object' &&
|
|
63
|
+
'status' in err &&
|
|
64
|
+
typeof (err as { status: unknown }).status === 'number'
|
|
65
|
+
) {
|
|
66
|
+
const e = err as {
|
|
67
|
+
status: number
|
|
68
|
+
message?: string
|
|
69
|
+
error?: string
|
|
70
|
+
name?: string
|
|
71
|
+
details?: Record<string, unknown>
|
|
72
|
+
}
|
|
73
|
+
const message = e.message ?? e.error ?? 'Conflict'
|
|
74
|
+
const body: Record<string, unknown> = { error: message }
|
|
75
|
+
if (e.details && typeof e.details === 'object') Object.assign(body, e.details)
|
|
76
|
+
return NextResponse.json(body, { status: e.status })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.error(`[${ctx}]`, err)
|
|
80
|
+
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
|
|
81
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { resolveCpqRouteContext } from '../context'
|
|
3
|
+
import { DefaultCpqValidationService } from '../../services/cpqValidationService'
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
GET: { requireAuth: true, requireFeatures: ['cpq.quotes.view'] },
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Returns resolved options for an attribute, including dynamic options from
|
|
11
|
+
* pricing tables (e.g. cloud_region filtered by cloud_provider).
|
|
12
|
+
* Used by the quoting wizard to render picklists for attributes with
|
|
13
|
+
* dynamicOptionsSource or dependsOn constraints.
|
|
14
|
+
*/
|
|
15
|
+
export async function GET(req: Request) {
|
|
16
|
+
try {
|
|
17
|
+
const ctx = await resolveCpqRouteContext(req)
|
|
18
|
+
if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
19
|
+
|
|
20
|
+
const url = new URL(req.url)
|
|
21
|
+
const productId = url.searchParams.get('productId')
|
|
22
|
+
const attributeCode = url.searchParams.get('attributeCode')
|
|
23
|
+
const configParam = url.searchParams.get('configuration')
|
|
24
|
+
|
|
25
|
+
if (!productId || !attributeCode) {
|
|
26
|
+
return NextResponse.json(
|
|
27
|
+
{ error: 'productId and attributeCode are required' },
|
|
28
|
+
{ status: 400 },
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let configuration: Record<string, unknown> = {}
|
|
33
|
+
if (configParam) {
|
|
34
|
+
try {
|
|
35
|
+
configuration = JSON.parse(configParam) as Record<string, unknown>
|
|
36
|
+
} catch {
|
|
37
|
+
return NextResponse.json({ error: 'Invalid configuration JSON' }, { status: 400 })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const validationService = new DefaultCpqValidationService(ctx.em)
|
|
42
|
+
const options = await validationService.getAttributeOptions({
|
|
43
|
+
productId,
|
|
44
|
+
attributeCode,
|
|
45
|
+
currentConfiguration: configuration,
|
|
46
|
+
tenantId: ctx.tenantId,
|
|
47
|
+
organizationId: ctx.organizationId,
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
return NextResponse.json({ items: options })
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('[cpq/attribute-options.GET]', err)
|
|
53
|
+
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { resolveCpqRouteContext } from '../context'
|
|
4
|
+
import { cpqBundleSlotCreateSchema, cpqBundleSlotUpdateSchema } from '../../data/validators'
|
|
5
|
+
import type { DefaultCpqBundleService } from '../../services/cpqBundleService'
|
|
6
|
+
|
|
7
|
+
export const metadata = {
|
|
8
|
+
GET: { requireAuth: true, requireFeatures: ['cpq.specifications.view'] },
|
|
9
|
+
POST: { requireAuth: true, requireFeatures: ['cpq.specifications.manage'] },
|
|
10
|
+
PUT: { requireAuth: true, requireFeatures: ['cpq.specifications.manage'] },
|
|
11
|
+
DELETE: { requireAuth: true, requireFeatures: ['cpq.specifications.manage'] },
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function GET(req: Request) {
|
|
15
|
+
try {
|
|
16
|
+
const ctx = await resolveCpqRouteContext(req)
|
|
17
|
+
if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
18
|
+
|
|
19
|
+
const url = new URL(req.url)
|
|
20
|
+
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
|
|
21
|
+
const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
|
|
22
|
+
|
|
23
|
+
const id = url.searchParams.get('id')
|
|
24
|
+
if (id) {
|
|
25
|
+
const slot = await service.getSlot(id, scope)
|
|
26
|
+
if (!slot) return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
|
27
|
+
return NextResponse.json(slot)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const specId = url.searchParams.get('specId')
|
|
31
|
+
if (!specId) return NextResponse.json({ error: 'specId is required' }, { status: 400 })
|
|
32
|
+
|
|
33
|
+
const slots = await service.getSlots(specId, scope)
|
|
34
|
+
return NextResponse.json({ items: slots })
|
|
35
|
+
} catch (err) {
|
|
36
|
+
console.error('[cpq/bundle-slots.GET]', err)
|
|
37
|
+
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function POST(req: Request) {
|
|
42
|
+
try {
|
|
43
|
+
const ctx = await resolveCpqRouteContext(req)
|
|
44
|
+
if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
45
|
+
|
|
46
|
+
const body = cpqBundleSlotCreateSchema.parse(await req.json())
|
|
47
|
+
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
|
|
48
|
+
const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
|
|
49
|
+
|
|
50
|
+
const slot = await service.createSlot(body, scope)
|
|
51
|
+
return NextResponse.json(slot, { status: 201 })
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err instanceof z.ZodError) {
|
|
54
|
+
return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
|
|
55
|
+
}
|
|
56
|
+
if (err instanceof Error && err.message.startsWith('V-')) {
|
|
57
|
+
return NextResponse.json({ error: err.message }, { status: 400 })
|
|
58
|
+
}
|
|
59
|
+
console.error('[cpq/bundle-slots.POST]', err)
|
|
60
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : 'Internal error' }, { status: 500 })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function PUT(req: Request) {
|
|
65
|
+
try {
|
|
66
|
+
const ctx = await resolveCpqRouteContext(req)
|
|
67
|
+
if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
68
|
+
|
|
69
|
+
const body = cpqBundleSlotUpdateSchema.parse(await req.json())
|
|
70
|
+
const { id, ...updates } = body
|
|
71
|
+
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
|
|
72
|
+
const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
|
|
73
|
+
|
|
74
|
+
const slot = await service.updateSlot(id, updates, scope)
|
|
75
|
+
if (!slot) return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
|
76
|
+
|
|
77
|
+
return NextResponse.json(slot)
|
|
78
|
+
} catch (err) {
|
|
79
|
+
if (err instanceof z.ZodError) {
|
|
80
|
+
return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
|
|
81
|
+
}
|
|
82
|
+
if (err instanceof Error && err.message.startsWith('V-')) {
|
|
83
|
+
return NextResponse.json({ error: err.message }, { status: 400 })
|
|
84
|
+
}
|
|
85
|
+
console.error('[cpq/bundle-slots.PUT]', err)
|
|
86
|
+
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function DELETE(req: Request) {
|
|
91
|
+
try {
|
|
92
|
+
const ctx = await resolveCpqRouteContext(req)
|
|
93
|
+
if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
94
|
+
|
|
95
|
+
const { id } = (await req.json()) as { id?: string }
|
|
96
|
+
if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })
|
|
97
|
+
|
|
98
|
+
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
|
|
99
|
+
const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
|
|
100
|
+
|
|
101
|
+
const deleted = await service.deleteSlot(id, scope)
|
|
102
|
+
if (!deleted) return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
|
103
|
+
|
|
104
|
+
return NextResponse.json({ success: true })
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err instanceof Error && err.message.includes('active offering components')) {
|
|
107
|
+
return NextResponse.json({ error: err.message }, { status: 400 })
|
|
108
|
+
}
|
|
109
|
+
console.error('[cpq/bundle-slots.DELETE]', err)
|
|
110
|
+
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { resolveCpqRouteContext } from '../context'
|
|
3
|
+
import type { DefaultCpqBundleService } from '../../services/cpqBundleService'
|
|
4
|
+
|
|
5
|
+
export const metadata = {
|
|
6
|
+
GET: { requireAuth: true, requireFeatures: ['cpq.offerings.view'] },
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function GET(req: Request) {
|
|
10
|
+
try {
|
|
11
|
+
const ctx = await resolveCpqRouteContext(req)
|
|
12
|
+
if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
13
|
+
|
|
14
|
+
const url = new URL(req.url)
|
|
15
|
+
const offeringId = url.searchParams.get('offeringId')
|
|
16
|
+
if (!offeringId) return NextResponse.json({ error: 'offeringId is required' }, { status: 400 })
|
|
17
|
+
|
|
18
|
+
const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
|
|
19
|
+
const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
|
|
20
|
+
|
|
21
|
+
const tree = await service.resolveBundleTree(offeringId, scope)
|
|
22
|
+
if (!tree) return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
|
23
|
+
|
|
24
|
+
const validation = await service.validateBundleCompleteness(offeringId, scope)
|
|
25
|
+
|
|
26
|
+
return NextResponse.json({ tree, validation })
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error('[cpq/bundle-tree.GET]', err)
|
|
29
|
+
return NextResponse.json({ error: 'Internal error' }, { status: 500 })
|
|
30
|
+
}
|
|
31
|
+
}
|