@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.
Files changed (504) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION.md +236 -0
  4. package/README.md +108 -0
  5. package/dist/modules/cpq/acl.js +30 -0
  6. package/dist/modules/cpq/acl.js.map +7 -0
  7. package/dist/modules/cpq/api/_helpers/handleArcError.js +37 -0
  8. package/dist/modules/cpq/api/_helpers/handleArcError.js.map +7 -0
  9. package/dist/modules/cpq/api/attribute-options/route.js +47 -0
  10. package/dist/modules/cpq/api/attribute-options/route.js.map +7 -0
  11. package/dist/modules/cpq/api/bundle-slots/route.js +101 -0
  12. package/dist/modules/cpq/api/bundle-slots/route.js.map +7 -0
  13. package/dist/modules/cpq/api/bundle-tree/route.js +28 -0
  14. package/dist/modules/cpq/api/bundle-tree/route.js.map +7 -0
  15. package/dist/modules/cpq/api/context.js +29 -0
  16. package/dist/modules/cpq/api/context.js.map +7 -0
  17. package/dist/modules/cpq/api/inventory/assets/route.js +113 -0
  18. package/dist/modules/cpq/api/inventory/assets/route.js.map +7 -0
  19. package/dist/modules/cpq/api/inventory/assets/status/route.js +35 -0
  20. package/dist/modules/cpq/api/inventory/assets/status/route.js.map +7 -0
  21. package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js +30 -0
  22. package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js.map +7 -0
  23. package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js +31 -0
  24. package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js.map +7 -0
  25. package/dist/modules/cpq/api/inventory/subscription-items/route.js +99 -0
  26. package/dist/modules/cpq/api/inventory/subscription-items/route.js.map +7 -0
  27. package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js +57 -0
  28. package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js.map +7 -0
  29. package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js +62 -0
  30. package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js.map +7 -0
  31. package/dist/modules/cpq/api/inventory/subscriptions/route.js +116 -0
  32. package/dist/modules/cpq/api/inventory/subscriptions/route.js.map +7 -0
  33. package/dist/modules/cpq/api/inventory/subscriptions/status/route.js +35 -0
  34. package/dist/modules/cpq/api/inventory/subscriptions/status/route.js.map +7 -0
  35. package/dist/modules/cpq/api/offering-components/route.js +91 -0
  36. package/dist/modules/cpq/api/offering-components/route.js.map +7 -0
  37. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js +39 -0
  38. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +7 -0
  39. package/dist/modules/cpq/api/orders/[orderId]/route.js +28 -0
  40. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +7 -0
  41. package/dist/modules/cpq/api/orders/[orderId]/status/route.js +38 -0
  42. package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +7 -0
  43. package/dist/modules/cpq/api/orders/route.js +71 -0
  44. package/dist/modules/cpq/api/orders/route.js.map +7 -0
  45. package/dist/modules/cpq/api/price-rules/route.js +180 -0
  46. package/dist/modules/cpq/api/price-rules/route.js.map +7 -0
  47. package/dist/modules/cpq/api/pricing-table-entries/route.js +142 -0
  48. package/dist/modules/cpq/api/pricing-table-entries/route.js.map +7 -0
  49. package/dist/modules/cpq/api/pricing-tables/route.js +179 -0
  50. package/dist/modules/cpq/api/pricing-tables/route.js.map +7 -0
  51. package/dist/modules/cpq/api/product-attributes/route.js +160 -0
  52. package/dist/modules/cpq/api/product-attributes/route.js.map +7 -0
  53. package/dist/modules/cpq/api/product-charges/route.js +166 -0
  54. package/dist/modules/cpq/api/product-charges/route.js.map +7 -0
  55. package/dist/modules/cpq/api/product-offerings/route.js +294 -0
  56. package/dist/modules/cpq/api/product-offerings/route.js.map +7 -0
  57. package/dist/modules/cpq/api/product-relationships/route.js +144 -0
  58. package/dist/modules/cpq/api/product-relationships/route.js.map +7 -0
  59. package/dist/modules/cpq/api/product-specifications/route.js +292 -0
  60. package/dist/modules/cpq/api/product-specifications/route.js.map +7 -0
  61. package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js +71 -0
  62. package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js.map +7 -0
  63. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +29 -0
  64. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js.map +7 -0
  65. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +54 -0
  66. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +7 -0
  67. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -0
  68. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +7 -0
  69. package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js +72 -0
  70. package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js.map +7 -0
  71. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +35 -0
  72. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +7 -0
  73. package/dist/modules/cpq/api/quotes/[quoteId]/route.js +102 -0
  74. package/dist/modules/cpq/api/quotes/[quoteId]/route.js.map +7 -0
  75. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +27 -0
  76. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +7 -0
  77. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js +79 -0
  78. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js.map +7 -0
  79. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js +95 -0
  80. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js.map +7 -0
  81. package/dist/modules/cpq/api/quotes/attributes/route.js +52 -0
  82. package/dist/modules/cpq/api/quotes/attributes/route.js.map +7 -0
  83. package/dist/modules/cpq/api/quotes/from-subscription/route.js +72 -0
  84. package/dist/modules/cpq/api/quotes/from-subscription/route.js.map +7 -0
  85. package/dist/modules/cpq/api/quotes/price/route.js +69 -0
  86. package/dist/modules/cpq/api/quotes/price/route.js.map +7 -0
  87. package/dist/modules/cpq/api/quotes/route.js +97 -0
  88. package/dist/modules/cpq/api/quotes/route.js.map +7 -0
  89. package/dist/modules/cpq/api/quotes/validate-relationships/route.js +86 -0
  90. package/dist/modules/cpq/api/quotes/validate-relationships/route.js.map +7 -0
  91. package/dist/modules/cpq/api/resolveQuotingService.js +7 -0
  92. package/dist/modules/cpq/api/resolveQuotingService.js.map +7 -0
  93. package/dist/modules/cpq/api/wizards/resolve/route.js +38 -0
  94. package/dist/modules/cpq/api/wizards/resolve/route.js.map +7 -0
  95. package/dist/modules/cpq/api/wizards/route.js +111 -0
  96. package/dist/modules/cpq/api/wizards/route.js.map +7 -0
  97. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +136 -0
  98. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +7 -0
  99. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +185 -0
  100. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +7 -0
  101. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +343 -0
  102. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +7 -0
  103. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +13 -0
  104. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +7 -0
  105. package/dist/modules/cpq/backend/cpq/inventory/page.js +35 -0
  106. package/dist/modules/cpq/backend/cpq/inventory/page.js.map +7 -0
  107. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +18 -0
  108. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +7 -0
  109. package/dist/modules/cpq/backend/cpq/inventory/shared.js +20 -0
  110. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +7 -0
  111. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +794 -0
  112. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +7 -0
  113. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +13 -0
  114. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +7 -0
  115. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +917 -0
  116. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +7 -0
  117. package/dist/modules/cpq/backend/cpq/offerings/page.js +405 -0
  118. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +7 -0
  119. package/dist/modules/cpq/backend/cpq/offerings/page.meta.js +18 -0
  120. package/dist/modules/cpq/backend/cpq/offerings/page.meta.js.map +7 -0
  121. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +677 -0
  122. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +7 -0
  123. package/dist/modules/cpq/backend/cpq/orders/page.js +134 -0
  124. package/dist/modules/cpq/backend/cpq/orders/page.js.map +7 -0
  125. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +18 -0
  126. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +7 -0
  127. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +272 -0
  128. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +7 -0
  129. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +407 -0
  130. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +7 -0
  131. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +13 -0
  132. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +7 -0
  133. package/dist/modules/cpq/backend/cpq/price-rules/page.js +211 -0
  134. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +7 -0
  135. package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js +18 -0
  136. package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js.map +7 -0
  137. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +275 -0
  138. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +7 -0
  139. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +112 -0
  140. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +7 -0
  141. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +492 -0
  142. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +7 -0
  143. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js +50 -0
  144. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js.map +7 -0
  145. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +249 -0
  146. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +7 -0
  147. package/dist/modules/cpq/backend/cpq/pricing/page.js +87 -0
  148. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +7 -0
  149. package/dist/modules/cpq/backend/cpq/pricing/page.meta.js +18 -0
  150. package/dist/modules/cpq/backend/cpq/pricing/page.meta.js.map +7 -0
  151. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +652 -0
  152. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +7 -0
  153. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +1289 -0
  154. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +7 -0
  155. package/dist/modules/cpq/backend/cpq/quotes/page.js +283 -0
  156. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +7 -0
  157. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +18 -0
  158. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +7 -0
  159. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +214 -0
  160. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +7 -0
  161. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +1399 -0
  162. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +7 -0
  163. package/dist/modules/cpq/backend/cpq/specifications/page.js +148 -0
  164. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +7 -0
  165. package/dist/modules/cpq/backend/cpq/specifications/page.meta.js +18 -0
  166. package/dist/modules/cpq/backend/cpq/specifications/page.meta.js.map +7 -0
  167. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +204 -0
  168. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +7 -0
  169. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +191 -0
  170. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +7 -0
  171. package/dist/modules/cpq/backend/cpq/wizards/page.js +119 -0
  172. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +7 -0
  173. package/dist/modules/cpq/backend/cpq/wizards/page.meta.js +18 -0
  174. package/dist/modules/cpq/backend/cpq/wizards/page.meta.js.map +7 -0
  175. package/dist/modules/cpq/ce.js +151 -0
  176. package/dist/modules/cpq/ce.js.map +7 -0
  177. package/dist/modules/cpq/cli.js +11 -0
  178. package/dist/modules/cpq/cli.js.map +7 -0
  179. package/dist/modules/cpq/components/CpqListView.js +265 -0
  180. package/dist/modules/cpq/components/CpqListView.js.map +7 -0
  181. package/dist/modules/cpq/components/NumberInput.js +95 -0
  182. package/dist/modules/cpq/components/NumberInput.js.map +7 -0
  183. package/dist/modules/cpq/components/StatusPath.js +60 -0
  184. package/dist/modules/cpq/components/StatusPath.js.map +7 -0
  185. package/dist/modules/cpq/components/statusMaps.js +66 -0
  186. package/dist/modules/cpq/components/statusMaps.js.map +7 -0
  187. package/dist/modules/cpq/components/useCpqRowActions.js +125 -0
  188. package/dist/modules/cpq/components/useCpqRowActions.js.map +7 -0
  189. package/dist/modules/cpq/data/entities.js +1556 -0
  190. package/dist/modules/cpq/data/entities.js.map +7 -0
  191. package/dist/modules/cpq/data/validators.js +575 -0
  192. package/dist/modules/cpq/data/validators.js.map +7 -0
  193. package/dist/modules/cpq/di.js +57 -0
  194. package/dist/modules/cpq/di.js.map +7 -0
  195. package/dist/modules/cpq/events.js +50 -0
  196. package/dist/modules/cpq/events.js.map +7 -0
  197. package/dist/modules/cpq/index.js +16 -0
  198. package/dist/modules/cpq/index.js.map +7 -0
  199. package/dist/modules/cpq/lib/seeds/api.js +32 -0
  200. package/dist/modules/cpq/lib/seeds/api.js.map +7 -0
  201. package/dist/modules/cpq/lib/seeds/tenant-provisioning.js +158 -0
  202. package/dist/modules/cpq/lib/seeds/tenant-provisioning.js.map +7 -0
  203. package/dist/modules/cpq/lib/seeds.js +6 -0
  204. package/dist/modules/cpq/lib/seeds.js.map +7 -0
  205. package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js +40 -0
  206. package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js.map +7 -0
  207. package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js +54 -0
  208. package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js.map +7 -0
  209. package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js +15 -0
  210. package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js.map +7 -0
  211. package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js +62 -0
  212. package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js.map +7 -0
  213. package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js +56 -0
  214. package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js.map +7 -0
  215. package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js +26 -0
  216. package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js.map +7 -0
  217. package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js +37 -0
  218. package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js.map +7 -0
  219. package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js +35 -0
  220. package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js.map +7 -0
  221. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js +21 -0
  222. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js.map +7 -0
  223. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js +46 -0
  224. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js.map +7 -0
  225. package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js +118 -0
  226. package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js.map +7 -0
  227. package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js +63 -0
  228. package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js.map +7 -0
  229. package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js +28 -0
  230. package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js.map +7 -0
  231. package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js +36 -0
  232. package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js.map +7 -0
  233. package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js +14 -0
  234. package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js.map +7 -0
  235. package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js +14 -0
  236. package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js.map +7 -0
  237. package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js +58 -0
  238. package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js.map +7 -0
  239. package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js +29 -0
  240. package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js.map +7 -0
  241. package/dist/modules/cpq/services/cpqBundleService.js +447 -0
  242. package/dist/modules/cpq/services/cpqBundleService.js.map +7 -0
  243. package/dist/modules/cpq/services/cpqInventoryService.js +1371 -0
  244. package/dist/modules/cpq/services/cpqInventoryService.js.map +7 -0
  245. package/dist/modules/cpq/services/cpqOrderService.js +901 -0
  246. package/dist/modules/cpq/services/cpqOrderService.js.map +7 -0
  247. package/dist/modules/cpq/services/cpqPricingService.js +289 -0
  248. package/dist/modules/cpq/services/cpqPricingService.js.map +7 -0
  249. package/dist/modules/cpq/services/cpqProductService.js +204 -0
  250. package/dist/modules/cpq/services/cpqProductService.js.map +7 -0
  251. package/dist/modules/cpq/services/cpqQuotingService.js +1867 -0
  252. package/dist/modules/cpq/services/cpqQuotingService.js.map +7 -0
  253. package/dist/modules/cpq/services/cpqValidationService.js +234 -0
  254. package/dist/modules/cpq/services/cpqValidationService.js.map +7 -0
  255. package/dist/modules/cpq/services/cpqWizardService.js +137 -0
  256. package/dist/modules/cpq/services/cpqWizardService.js.map +7 -0
  257. package/dist/modules/cpq/services/types.js +114 -0
  258. package/dist/modules/cpq/services/types.js.map +7 -0
  259. package/dist/modules/cpq/setup.js +49 -0
  260. package/dist/modules/cpq/setup.js.map +7 -0
  261. package/dist/modules/cpq/subscribers/subscription-amended-log.js +18 -0
  262. package/dist/modules/cpq/subscribers/subscription-amended-log.js.map +7 -0
  263. package/dist/modules/cpq/subscribers/subscription-cancelled-log.js +19 -0
  264. package/dist/modules/cpq/subscribers/subscription-cancelled-log.js.map +7 -0
  265. package/dist/modules/cpq/subscribers/subscription-merged-log.js +20 -0
  266. package/dist/modules/cpq/subscribers/subscription-merged-log.js.map +7 -0
  267. package/dist/modules/cpq/subscribers/subscription-renewed-log.js +18 -0
  268. package/dist/modules/cpq/subscribers/subscription-renewed-log.js.map +7 -0
  269. package/dist/modules/cpq/subscribers/subscription-superseded-log.js +18 -0
  270. package/dist/modules/cpq/subscribers/subscription-superseded-log.js.map +7 -0
  271. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +192 -0
  272. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +7 -0
  273. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js +17 -0
  274. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js.map +7 -0
  275. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +79 -0
  276. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +7 -0
  277. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js +17 -0
  278. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js.map +7 -0
  279. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +181 -0
  280. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +7 -0
  281. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js +17 -0
  282. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js.map +7 -0
  283. package/dist/modules/cpq/widgets/injection-table.js +44 -0
  284. package/dist/modules/cpq/widgets/injection-table.js.map +7 -0
  285. package/dist/modules/cpq/workflows/WizardRunner.js +113 -0
  286. package/dist/modules/cpq/workflows/WizardRunner.js.map +7 -0
  287. package/dist/modules/cpq/workflows/WizardTrigger.js +116 -0
  288. package/dist/modules/cpq/workflows/WizardTrigger.js.map +7 -0
  289. package/dist/modules/cpq/workflows/engine.js +188 -0
  290. package/dist/modules/cpq/workflows/engine.js.map +7 -0
  291. package/dist/modules/cpq/workflows/registry.js +16 -0
  292. package/dist/modules/cpq/workflows/registry.js.map +7 -0
  293. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +119 -0
  294. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +7 -0
  295. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +24 -0
  296. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +7 -0
  297. package/dist/modules/cpq/workflows/steps/ItemListStep.js +115 -0
  298. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +7 -0
  299. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +327 -0
  300. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +7 -0
  301. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +231 -0
  302. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +7 -0
  303. package/dist/modules/cpq/workflows/steps/ReviewStep.js +152 -0
  304. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +7 -0
  305. package/dist/modules/cpq/workflows/steps/index.js +49 -0
  306. package/dist/modules/cpq/workflows/steps/index.js.map +7 -0
  307. package/dist/modules/cpq/workflows/types.js +1 -0
  308. package/dist/modules/cpq/workflows/types.js.map +7 -0
  309. package/package.json +150 -0
  310. package/src/modules/cpq/__integration__/TC-CPQ-001.spec.ts +177 -0
  311. package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +123 -0
  312. package/src/modules/cpq/__integration__/TC-CPQ-003.spec.ts +115 -0
  313. package/src/modules/cpq/__integration__/TC-CPQ-004.spec.ts +184 -0
  314. package/src/modules/cpq/__integration__/TC-CPQ-005.spec.ts +43 -0
  315. package/src/modules/cpq/__integration__/TC-CPQ-006.spec.ts +87 -0
  316. package/src/modules/cpq/__integration__/TC-CPQ-007.spec.ts +79 -0
  317. package/src/modules/cpq/__integration__/TC-CPQ-008.spec.ts +55 -0
  318. package/src/modules/cpq/__integration__/TC-CPQ-009.spec.ts +45 -0
  319. package/src/modules/cpq/__integration__/TC-CPQ-010.spec.ts +54 -0
  320. package/src/modules/cpq/__integration__/TC-CPQ-011.spec.ts +24 -0
  321. package/src/modules/cpq/__integration__/TC-CPQ-012.spec.ts +62 -0
  322. package/src/modules/cpq/__integration__/TC-CPQ-ARC-001.spec.ts +52 -0
  323. package/src/modules/cpq/__integration__/TC-CPQ-ARC-002.spec.ts +65 -0
  324. package/src/modules/cpq/__integration__/TC-CPQ-ARC-003.spec.ts +107 -0
  325. package/src/modules/cpq/__integration__/TC-CPQ-ARC-004.spec.ts +74 -0
  326. package/src/modules/cpq/__integration__/TC-CPQ-ARC-005.spec.ts +86 -0
  327. package/src/modules/cpq/__integration__/TC-CPQ-ARC-006.spec.ts +79 -0
  328. package/src/modules/cpq/__integration__/TC-CPQ-ARC-007.spec.ts +63 -0
  329. package/src/modules/cpq/__integration__/TC-CPQ-ARC-008.spec.ts +62 -0
  330. package/src/modules/cpq/__integration__/TC-CPQ-ARC-009.spec.ts +62 -0
  331. package/src/modules/cpq/__integration__/TC-CPQ-ARC-010.spec.ts +59 -0
  332. package/src/modules/cpq/__integration__/TC-CPQ-ARC-011.spec.ts +128 -0
  333. package/src/modules/cpq/__integration__/_helpers/cpqArcFixtures.ts +208 -0
  334. package/src/modules/cpq/__tests__/package-isolation.test.ts +232 -0
  335. package/src/modules/cpq/acl.ts +26 -0
  336. package/src/modules/cpq/api/_helpers/__tests__/handleArcError.test.ts +117 -0
  337. package/src/modules/cpq/api/_helpers/handleArcError.ts +81 -0
  338. package/src/modules/cpq/api/attribute-options/route.ts +55 -0
  339. package/src/modules/cpq/api/bundle-slots/route.ts +112 -0
  340. package/src/modules/cpq/api/bundle-tree/route.ts +31 -0
  341. package/src/modules/cpq/api/context.ts +39 -0
  342. package/src/modules/cpq/api/inventory/assets/route.ts +123 -0
  343. package/src/modules/cpq/api/inventory/assets/status/route.ts +34 -0
  344. package/src/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.ts +30 -0
  345. package/src/modules/cpq/api/inventory/customer/[customerId]/route.ts +31 -0
  346. package/src/modules/cpq/api/inventory/subscription-items/route.ts +107 -0
  347. package/src/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.ts +64 -0
  348. package/src/modules/cpq/api/inventory/subscriptions/expiring/route.ts +66 -0
  349. package/src/modules/cpq/api/inventory/subscriptions/route.ts +126 -0
  350. package/src/modules/cpq/api/inventory/subscriptions/status/route.ts +34 -0
  351. package/src/modules/cpq/api/offering-components/route.ts +103 -0
  352. package/src/modules/cpq/api/orders/[orderId]/activate/route.ts +48 -0
  353. package/src/modules/cpq/api/orders/[orderId]/route.ts +28 -0
  354. package/src/modules/cpq/api/orders/[orderId]/status/route.ts +38 -0
  355. package/src/modules/cpq/api/orders/route.ts +77 -0
  356. package/src/modules/cpq/api/price-rules/route.ts +210 -0
  357. package/src/modules/cpq/api/pricing-table-entries/route.ts +159 -0
  358. package/src/modules/cpq/api/pricing-tables/route.ts +202 -0
  359. package/src/modules/cpq/api/product-attributes/route.ts +175 -0
  360. package/src/modules/cpq/api/product-charges/route.ts +181 -0
  361. package/src/modules/cpq/api/product-offerings/route.ts +329 -0
  362. package/src/modules/cpq/api/product-relationships/route.ts +156 -0
  363. package/src/modules/cpq/api/product-specifications/route.ts +325 -0
  364. package/src/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.ts +77 -0
  365. package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +28 -0
  366. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +55 -0
  367. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +26 -0
  368. package/src/modules/cpq/api/quotes/[quoteId]/merge-meta/route.ts +78 -0
  369. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +34 -0
  370. package/src/modules/cpq/api/quotes/[quoteId]/route.ts +109 -0
  371. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +26 -0
  372. package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.ts +91 -0
  373. package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.ts +107 -0
  374. package/src/modules/cpq/api/quotes/attributes/route.ts +61 -0
  375. package/src/modules/cpq/api/quotes/from-subscription/route.ts +85 -0
  376. package/src/modules/cpq/api/quotes/price/route.ts +81 -0
  377. package/src/modules/cpq/api/quotes/route.ts +114 -0
  378. package/src/modules/cpq/api/quotes/validate-relationships/route.ts +100 -0
  379. package/src/modules/cpq/api/resolveQuotingService.ts +11 -0
  380. package/src/modules/cpq/api/wizards/resolve/route.ts +39 -0
  381. package/src/modules/cpq/api/wizards/route.ts +127 -0
  382. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +166 -0
  383. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +251 -0
  384. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +9 -0
  385. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +440 -0
  386. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +16 -0
  387. package/src/modules/cpq/backend/cpq/inventory/page.tsx +49 -0
  388. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +32 -0
  389. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +9 -0
  390. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +1085 -0
  391. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +1165 -0
  392. package/src/modules/cpq/backend/cpq/offerings/page.meta.ts +16 -0
  393. package/src/modules/cpq/backend/cpq/offerings/page.tsx +468 -0
  394. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +921 -0
  395. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +16 -0
  396. package/src/modules/cpq/backend/cpq/orders/page.tsx +182 -0
  397. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +310 -0
  398. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +9 -0
  399. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +472 -0
  400. package/src/modules/cpq/backend/cpq/price-rules/page.meta.ts +16 -0
  401. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +253 -0
  402. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +431 -0
  403. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +122 -0
  404. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +588 -0
  405. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.ts +90 -0
  406. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +338 -0
  407. package/src/modules/cpq/backend/cpq/pricing/page.meta.ts +16 -0
  408. package/src/modules/cpq/backend/cpq/pricing/page.tsx +111 -0
  409. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +737 -0
  410. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +1721 -0
  411. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +16 -0
  412. package/src/modules/cpq/backend/cpq/quotes/page.tsx +370 -0
  413. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +300 -0
  414. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +1722 -0
  415. package/src/modules/cpq/backend/cpq/specifications/page.meta.ts +16 -0
  416. package/src/modules/cpq/backend/cpq/specifications/page.tsx +191 -0
  417. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +319 -0
  418. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +234 -0
  419. package/src/modules/cpq/backend/cpq/wizards/page.meta.ts +16 -0
  420. package/src/modules/cpq/backend/cpq/wizards/page.tsx +141 -0
  421. package/src/modules/cpq/ce.ts +150 -0
  422. package/src/modules/cpq/cli.ts +10 -0
  423. package/src/modules/cpq/components/CpqListView.tsx +431 -0
  424. package/src/modules/cpq/components/NumberInput.tsx +166 -0
  425. package/src/modules/cpq/components/StatusPath.tsx +116 -0
  426. package/src/modules/cpq/components/statusMaps.ts +148 -0
  427. package/src/modules/cpq/components/useCpqRowActions.tsx +183 -0
  428. package/src/modules/cpq/data/__tests__/validators.arc.test.ts +271 -0
  429. package/src/modules/cpq/data/__tests__/validators.test.ts +782 -0
  430. package/src/modules/cpq/data/entities.ts +1467 -0
  431. package/src/modules/cpq/data/validators.ts +663 -0
  432. package/src/modules/cpq/di.ts +70 -0
  433. package/src/modules/cpq/events.ts +62 -0
  434. package/src/modules/cpq/index.ts +15 -0
  435. package/src/modules/cpq/lib/seeds/__tests__/api.test.ts +86 -0
  436. package/src/modules/cpq/lib/seeds/__tests__/tenant-provisioning.test.ts +349 -0
  437. package/src/modules/cpq/lib/seeds/api.ts +119 -0
  438. package/src/modules/cpq/lib/seeds/tenant-provisioning.ts +239 -0
  439. package/src/modules/cpq/lib/seeds.ts +18 -0
  440. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +7473 -0
  441. package/src/modules/cpq/migrations/Migration20260220220000_cpq.ts +51 -0
  442. package/src/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.ts +70 -0
  443. package/src/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.ts +15 -0
  444. package/src/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.ts +73 -0
  445. package/src/modules/cpq/migrations/Migration20260402000000_cpq_workflows.ts +59 -0
  446. package/src/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.ts +30 -0
  447. package/src/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.ts +36 -0
  448. package/src/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.ts +33 -0
  449. package/src/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.ts +22 -0
  450. package/src/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.ts +56 -0
  451. package/src/modules/cpq/migrations/Migration20260410100000_cpq_inventory.ts +118 -0
  452. package/src/modules/cpq/migrations/Migration20260411000000_cpq_orders.ts +62 -0
  453. package/src/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.ts +33 -0
  454. package/src/modules/cpq/migrations/Migration20260504064621_cpq_arc.ts +68 -0
  455. package/src/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.ts +30 -0
  456. package/src/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.ts +27 -0
  457. package/src/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.ts +82 -0
  458. package/src/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.ts +52 -0
  459. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +59 -0
  460. package/src/modules/cpq/services/__tests__/cpqBundleService.test.ts +661 -0
  461. package/src/modules/cpq/services/__tests__/cpqInventoryService.arc.test.ts +357 -0
  462. package/src/modules/cpq/services/__tests__/cpqInventoryService.test.ts +718 -0
  463. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +603 -0
  464. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +1012 -0
  465. package/src/modules/cpq/services/__tests__/cpqProductService.test.ts +603 -0
  466. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +143 -0
  467. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +658 -0
  468. package/src/modules/cpq/services/__tests__/cpqValidationService.test.ts +811 -0
  469. package/src/modules/cpq/services/__tests__/cpqWizardService.test.ts +389 -0
  470. package/src/modules/cpq/services/__tests__/types.test.ts +359 -0
  471. package/src/modules/cpq/services/cpqBundleService.ts +671 -0
  472. package/src/modules/cpq/services/cpqInventoryService.ts +1929 -0
  473. package/src/modules/cpq/services/cpqOrderService.ts +1230 -0
  474. package/src/modules/cpq/services/cpqPricingService.ts +412 -0
  475. package/src/modules/cpq/services/cpqProductService.ts +311 -0
  476. package/src/modules/cpq/services/cpqQuotingService.ts +2638 -0
  477. package/src/modules/cpq/services/cpqValidationService.ts +323 -0
  478. package/src/modules/cpq/services/cpqWizardService.ts +204 -0
  479. package/src/modules/cpq/services/types.ts +303 -0
  480. package/src/modules/cpq/setup.ts +63 -0
  481. package/src/modules/cpq/subscribers/subscription-amended-log.ts +20 -0
  482. package/src/modules/cpq/subscribers/subscription-cancelled-log.ts +19 -0
  483. package/src/modules/cpq/subscribers/subscription-merged-log.ts +22 -0
  484. package/src/modules/cpq/subscribers/subscription-renewed-log.ts +18 -0
  485. package/src/modules/cpq/subscribers/subscription-superseded-log.ts +19 -0
  486. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +339 -0
  487. package/src/modules/cpq/widgets/injection/customer-inventory/widget.ts +16 -0
  488. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +96 -0
  489. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.ts +16 -0
  490. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +265 -0
  491. package/src/modules/cpq/widgets/injection/quote-configurator/widget.ts +16 -0
  492. package/src/modules/cpq/widgets/injection-table.ts +42 -0
  493. package/src/modules/cpq/workflows/WizardRunner.tsx +158 -0
  494. package/src/modules/cpq/workflows/WizardTrigger.tsx +142 -0
  495. package/src/modules/cpq/workflows/engine.tsx +278 -0
  496. package/src/modules/cpq/workflows/registry.ts +15 -0
  497. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +136 -0
  498. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +34 -0
  499. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +146 -0
  500. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +438 -0
  501. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +287 -0
  502. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +219 -0
  503. package/src/modules/cpq/workflows/steps/index.ts +51 -0
  504. 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
+ }