@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,103 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { resolveCpqRouteContext } from '../context'
4
+ import { cpqOfferingComponentCreateSchema, cpqOfferingComponentUpdateSchema } from '../../data/validators'
5
+ import type { DefaultCpqBundleService } from '../../services/cpqBundleService'
6
+
7
+ export const metadata = {
8
+ GET: { requireAuth: true, requireFeatures: ['cpq.offerings.view'] },
9
+ POST: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },
10
+ PUT: { requireAuth: true, requireFeatures: ['cpq.offerings.manage'] },
11
+ DELETE: { requireAuth: true, requireFeatures: ['cpq.offerings.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 offeringId = url.searchParams.get('offeringId')
24
+ if (!offeringId) return NextResponse.json({ error: 'offeringId is required' }, { status: 400 })
25
+
26
+ const components = await service.getComponents(offeringId, scope)
27
+
28
+ const slotId = url.searchParams.get('slotId')
29
+ const filtered = slotId ? components.filter((c) => c.slotId === slotId) : components
30
+
31
+ return NextResponse.json({ items: filtered })
32
+ } catch (err) {
33
+ console.error('[cpq/offering-components.GET]', err)
34
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
35
+ }
36
+ }
37
+
38
+ export async function POST(req: Request) {
39
+ try {
40
+ const ctx = await resolveCpqRouteContext(req)
41
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
42
+
43
+ const body = cpqOfferingComponentCreateSchema.parse(await req.json())
44
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
45
+ const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
46
+
47
+ const component = await service.addComponent(body, scope)
48
+ return NextResponse.json(component, { status: 201 })
49
+ } catch (err) {
50
+ if (err instanceof z.ZodError) {
51
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
52
+ }
53
+ if (err instanceof Error && err.message.startsWith('V-')) {
54
+ return NextResponse.json({ error: err.message }, { status: 400 })
55
+ }
56
+ console.error('[cpq/offering-components.POST]', err)
57
+ return NextResponse.json({ error: err instanceof Error ? err.message : 'Internal error' }, { status: 500 })
58
+ }
59
+ }
60
+
61
+ export async function PUT(req: Request) {
62
+ try {
63
+ const ctx = await resolveCpqRouteContext(req)
64
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
65
+
66
+ const body = cpqOfferingComponentUpdateSchema.parse(await req.json())
67
+ const { id, ...updates } = body
68
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
69
+ const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
70
+
71
+ const component = await service.updateComponent(id, updates, scope)
72
+ if (!component) return NextResponse.json({ error: 'Not found' }, { status: 404 })
73
+
74
+ return NextResponse.json(component)
75
+ } catch (err) {
76
+ if (err instanceof z.ZodError) {
77
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
78
+ }
79
+ console.error('[cpq/offering-components.PUT]', err)
80
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
81
+ }
82
+ }
83
+
84
+ export async function DELETE(req: Request) {
85
+ try {
86
+ const ctx = await resolveCpqRouteContext(req)
87
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
88
+
89
+ const { id } = (await req.json()) as { id?: string }
90
+ if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })
91
+
92
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
93
+ const service = ctx.container.resolve('cpqBundleService') as DefaultCpqBundleService
94
+
95
+ const deleted = await service.removeComponent(id, scope)
96
+ if (!deleted) return NextResponse.json({ error: 'Not found' }, { status: 404 })
97
+
98
+ return NextResponse.json({ success: true })
99
+ } catch (err) {
100
+ console.error('[cpq/offering-components.DELETE]', err)
101
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
102
+ }
103
+ }
@@ -0,0 +1,48 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { OptimisticLockError } from '@mikro-orm/core'
3
+ import { resolveCpqRouteContext } from '../../../context'
4
+ import { OrderError } from '../../../../services/cpqOrderService'
5
+ import type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'
6
+
7
+ export const metadata = {
8
+ POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },
9
+ }
10
+
11
+ export async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {
12
+ try {
13
+ const ctx = await resolveCpqRouteContext(req)
14
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
15
+
16
+ const { orderId } = await params
17
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
18
+
19
+ const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService
20
+ const result = await service.activateOrder(orderId, scope)
21
+ return NextResponse.json(result)
22
+ } catch (err) {
23
+ if (err instanceof OrderError) {
24
+ return NextResponse.json({ error: err.message }, { status: err.status })
25
+ }
26
+ // XD-250 ARC: optimistic-lock collision on subscription.version when two
27
+ // ARC orders touching the same sub race to activate. Spec § Risks:
28
+ // "second activation gets 409, order stays in pending_activation,
29
+ // retryable". Constructor-name fallback survives Turbopack class drift.
30
+ if (
31
+ err instanceof OptimisticLockError ||
32
+ (err &&
33
+ typeof err === 'object' &&
34
+ (err as { constructor?: { name?: string } }).constructor?.name === 'OptimisticLockError')
35
+ ) {
36
+ return NextResponse.json(
37
+ { error: 'Concurrent modification detected', details: (err as Error).message },
38
+ { status: 409 },
39
+ )
40
+ }
41
+ const errObj = err as Record<string, unknown>
42
+ if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {
43
+ return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })
44
+ }
45
+ console.error('[cpq/orders/[orderId]/activate.POST]', err)
46
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
47
+ }
48
+ }
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { resolveCpqRouteContext } from '../../context'
3
+ import { OrderError } from '../../../services/cpqOrderService'
4
+ import type { DefaultCpqOrderService } from '../../../services/cpqOrderService'
5
+
6
+ export const metadata = {
7
+ GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },
8
+ }
9
+
10
+ export async function GET(req: Request, { params }: { params: Promise<{ orderId: string }> }) {
11
+ try {
12
+ const ctx = await resolveCpqRouteContext(req)
13
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
14
+
15
+ const { orderId } = await params
16
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
17
+
18
+ const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService
19
+ const result = await service.getOrder(orderId, scope)
20
+ return NextResponse.json(result)
21
+ } catch (err) {
22
+ if (err instanceof OrderError) {
23
+ return NextResponse.json({ error: err.message }, { status: err.status })
24
+ }
25
+ console.error('[cpq/orders/[orderId].GET]', err)
26
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
27
+ }
28
+ }
@@ -0,0 +1,38 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { resolveCpqRouteContext } from '../../../context'
4
+ import { cpqOrderStatusTransitionSchema } from '../../../../data/validators'
5
+ import { OrderError } from '../../../../services/cpqOrderService'
6
+ import type { DefaultCpqOrderService } from '../../../../services/cpqOrderService'
7
+
8
+ export const metadata = {
9
+ POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },
10
+ }
11
+
12
+ export async function POST(req: Request, { params }: { params: Promise<{ orderId: string }> }) {
13
+ try {
14
+ const ctx = await resolveCpqRouteContext(req)
15
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
16
+
17
+ const { orderId } = await params
18
+ const body = cpqOrderStatusTransitionSchema.parse(await req.json())
19
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
20
+
21
+ const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService
22
+ const result = await service.transitionOrderStatus(orderId, body.targetStatus, scope)
23
+ return NextResponse.json(result)
24
+ } catch (err) {
25
+ if (err instanceof z.ZodError) {
26
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
27
+ }
28
+ if (err instanceof OrderError) {
29
+ return NextResponse.json({ error: err.message }, { status: err.status })
30
+ }
31
+ const errObj = err as Record<string, unknown>
32
+ if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {
33
+ return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })
34
+ }
35
+ console.error('[cpq/orders/[orderId]/status.POST]', err)
36
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
37
+ }
38
+ }
@@ -0,0 +1,77 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { resolveCpqRouteContext } from '../context'
4
+ import { cpqConvertQuoteToOrderSchema } from '../../data/validators'
5
+ import { OrderError } from '../../services/cpqOrderService'
6
+ import type { DefaultCpqOrderService } from '../../services/cpqOrderService'
7
+
8
+ export const metadata = {
9
+ POST: { requireAuth: true, requireFeatures: ['cpq.orders.manage'] },
10
+ GET: { requireAuth: true, requireFeatures: ['cpq.orders.view'] },
11
+ }
12
+
13
+ export async function POST(req: Request) {
14
+ try {
15
+ const ctx = await resolveCpqRouteContext(req)
16
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
17
+
18
+ const body = cpqConvertQuoteToOrderSchema.parse(await req.json())
19
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
20
+
21
+ const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService
22
+ const result = await service.convertQuoteToOrder(body.quoteId, scope)
23
+ return NextResponse.json(result, { status: 201 })
24
+ } catch (err) {
25
+ return handleError(err, 'cpq/orders.POST')
26
+ }
27
+ }
28
+
29
+ export async function GET(req: Request) {
30
+ try {
31
+ const ctx = await resolveCpqRouteContext(req)
32
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
33
+
34
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
35
+ const url = new URL(req.url)
36
+ const ALLOWED_SORT_FIELDS = ['createdAt', 'updatedAt', 'cpqStatus', 'activatedAt', 'currencyCode'] as const
37
+ const sortFieldParam = url.searchParams.get('sortField') ?? ''
38
+ const sortField = (ALLOWED_SORT_FIELDS as readonly string[]).includes(sortFieldParam)
39
+ ? (sortFieldParam as (typeof ALLOWED_SORT_FIELDS)[number])
40
+ : undefined
41
+ const sortDir = url.searchParams.get('sortDir') === 'asc' ? 'asc' : 'desc'
42
+
43
+ const filters = {
44
+ customerId: url.searchParams.get('customerId') ?? undefined,
45
+ sourceQuoteId: url.searchParams.get('sourceQuoteId') ?? undefined,
46
+ salesOrderId: url.searchParams.get('salesOrderId') ?? undefined,
47
+ cpqStatus: url.searchParams.get('cpqStatus') ?? undefined,
48
+ currencyCode: url.searchParams.get('currencyCode') ?? undefined,
49
+ search: url.searchParams.get('search') ?? undefined,
50
+ sortField,
51
+ sortDir,
52
+ page: url.searchParams.has('page') ? Number(url.searchParams.get('page')) : undefined,
53
+ pageSize: url.searchParams.has('pageSize') ? Number(url.searchParams.get('pageSize')) : undefined,
54
+ } as const
55
+
56
+ const service = ctx.container.resolve('cpqOrderService') as DefaultCpqOrderService
57
+ const result = await service.listOrders(filters, scope)
58
+ return NextResponse.json(result)
59
+ } catch (err) {
60
+ return handleError(err, 'cpq/orders.GET')
61
+ }
62
+ }
63
+
64
+ function handleError(err: unknown, context: string) {
65
+ if (err instanceof z.ZodError) {
66
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
67
+ }
68
+ if (err instanceof OrderError) {
69
+ return NextResponse.json({ error: err.message }, { status: err.status })
70
+ }
71
+ const errObj = err as Record<string, unknown>
72
+ if (typeof errObj?.status === 'number' && typeof errObj?.error === 'string') {
73
+ return NextResponse.json({ error: errObj.error }, { status: errObj.status as number })
74
+ }
75
+ console.error(`[${context}]`, err)
76
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
77
+ }
@@ -0,0 +1,210 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { resolveCpqRouteContext } from '../context'
4
+ import { CpqPriceRule, CpqProductOffering } from '../../data/entities'
5
+ import { cpqPriceRuleCreateSchema, cpqPriceRuleUpdateSchema } from '../../data/validators'
6
+
7
+ export const metadata = {
8
+ GET: { requireAuth: true, requireFeatures: ['cpq.pricing.view'] },
9
+ POST: { requireAuth: true, requireFeatures: ['cpq.pricing.manage'] },
10
+ PUT: { requireAuth: true, requireFeatures: ['cpq.pricing.manage'] },
11
+ DELETE: { requireAuth: true, requireFeatures: ['cpq.pricing.manage'] },
12
+ }
13
+
14
+ function serializeRule(rule: CpqPriceRule) {
15
+ return {
16
+ id: rule.id,
17
+ code: rule.code,
18
+ name: rule.name,
19
+ description: rule.description ?? null,
20
+ productOfferingId: rule.productOfferingId ?? null,
21
+ ruleType: rule.ruleType,
22
+ value: Number(rule.value),
23
+ chargeCodeFilter: rule.chargeCodeFilter ?? null,
24
+ chargeTypeFilter: rule.chargeTypeFilter ?? null,
25
+ applicabilityCondition: rule.applicabilityCondition ?? null,
26
+ sortOrder: rule.sortOrder,
27
+ isActive: rule.isActive,
28
+ createdAt: rule.createdAt,
29
+ updatedAt: rule.updatedAt,
30
+ }
31
+ }
32
+
33
+ export async function GET(req: Request) {
34
+ try {
35
+ const ctx = await resolveCpqRouteContext(req)
36
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
37
+
38
+ const url = new URL(req.url)
39
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }
40
+
41
+ const id = url.searchParams.get('id')
42
+ if (id) {
43
+ const item = await ctx.em.findOne(CpqPriceRule, { id, ...scope })
44
+ if (!item) return NextResponse.json({ error: 'Not found' }, { status: 404 })
45
+ return NextResponse.json(serializeRule(item))
46
+ }
47
+
48
+ const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))
49
+ const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))
50
+
51
+ const filters: Record<string, unknown> = { ...scope }
52
+
53
+ const productOfferingId = url.searchParams.get('productOfferingId')
54
+ if (productOfferingId) {
55
+ filters.productOfferingId = productOfferingId
56
+ }
57
+
58
+ const globalOnly = url.searchParams.get('globalOnly')
59
+ if (globalOnly === 'true' && !productOfferingId) {
60
+ filters.productOfferingId = null
61
+ }
62
+
63
+ const ruleType = url.searchParams.get('ruleType')
64
+ if (ruleType) filters.ruleType = ruleType
65
+
66
+ const chargeTypeFilter = url.searchParams.get('chargeTypeFilter')
67
+ if (chargeTypeFilter) filters.chargeTypeFilter = chargeTypeFilter
68
+
69
+ const isActiveParam = url.searchParams.get('isActive')
70
+ if (isActiveParam === 'true') filters.isActive = true
71
+ else if (isActiveParam === 'false') filters.isActive = false
72
+
73
+ // Free-text search across code, name, description (case-insensitive)
74
+ const search = url.searchParams.get('search')?.trim()
75
+ if (search) {
76
+ filters.$or = [
77
+ { code: { $ilike: `%${search}%` } },
78
+ { name: { $ilike: `%${search}%` } },
79
+ { description: { $ilike: `%${search}%` } },
80
+ ]
81
+ }
82
+
83
+ // Configurable sort
84
+ const ALLOWED_SORT_FIELDS = ['sortOrder', 'createdAt', 'updatedAt', 'code', 'name', 'ruleType'] as const
85
+ const sortFieldParam = url.searchParams.get('sortField') ?? ''
86
+ const sortField = (ALLOWED_SORT_FIELDS as readonly string[]).includes(sortFieldParam)
87
+ ? (sortFieldParam as (typeof ALLOWED_SORT_FIELDS)[number])
88
+ : 'sortOrder'
89
+ const sortDir = url.searchParams.get('sortDir') === 'desc' ? 'desc' : 'asc'
90
+
91
+ const [items, total] = await ctx.em.findAndCount(CpqPriceRule, filters, {
92
+ limit: pageSize,
93
+ offset: (page - 1) * pageSize,
94
+ orderBy: { [sortField]: sortDir },
95
+ })
96
+
97
+ // Batch-load offering names for the visible rules (single query — avoids N+1)
98
+ const offeringIds = Array.from(
99
+ new Set(items.map((r) => r.productOfferingId).filter((id): id is string => !!id)),
100
+ )
101
+ const offeringNameById = new Map<string, string>()
102
+ if (offeringIds.length > 0) {
103
+ const offerings = await ctx.em.find(
104
+ CpqProductOffering,
105
+ { id: { $in: offeringIds }, ...scope },
106
+ )
107
+ for (const o of offerings) offeringNameById.set(o.id, o.name)
108
+ }
109
+
110
+ return NextResponse.json({
111
+ items: items.map((rule) => ({
112
+ ...serializeRule(rule),
113
+ productOfferingName: rule.productOfferingId
114
+ ? offeringNameById.get(rule.productOfferingId) ?? null
115
+ : null,
116
+ })),
117
+ total,
118
+ page,
119
+ pageSize,
120
+ totalPages: Math.ceil(total / pageSize),
121
+ })
122
+ } catch (err) {
123
+ console.error('[cpq/price-rules.GET]', err)
124
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
125
+ }
126
+ }
127
+
128
+ export async function POST(req: Request) {
129
+ try {
130
+ const ctx = await resolveCpqRouteContext(req)
131
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
132
+
133
+ const body = cpqPriceRuleCreateSchema.parse(await req.json())
134
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
135
+
136
+ const entity = ctx.em.create(CpqPriceRule, {
137
+ ...scope,
138
+ code: body.code,
139
+ name: body.name,
140
+ description: body.description ?? undefined,
141
+ productOfferingId: body.productOfferingId ?? undefined,
142
+ ruleType: body.ruleType,
143
+ value: String(body.value),
144
+ chargeCodeFilter: body.chargeCodeFilter ?? undefined,
145
+ chargeTypeFilter: body.chargeTypeFilter ?? undefined,
146
+ applicabilityCondition: body.applicabilityCondition ?? undefined,
147
+ sortOrder: body.sortOrder,
148
+ isActive: body.isActive,
149
+ })
150
+ await ctx.em.flush()
151
+
152
+ return NextResponse.json(serializeRule(entity), { status: 201 })
153
+ } catch (err) {
154
+ if (err instanceof z.ZodError) {
155
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
156
+ }
157
+ console.error('[cpq/price-rules.POST]', err)
158
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
159
+ }
160
+ }
161
+
162
+ export async function PUT(req: Request) {
163
+ try {
164
+ const ctx = await resolveCpqRouteContext(req)
165
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
166
+
167
+ const body = cpqPriceRuleUpdateSchema.parse(await req.json())
168
+ const { id, ...updates } = body
169
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }
170
+
171
+ const entity = await ctx.em.findOne(CpqPriceRule, { id, ...scope })
172
+ if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })
173
+
174
+ const { value: rawValue, ...rest } = updates
175
+ const assignData: Record<string, unknown> = { ...rest }
176
+ if (rawValue !== undefined) assignData.value = String(rawValue)
177
+ ctx.em.assign(entity, assignData)
178
+ await ctx.em.flush()
179
+
180
+ return NextResponse.json(serializeRule(entity))
181
+ } catch (err) {
182
+ if (err instanceof z.ZodError) {
183
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
184
+ }
185
+ console.error('[cpq/price-rules.PUT]', err)
186
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
187
+ }
188
+ }
189
+
190
+ export async function DELETE(req: Request) {
191
+ try {
192
+ const ctx = await resolveCpqRouteContext(req)
193
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
194
+
195
+ const { id } = (await req.json()) as { id?: string }
196
+ if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })
197
+
198
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }
199
+ const entity = await ctx.em.findOne(CpqPriceRule, { id, ...scope })
200
+ if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })
201
+
202
+ entity.deletedAt = new Date()
203
+ await ctx.em.flush()
204
+
205
+ return NextResponse.json({ success: true })
206
+ } catch (err) {
207
+ console.error('[cpq/price-rules.DELETE]', err)
208
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
209
+ }
210
+ }
@@ -0,0 +1,159 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+ import { resolveCpqRouteContext } from '../context'
4
+ import { CpqPricingTableEntry } from '../../data/entities'
5
+ import { cpqPricingTableEntryCreateSchema, cpqPricingTableEntryUpdateSchema } from '../../data/validators'
6
+
7
+ export const metadata = {
8
+ GET: { requireAuth: true, requireFeatures: ['cpq.pricing.view'] },
9
+ POST: { requireAuth: true, requireFeatures: ['cpq.pricing.manage'] },
10
+ PUT: { requireAuth: true, requireFeatures: ['cpq.pricing.manage'] },
11
+ DELETE: { requireAuth: true, requireFeatures: ['cpq.pricing.manage'] },
12
+ }
13
+
14
+ function serializeEntry(e: CpqPricingTableEntry) {
15
+ return {
16
+ id: e.id,
17
+ pricingTableId: e.pricingTableId,
18
+ dimensionValues: e.dimensionValues,
19
+ tierNumber: e.tierNumber,
20
+ rangeFrom: e.rangeFrom,
21
+ rangeTo: e.rangeTo,
22
+ currencyCode: e.currencyCode,
23
+ prices: e.prices,
24
+ isActive: e.isActive,
25
+ createdAt: e.createdAt,
26
+ updatedAt: e.updatedAt,
27
+ }
28
+ }
29
+
30
+ export async function GET(req: Request) {
31
+ try {
32
+ const ctx = await resolveCpqRouteContext(req)
33
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
34
+
35
+ const url = new URL(req.url)
36
+ const pricingTableId = url.searchParams.get('pricingTableId')
37
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
38
+ if (!pricingTableId || !uuidRegex.test(pricingTableId)) {
39
+ return NextResponse.json({ error: 'pricingTableId is required and must be a valid UUID' }, { status: 400 })
40
+ }
41
+
42
+ const page = Math.max(1, Number(url.searchParams.get('page') ?? '1'))
43
+ const pageSize = Math.min(100, Math.max(1, Number(url.searchParams.get('pageSize') ?? '50')))
44
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }
45
+
46
+ const filters: Record<string, unknown> = { ...scope, pricingTableId }
47
+
48
+ const [items, total] = await ctx.em.findAndCount(CpqPricingTableEntry, filters, {
49
+ limit: pageSize,
50
+ offset: (page - 1) * pageSize,
51
+ orderBy: { createdAt: 'desc' },
52
+ })
53
+
54
+ return NextResponse.json({
55
+ items: items.map(serializeEntry),
56
+ total,
57
+ page,
58
+ pageSize,
59
+ totalPages: Math.ceil(total / pageSize),
60
+ })
61
+ } catch (err) {
62
+ console.error('[cpq/pricing-table-entries.GET]', err)
63
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
64
+ }
65
+ }
66
+
67
+ export async function POST(req: Request) {
68
+ try {
69
+ const ctx = await resolveCpqRouteContext(req)
70
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
71
+
72
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId }
73
+ const rawBody = await req.json()
74
+
75
+ const mapEntry = (parsed: ReturnType<typeof cpqPricingTableEntryCreateSchema.parse>) => ({
76
+ ...scope,
77
+ pricingTableId: parsed.pricingTableId,
78
+ dimensionValues: parsed.dimensionValues as Record<string, string>,
79
+ tierNumber: parsed.tierNumber ?? null,
80
+ rangeFrom: parsed.rangeFrom != null ? String(parsed.rangeFrom) : null,
81
+ rangeTo: parsed.rangeTo != null ? String(parsed.rangeTo) : null,
82
+ currencyCode: parsed.currencyCode,
83
+ prices: parsed.prices as Record<string, number>,
84
+ isActive: parsed.isActive,
85
+ })
86
+
87
+ if (Array.isArray(rawBody)) {
88
+ const entries = rawBody.map((item) => {
89
+ const parsed = cpqPricingTableEntryCreateSchema.parse(item)
90
+ return ctx.em.create(CpqPricingTableEntry, mapEntry(parsed) as any)
91
+ })
92
+ await ctx.em.flush()
93
+
94
+ return NextResponse.json({ items: entries.map(serializeEntry) }, { status: 201 })
95
+ }
96
+
97
+ const body = cpqPricingTableEntryCreateSchema.parse(rawBody)
98
+ const entity = ctx.em.create(CpqPricingTableEntry, mapEntry(body) as any)
99
+ await ctx.em.flush()
100
+
101
+ return NextResponse.json(serializeEntry(entity), { status: 201 })
102
+ } catch (err) {
103
+ if (err instanceof z.ZodError) {
104
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
105
+ }
106
+ console.error('[cpq/pricing-table-entries.POST]', err)
107
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
108
+ }
109
+ }
110
+
111
+ export async function PUT(req: Request) {
112
+ try {
113
+ const ctx = await resolveCpqRouteContext(req)
114
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
115
+
116
+ const body = cpqPricingTableEntryUpdateSchema.parse(await req.json())
117
+ const { id, ...rawUpdates } = body
118
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }
119
+
120
+ const entity = await ctx.em.findOne(CpqPricingTableEntry, { id, ...scope })
121
+ if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })
122
+
123
+ const updates: Record<string, unknown> = { ...rawUpdates }
124
+ if (rawUpdates.rangeFrom !== undefined) updates.rangeFrom = rawUpdates.rangeFrom != null ? String(rawUpdates.rangeFrom) : null
125
+ if (rawUpdates.rangeTo !== undefined) updates.rangeTo = rawUpdates.rangeTo != null ? String(rawUpdates.rangeTo) : null
126
+ ctx.em.assign(entity, updates as any)
127
+ await ctx.em.flush()
128
+
129
+ return NextResponse.json(serializeEntry(entity))
130
+ } catch (err) {
131
+ if (err instanceof z.ZodError) {
132
+ return NextResponse.json({ error: 'Validation error', details: err.issues }, { status: 400 })
133
+ }
134
+ console.error('[cpq/pricing-table-entries.PUT]', err)
135
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
136
+ }
137
+ }
138
+
139
+ export async function DELETE(req: Request) {
140
+ try {
141
+ const ctx = await resolveCpqRouteContext(req)
142
+ if (!ctx.auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
143
+
144
+ const { id } = (await req.json()) as { id?: string }
145
+ if (!id) return NextResponse.json({ error: 'Missing id' }, { status: 400 })
146
+
147
+ const scope = { organizationId: ctx.organizationId, tenantId: ctx.tenantId, deletedAt: null }
148
+ const entity = await ctx.em.findOne(CpqPricingTableEntry, { id, ...scope })
149
+ if (!entity) return NextResponse.json({ error: 'Not found' }, { status: 404 })
150
+
151
+ entity.deletedAt = new Date()
152
+ await ctx.em.flush()
153
+
154
+ return NextResponse.json({ success: true })
155
+ } catch (err) {
156
+ console.error('[cpq/pricing-table-entries.DELETE]', err)
157
+ return NextResponse.json({ error: 'Internal error' }, { status: 500 })
158
+ }
159
+ }