@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,917 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
+ import { useParams, useRouter } from "next/navigation";
6
+ import { Alert } from "@open-mercato/ui/primitives/alert";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
8
+ import { Tag } from "@open-mercato/ui/primitives/tag";
9
+ import { NumberInput } from "../../../../components/NumberInput.js";
10
+ import {
11
+ chargeTypeMap,
12
+ formatStatusLabel,
13
+ lifecycleStatusMap
14
+ } from "../../../../components/statusMaps.js";
15
+ const EMPTY_FORM = {
16
+ code: "",
17
+ name: "",
18
+ description: "",
19
+ offeringType: "simple",
20
+ lifecycleStatus: "draft",
21
+ effectiveFrom: null,
22
+ effectiveTo: null,
23
+ isActive: true,
24
+ specId: ""
25
+ };
26
+ const EMPTY_CHARGE = {
27
+ code: "",
28
+ name: "",
29
+ description: null,
30
+ chargeType: "mrc",
31
+ pricingMethod: "flat",
32
+ pricingTableId: null,
33
+ priceColumnKey: null,
34
+ fixedPrice: null,
35
+ currencyCode: "USD",
36
+ quantityAttributeCode: null,
37
+ applicabilityCondition: null,
38
+ sortOrder: 0,
39
+ isActive: true
40
+ };
41
+ const PRICING_METHOD_LABELS = {
42
+ flat: "flat price",
43
+ fixed: "flat price",
44
+ // legacy alias — see validators.ts
45
+ per_unit: "per unit",
46
+ tiered: "tiered"
47
+ };
48
+ function isChargeComplete(charge) {
49
+ return chargeMissingFields(charge).length === 0;
50
+ }
51
+ function chargeMissingFields(charge) {
52
+ const method = charge.pricingMethod === "fixed" ? "flat" : charge.pricingMethod;
53
+ const missing = [];
54
+ if (method === "flat") {
55
+ if (!charge.fixedPrice) missing.push("Fixed Price");
56
+ if (!charge.currencyCode) missing.push("Currency");
57
+ } else if (method === "per_unit" || method === "tiered") {
58
+ if (!charge.pricingTableId) missing.push("Pricing Table");
59
+ if (!charge.priceColumnKey) missing.push("Price Column");
60
+ if (!charge.quantityAttributeCode) missing.push("Quantity Attribute");
61
+ }
62
+ return missing;
63
+ }
64
+ function OfferingDetailPage(props) {
65
+ const t = useT();
66
+ const router = useRouter();
67
+ const urlParams = useParams();
68
+ const offeringId = props.params?.id ?? urlParams?.id;
69
+ const isNew = offeringId === "new";
70
+ const [tab, setTab] = React.useState("general");
71
+ const [form, setForm] = React.useState(EMPTY_FORM);
72
+ const [designTimeValues, setDesignTimeValues] = React.useState({});
73
+ const [charges, setCharges] = React.useState([]);
74
+ const [attributes, setAttributes] = React.useState([]);
75
+ const [specs, setSpecs] = React.useState([]);
76
+ const [specDetail, setSpecDetail] = React.useState(null);
77
+ const [pricingTables, setPricingTables] = React.useState([]);
78
+ const [components, setComponents] = React.useState([]);
79
+ const [bundleSlots, setBundleSlots] = React.useState([]);
80
+ const [addingComponent, setAddingComponent] = React.useState(null);
81
+ const [slotOfferings, setSlotOfferings] = React.useState({});
82
+ const [loading, setLoading] = React.useState(!isNew);
83
+ const [saving, setSaving] = React.useState(false);
84
+ const [error, setError] = React.useState(null);
85
+ const [editingCharge, setEditingCharge] = React.useState(null);
86
+ const [generalMode, setGeneralMode] = React.useState(isNew ? "edit" : "view");
87
+ const [formSnapshot, setFormSnapshot] = React.useState(null);
88
+ const [designTimeSnapshot, setDesignTimeSnapshot] = React.useState(null);
89
+ React.useEffect(() => {
90
+ let cancelled = false;
91
+ async function load() {
92
+ try {
93
+ const specsRes = await fetch("/api/cpq/product-specifications?pageSize=200");
94
+ if (specsRes.ok) {
95
+ const d = await specsRes.json();
96
+ if (!cancelled) setSpecs(d.items ?? []);
97
+ }
98
+ if (isNew) return;
99
+ const res = await fetch(`/api/cpq/product-offerings?id=${encodeURIComponent(offeringId)}`);
100
+ if (!res.ok) {
101
+ if (!cancelled) setError("Failed to load offering");
102
+ return;
103
+ }
104
+ const data = await res.json();
105
+ if (!cancelled) {
106
+ setForm({
107
+ code: data.code,
108
+ name: data.name,
109
+ description: data.description ?? "",
110
+ offeringType: data.offeringType ?? "simple",
111
+ lifecycleStatus: data.lifecycleStatus,
112
+ effectiveFrom: data.effectiveFrom,
113
+ effectiveTo: data.effectiveTo,
114
+ isActive: data.isActive,
115
+ specId: data.specId
116
+ });
117
+ setDesignTimeValues(data.designTimeValues ?? {});
118
+ setCharges(data.charges ?? []);
119
+ setSpecDetail(data.specification ?? null);
120
+ setComponents(data.components ?? []);
121
+ if (data.specId) {
122
+ const attrRes = await fetch(`/api/cpq/product-attributes?specId=${encodeURIComponent(data.specId)}&pageSize=200`);
123
+ if (attrRes.ok) {
124
+ const attrData = await attrRes.json();
125
+ if (!cancelled) setAttributes(attrData.items ?? []);
126
+ }
127
+ }
128
+ if (data.offeringType === "bundle" && data.specId) {
129
+ const slotsRes = await fetch(`/api/cpq/bundle-slots?specId=${encodeURIComponent(data.specId)}`);
130
+ if (slotsRes.ok) {
131
+ const slotsData = await slotsRes.json();
132
+ if (!cancelled) setBundleSlots(slotsData.items ?? []);
133
+ }
134
+ }
135
+ }
136
+ } catch (err) {
137
+ console.error("Failed to load", err);
138
+ if (!cancelled) setError("Failed to load");
139
+ } finally {
140
+ if (!cancelled) setLoading(false);
141
+ }
142
+ }
143
+ load();
144
+ return () => {
145
+ cancelled = true;
146
+ };
147
+ }, [offeringId, isNew]);
148
+ const loadSpecAttributes = async (specId) => {
149
+ if (!specId) {
150
+ setAttributes([]);
151
+ return;
152
+ }
153
+ try {
154
+ const res = await fetch(`/api/cpq/product-attributes?specId=${encodeURIComponent(specId)}&pageSize=200`);
155
+ if (res.ok) {
156
+ const data = await res.json();
157
+ setAttributes(data.items ?? []);
158
+ }
159
+ } catch {
160
+ console.error("Failed to load spec attributes");
161
+ }
162
+ };
163
+ const saveOffering = async () => {
164
+ setSaving(true);
165
+ setError(null);
166
+ try {
167
+ const payload = isNew ? { ...form, description: form.description || null, designTimeValues, specId: form.specId } : { id: offeringId, ...form, description: form.description || null, designTimeValues };
168
+ const res = await fetch("/api/cpq/product-offerings", {
169
+ method: isNew ? "POST" : "PUT",
170
+ headers: { "Content-Type": "application/json" },
171
+ body: JSON.stringify(payload)
172
+ });
173
+ if (!res.ok) {
174
+ const d = await res.json();
175
+ setError(d.error ?? "Failed to save");
176
+ return;
177
+ }
178
+ const saved = await res.json();
179
+ if (isNew) {
180
+ router.push(`/backend/cpq/offerings/${saved.id}`);
181
+ } else {
182
+ setForm({
183
+ code: saved.code,
184
+ name: saved.name,
185
+ description: saved.description ?? "",
186
+ offeringType: saved.offeringType ?? form.offeringType,
187
+ lifecycleStatus: saved.lifecycleStatus,
188
+ effectiveFrom: saved.effectiveFrom,
189
+ effectiveTo: saved.effectiveTo,
190
+ isActive: saved.isActive,
191
+ specId: saved.specId
192
+ });
193
+ setDesignTimeValues(saved.designTimeValues ?? {});
194
+ setFormSnapshot(null);
195
+ setDesignTimeSnapshot(null);
196
+ setGeneralMode("view");
197
+ }
198
+ } catch {
199
+ setError("Failed to save");
200
+ } finally {
201
+ setSaving(false);
202
+ }
203
+ };
204
+ const deleteOffering = async () => {
205
+ if (!confirm(t("cpq.offerings.confirmDelete", "Delete this offering and all its charges?"))) return;
206
+ try {
207
+ const res = await fetch("/api/cpq/product-offerings", {
208
+ method: "DELETE",
209
+ headers: { "Content-Type": "application/json" },
210
+ body: JSON.stringify({ id: offeringId })
211
+ });
212
+ if (res.ok) {
213
+ router.push("/backend/cpq/offerings");
214
+ } else {
215
+ const d = await res.json();
216
+ setError(d.error ?? "Failed to delete");
217
+ }
218
+ } catch {
219
+ setError("Failed to delete");
220
+ }
221
+ };
222
+ const loadPricingTables = async () => {
223
+ if (pricingTables.length > 0) return;
224
+ const res = await fetch("/api/cpq/pricing-tables?pageSize=200");
225
+ if (res.ok) {
226
+ const data = await res.json();
227
+ setPricingTables(data.items ?? []);
228
+ }
229
+ };
230
+ const startEditCharge = (charge) => {
231
+ const editing = charge ? { ...charge } : { ...EMPTY_CHARGE };
232
+ setEditingCharge(editing);
233
+ loadPricingTables();
234
+ };
235
+ const saveCharge = async () => {
236
+ if (!editingCharge) return;
237
+ setSaving(true);
238
+ setError(null);
239
+ try {
240
+ const isEdit = !!editingCharge.id;
241
+ const payload = {
242
+ ...editingCharge,
243
+ offeringId
244
+ };
245
+ const res = await fetch("/api/cpq/product-charges", {
246
+ method: isEdit ? "PUT" : "POST",
247
+ headers: { "Content-Type": "application/json" },
248
+ body: JSON.stringify(payload)
249
+ });
250
+ if (!res.ok) {
251
+ const d = await res.json();
252
+ setError(d.error ?? "Failed to save charge");
253
+ return;
254
+ }
255
+ const offeringRes = await fetch(`/api/cpq/product-offerings?id=${encodeURIComponent(offeringId)}`);
256
+ if (offeringRes.ok) {
257
+ const data = await offeringRes.json();
258
+ setCharges(data.charges ?? []);
259
+ }
260
+ setEditingCharge(null);
261
+ } catch {
262
+ setError("Failed to save charge");
263
+ } finally {
264
+ setSaving(false);
265
+ }
266
+ };
267
+ const deleteCharge = async (chargeId) => {
268
+ if (!confirm("Delete this charge?")) return;
269
+ try {
270
+ const res = await fetch("/api/cpq/product-charges", {
271
+ method: "DELETE",
272
+ headers: { "Content-Type": "application/json" },
273
+ body: JSON.stringify({ id: chargeId })
274
+ });
275
+ if (res.ok) setCharges((prev) => prev.filter((c) => c.id !== chargeId));
276
+ } catch {
277
+ setError("Failed to delete charge");
278
+ }
279
+ };
280
+ const selectedPricingTable = editingCharge?.pricingTableId ? pricingTables.find((pt) => pt.id === editingCharge.pricingTableId) : null;
281
+ const designTimeAttrs = attributes.filter((a) => a.resolutionTime === "design_time");
282
+ if (loading) {
283
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("common.loading", "Loading...") });
284
+ }
285
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
286
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
287
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
288
+ /* @__PURE__ */ jsxs(
289
+ "button",
290
+ {
291
+ onClick: () => router.push("/backend/cpq/offerings"),
292
+ className: "text-sm text-muted-foreground hover:text-foreground",
293
+ children: [
294
+ "\u2190 ",
295
+ t("common.back", "Back")
296
+ ]
297
+ }
298
+ ),
299
+ /* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold", children: isNew ? t("cpq.offerings.new", "New Offering") : form.name }),
300
+ !isNew && /* @__PURE__ */ jsx(Tag, { variant: lifecycleStatusMap[form.lifecycleStatus] ?? "neutral", dot: true, children: formatStatusLabel(form.lifecycleStatus) }),
301
+ specDetail && /* @__PURE__ */ jsxs("span", { className: "text-sm text-muted-foreground", children: [
302
+ "Spec: ",
303
+ /* @__PURE__ */ jsx("button", { onClick: () => router.push(`/backend/cpq/specifications/${specDetail.id}`), className: "text-primary hover:underline", children: specDetail.name })
304
+ ] })
305
+ ] }),
306
+ !isNew && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
307
+ generalMode === "view" && tab === "general" && /* @__PURE__ */ jsx(
308
+ Button,
309
+ {
310
+ type: "button",
311
+ onClick: () => {
312
+ setFormSnapshot(form);
313
+ setDesignTimeSnapshot(designTimeValues);
314
+ setGeneralMode("edit");
315
+ },
316
+ children: t("common.edit", "Edit")
317
+ }
318
+ ),
319
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "destructive", onClick: deleteOffering, children: t("common.delete", "Delete") })
320
+ ] })
321
+ ] }),
322
+ error && /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: error }),
323
+ !isNew && /* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b", children: [
324
+ "general",
325
+ "charges",
326
+ ...form.offeringType === "bundle" ? ["components"] : []
327
+ ].map((t_) => /* @__PURE__ */ jsxs(
328
+ "button",
329
+ {
330
+ onClick: () => setTab(t_),
331
+ className: `px-4 py-2 text-sm font-medium border-b-2 transition-colors ${tab === t_ ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"}`,
332
+ children: [
333
+ t_ === "general" && "General",
334
+ t_ === "charges" && `Charges (${charges.length})`,
335
+ t_ === "components" && `Components (${components.length})`
336
+ ]
337
+ },
338
+ t_
339
+ )) }),
340
+ (isNew || tab === "general") && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6 space-y-4", children: [
341
+ isNew && /* @__PURE__ */ jsxs("div", { children: [
342
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Specification" }),
343
+ /* @__PURE__ */ jsxs(
344
+ "select",
345
+ {
346
+ value: form.specId,
347
+ onChange: (e) => {
348
+ setForm({ ...form, specId: e.target.value });
349
+ loadSpecAttributes(e.target.value);
350
+ setDesignTimeValues({});
351
+ },
352
+ className: "w-full rounded-md border px-3 py-2 text-sm",
353
+ children: [
354
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select a specification..." }),
355
+ specs.map((s) => /* @__PURE__ */ jsxs("option", { value: s.id, children: [
356
+ s.name,
357
+ " (",
358
+ s.code,
359
+ ")"
360
+ ] }, s.id))
361
+ ]
362
+ }
363
+ )
364
+ ] }),
365
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
366
+ /* @__PURE__ */ jsxs("div", { children: [
367
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.offerings.code", "Code") }),
368
+ generalMode === "view" && !isNew ? /* @__PURE__ */ jsx("p", { className: "text-sm font-mono py-2", children: form.code || "\u2014" }) : /* @__PURE__ */ jsx(
369
+ "input",
370
+ {
371
+ type: "text",
372
+ value: form.code,
373
+ onChange: (e) => setForm({ ...form, code: e.target.value }),
374
+ placeholder: "e.g. gix-cloud-connect-aws",
375
+ className: "w-full rounded-md border px-3 py-2 text-sm"
376
+ }
377
+ )
378
+ ] }),
379
+ /* @__PURE__ */ jsxs("div", { children: [
380
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.offerings.name", "Name") }),
381
+ generalMode === "view" && !isNew ? /* @__PURE__ */ jsx("p", { className: "text-sm py-2", children: form.name || "\u2014" }) : /* @__PURE__ */ jsx(
382
+ "input",
383
+ {
384
+ type: "text",
385
+ value: form.name,
386
+ onChange: (e) => setForm({ ...form, name: e.target.value }),
387
+ className: "w-full rounded-md border px-3 py-2 text-sm"
388
+ }
389
+ )
390
+ ] }),
391
+ /* @__PURE__ */ jsxs("div", { children: [
392
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.offerings.lifecycleStatus", "Lifecycle Status") }),
393
+ generalMode === "view" && !isNew ? /* @__PURE__ */ jsx("div", { className: "py-2", children: /* @__PURE__ */ jsx(Tag, { variant: lifecycleStatusMap[form.lifecycleStatus] ?? "neutral", dot: true, children: formatStatusLabel(form.lifecycleStatus) }) }) : /* @__PURE__ */ jsxs(
394
+ "select",
395
+ {
396
+ value: form.lifecycleStatus,
397
+ onChange: (e) => setForm({ ...form, lifecycleStatus: e.target.value }),
398
+ className: "w-full rounded-md border px-3 py-2 text-sm",
399
+ children: [
400
+ /* @__PURE__ */ jsx("option", { value: "draft", children: "Draft" }),
401
+ /* @__PURE__ */ jsx("option", { value: "active", children: "Active" }),
402
+ /* @__PURE__ */ jsx("option", { value: "deprecated", children: "Deprecated" })
403
+ ]
404
+ }
405
+ )
406
+ ] }),
407
+ /* @__PURE__ */ jsxs("div", { children: [
408
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.offerings.isActive", "Is Active?") }),
409
+ /* @__PURE__ */ jsx("div", { className: "flex items-center h-[38px]", children: generalMode === "view" && !isNew ? /* @__PURE__ */ jsx(Tag, { variant: form.isActive ? "success" : "neutral", dot: true, children: form.isActive ? t("common.yes", "Yes") : t("common.no", "No") }) : /* @__PURE__ */ jsx(
410
+ "input",
411
+ {
412
+ type: "checkbox",
413
+ checked: form.isActive,
414
+ onChange: (e) => setForm({ ...form, isActive: e.target.checked }),
415
+ className: "rounded border"
416
+ }
417
+ ) })
418
+ ] })
419
+ ] }),
420
+ /* @__PURE__ */ jsxs("div", { children: [
421
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.offerings.description", "Description") }),
422
+ generalMode === "view" && !isNew ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-2 whitespace-pre-wrap", children: form.description || "\u2014" }) : /* @__PURE__ */ jsx(
423
+ "textarea",
424
+ {
425
+ value: form.description ?? "",
426
+ onChange: (e) => setForm({ ...form, description: e.target.value }),
427
+ rows: 3,
428
+ className: "w-full rounded-md border px-3 py-2 text-sm"
429
+ }
430
+ )
431
+ ] }),
432
+ designTimeAttrs.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
433
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium", children: "Design-Time Values" }),
434
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 gap-3", children: designTimeAttrs.map((attr) => {
435
+ const value = designTimeValues[attr.code];
436
+ const isViewing = generalMode === "view" && !isNew;
437
+ const optionLabel = (attr.attributeType === "select" || attr.attributeType === "enum") && attr.options ? attr.options.find((o) => o.value === value)?.label : void 0;
438
+ return /* @__PURE__ */ jsxs("div", { children: [
439
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium mb-1", children: [
440
+ attr.name,
441
+ attr.isRequired && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
442
+ ] }),
443
+ isViewing ? /* @__PURE__ */ jsx("p", { className: "text-sm py-2", children: value == null || value === "" ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" }) : attr.attributeType === "boolean" ? /* @__PURE__ */ jsx(Tag, { variant: value ? "success" : "neutral", dot: true, children: value ? t("common.yes", "Yes") : t("common.no", "No") }) : optionLabel ?? String(value) }) : (attr.attributeType === "select" || attr.attributeType === "enum") && attr.options ? /* @__PURE__ */ jsxs(
444
+ "select",
445
+ {
446
+ value: String(value ?? ""),
447
+ onChange: (e) => setDesignTimeValues({ ...designTimeValues, [attr.code]: e.target.value || null }),
448
+ className: "w-full rounded-md border px-2 py-1.5 text-sm",
449
+ children: [
450
+ /* @__PURE__ */ jsxs("option", { value: "", children: [
451
+ "Select ",
452
+ attr.name,
453
+ "..."
454
+ ] }),
455
+ attr.options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
456
+ ]
457
+ }
458
+ ) : attr.attributeType === "number" ? /* @__PURE__ */ jsx(
459
+ NumberInput,
460
+ {
461
+ value: typeof value === "number" ? value : null,
462
+ onChange: (n) => setDesignTimeValues({ ...designTimeValues, [attr.code]: n })
463
+ }
464
+ ) : attr.attributeType === "boolean" ? /* @__PURE__ */ jsx("div", { className: "pt-1", children: /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-sm", children: [
465
+ /* @__PURE__ */ jsx(
466
+ "input",
467
+ {
468
+ type: "checkbox",
469
+ checked: !!value,
470
+ onChange: (e) => setDesignTimeValues({ ...designTimeValues, [attr.code]: e.target.checked }),
471
+ className: "rounded border"
472
+ }
473
+ ),
474
+ attr.name
475
+ ] }) }) : /* @__PURE__ */ jsx(
476
+ "input",
477
+ {
478
+ type: "text",
479
+ value: String(value ?? ""),
480
+ onChange: (e) => setDesignTimeValues({ ...designTimeValues, [attr.code]: e.target.value || null }),
481
+ className: "w-full rounded-md border px-2 py-1.5 text-sm",
482
+ placeholder: attr.helpText ?? void 0
483
+ }
484
+ ),
485
+ attr.helpText && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: attr.helpText })
486
+ ] }, attr.code);
487
+ }) })
488
+ ] }),
489
+ !(generalMode === "view" && !isNew) && /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
490
+ /* @__PURE__ */ jsx(
491
+ Button,
492
+ {
493
+ type: "button",
494
+ onClick: saveOffering,
495
+ disabled: saving || !form.code || !form.name || isNew && !form.specId,
496
+ children: saving ? t("common.saving", "Saving...") : t("common.save", "Save")
497
+ }
498
+ ),
499
+ /* @__PURE__ */ jsx(
500
+ Button,
501
+ {
502
+ type: "button",
503
+ variant: "outline",
504
+ onClick: () => {
505
+ if (isNew) {
506
+ router.push("/backend/cpq/offerings");
507
+ return;
508
+ }
509
+ if (formSnapshot) setForm(formSnapshot);
510
+ if (designTimeSnapshot) setDesignTimeValues(designTimeSnapshot);
511
+ setFormSnapshot(null);
512
+ setDesignTimeSnapshot(null);
513
+ setGeneralMode("view");
514
+ },
515
+ children: t("common.cancel", "Cancel")
516
+ }
517
+ )
518
+ ] })
519
+ ] }),
520
+ tab === "charges" && !isNew && /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
521
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
522
+ "button",
523
+ {
524
+ onClick: () => startEditCharge(),
525
+ className: "inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90",
526
+ children: t("cpq.offerings.addCharge", "Add Charge")
527
+ }
528
+ ) }),
529
+ editingCharge && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
530
+ /* @__PURE__ */ jsx("h3", { className: "font-medium text-sm", children: editingCharge.id ? "Edit Charge" : "New Charge" }),
531
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3", children: [
532
+ /* @__PURE__ */ jsxs("div", { children: [
533
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Code" }),
534
+ /* @__PURE__ */ jsx("input", { type: "text", value: editingCharge.code, onChange: (e) => setEditingCharge({ ...editingCharge, code: e.target.value }), placeholder: "e.g. mrc-monthly", className: "w-full rounded-md border px-2 py-1.5 text-sm" })
535
+ ] }),
536
+ /* @__PURE__ */ jsxs("div", { children: [
537
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Name" }),
538
+ /* @__PURE__ */ jsx("input", { type: "text", value: editingCharge.name, onChange: (e) => setEditingCharge({ ...editingCharge, name: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
539
+ ] }),
540
+ /* @__PURE__ */ jsxs("div", { children: [
541
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Charge Type" }),
542
+ /* @__PURE__ */ jsxs("select", { value: editingCharge.chargeType, onChange: (e) => setEditingCharge({ ...editingCharge, chargeType: e.target.value }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
543
+ /* @__PURE__ */ jsx("option", { value: "mrc", children: "MRC (Monthly Recurring)" }),
544
+ /* @__PURE__ */ jsx("option", { value: "nrc", children: "NRC (Non-Recurring)" }),
545
+ /* @__PURE__ */ jsx("option", { value: "usage", children: "Usage" })
546
+ ] })
547
+ ] }),
548
+ /* @__PURE__ */ jsxs("div", { children: [
549
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Pricing Method" }),
550
+ /* @__PURE__ */ jsxs("select", { value: editingCharge.pricingMethod, onChange: (e) => {
551
+ const method = e.target.value;
552
+ if (method === "flat") {
553
+ setEditingCharge({ ...editingCharge, pricingMethod: method, pricingTableId: null, priceColumnKey: null, quantityAttributeCode: null });
554
+ } else {
555
+ setEditingCharge({ ...editingCharge, pricingMethod: method, fixedPrice: null });
556
+ }
557
+ }, className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
558
+ /* @__PURE__ */ jsx("option", { value: "flat", children: "Flat Price" }),
559
+ /* @__PURE__ */ jsx("option", { value: "per_unit", children: "Per Unit (table lookup)" }),
560
+ /* @__PURE__ */ jsx("option", { value: "tiered", children: "Tiered (table lookup)" })
561
+ ] })
562
+ ] }),
563
+ editingCharge.pricingMethod === "flat" || editingCharge.pricingMethod === "fixed" ? /* @__PURE__ */ jsxs(Fragment, { children: [
564
+ /* @__PURE__ */ jsxs("div", { children: [
565
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium mb-1", children: [
566
+ "Fixed Price ",
567
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
568
+ ] }),
569
+ /* @__PURE__ */ jsx("input", { type: "text", value: editingCharge.fixedPrice ?? "", onChange: (e) => setEditingCharge({ ...editingCharge, fixedPrice: e.target.value || null }), placeholder: "0.00", className: "w-full rounded-md border px-2 py-1.5 text-sm" })
570
+ ] }),
571
+ /* @__PURE__ */ jsxs("div", { children: [
572
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium mb-1", children: [
573
+ "Currency ",
574
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
575
+ ] }),
576
+ /* @__PURE__ */ jsx("input", { type: "text", value: editingCharge.currencyCode ?? "USD", onChange: (e) => setEditingCharge({ ...editingCharge, currencyCode: e.target.value || null }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
577
+ ] })
578
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
579
+ /* @__PURE__ */ jsxs("div", { children: [
580
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium mb-1", children: [
581
+ "Pricing Table ",
582
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
583
+ ] }),
584
+ /* @__PURE__ */ jsxs("select", { value: editingCharge.pricingTableId ?? "", onChange: (e) => setEditingCharge({ ...editingCharge, pricingTableId: e.target.value || null, priceColumnKey: null }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
585
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select table..." }),
586
+ pricingTables.map((pt) => /* @__PURE__ */ jsxs("option", { value: pt.id, children: [
587
+ pt.name,
588
+ " (",
589
+ pt.code,
590
+ ")"
591
+ ] }, pt.id))
592
+ ] }),
593
+ pricingTables.length === 0 && /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning mt-0.5", children: [
594
+ "No pricing tables yet \u2014",
595
+ " ",
596
+ /* @__PURE__ */ jsx("a", { href: "/backend/cpq/pricing-tables", className: "text-primary hover:underline", children: "create one" }),
597
+ " ",
598
+ "before using ",
599
+ editingCharge.pricingMethod,
600
+ " pricing."
601
+ ] })
602
+ ] }),
603
+ /* @__PURE__ */ jsxs("div", { children: [
604
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium mb-1", children: [
605
+ "Price Column ",
606
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
607
+ ] }),
608
+ /* @__PURE__ */ jsxs("select", { value: editingCharge.priceColumnKey ?? "", onChange: (e) => setEditingCharge({ ...editingCharge, priceColumnKey: e.target.value || null }), className: "w-full rounded-md border px-2 py-1.5 text-sm", disabled: !selectedPricingTable, children: [
609
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select column..." }),
610
+ selectedPricingTable?.priceColumns.map((col) => /* @__PURE__ */ jsx("option", { value: col.key, children: col.label }, col.key))
611
+ ] })
612
+ ] }),
613
+ /* @__PURE__ */ jsxs("div", { children: [
614
+ /* @__PURE__ */ jsxs("label", { className: "block text-xs font-medium mb-1", children: [
615
+ "Quantity Attribute ",
616
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "*" })
617
+ ] }),
618
+ /* @__PURE__ */ jsxs("select", { value: editingCharge.quantityAttributeCode ?? "", onChange: (e) => setEditingCharge({ ...editingCharge, quantityAttributeCode: e.target.value || null }), className: "w-full rounded-md border px-2 py-1.5 text-sm", children: [
619
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select attribute..." }),
620
+ attributes.filter((a) => a.attributeType === "number").map((a) => /* @__PURE__ */ jsxs("option", { value: a.code, children: [
621
+ a.name,
622
+ " (",
623
+ a.code,
624
+ ")"
625
+ ] }, a.code))
626
+ ] }),
627
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5", children: "Attribute whose value drives the quantity for pricing" }),
628
+ attributes.filter((a) => a.attributeType === "number").length === 0 && form.specId && /* @__PURE__ */ jsxs("p", { className: "text-xs text-warning mt-0.5", children: [
629
+ "Spec has no number attributes \u2014",
630
+ " ",
631
+ /* @__PURE__ */ jsx("a", { href: `/backend/cpq/specifications/${form.specId}`, className: "text-primary hover:underline", children: "add one to the spec" }),
632
+ " ",
633
+ "first."
634
+ ] })
635
+ ] })
636
+ ] }),
637
+ /* @__PURE__ */ jsxs("div", { children: [
638
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Sort Order" }),
639
+ /* @__PURE__ */ jsx(
640
+ NumberInput,
641
+ {
642
+ integer: true,
643
+ value: editingCharge.sortOrder,
644
+ onChange: (n) => setEditingCharge({ ...editingCharge, sortOrder: n ?? 0 })
645
+ }
646
+ )
647
+ ] })
648
+ ] }),
649
+ /* @__PURE__ */ jsxs("div", { children: [
650
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Description" }),
651
+ /* @__PURE__ */ jsx("input", { type: "text", value: editingCharge.description ?? "", onChange: (e) => setEditingCharge({ ...editingCharge, description: e.target.value || null }), className: "w-full rounded-md border px-2 py-1.5 text-sm" })
652
+ ] }),
653
+ /* @__PURE__ */ jsx("div", { className: "flex items-end gap-4", children: /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-1.5 text-sm", children: [
654
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: editingCharge.isActive, onChange: (e) => setEditingCharge({ ...editingCharge, isActive: e.target.checked }), className: "rounded border" }),
655
+ "Active"
656
+ ] }) }),
657
+ (() => {
658
+ const missing = chargeMissingFields(editingCharge);
659
+ const blocked = saving || !editingCharge.code || !editingCharge.name || missing.length > 0;
660
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
661
+ missing.length > 0 && /* @__PURE__ */ jsxs(Alert, { variant: "destructive", children: [
662
+ "Missing required field",
663
+ missing.length > 1 ? "s" : "",
664
+ " for ",
665
+ editingCharge.pricingMethod,
666
+ " pricing: ",
667
+ missing.join(", ")
668
+ ] }),
669
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
670
+ /* @__PURE__ */ jsx("button", { onClick: saveCharge, disabled: blocked, className: "inline-flex items-center rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed", children: saving ? "Saving..." : "Save" }),
671
+ /* @__PURE__ */ jsx("button", { onClick: () => setEditingCharge(null), className: "inline-flex items-center rounded-md border px-3 py-1.5 text-sm font-medium hover:bg-muted", children: "Cancel" })
672
+ ] })
673
+ ] });
674
+ })()
675
+ ] }),
676
+ charges.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No charges defined. Add recurring, non-recurring, or usage charges to this offering." }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
677
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
678
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Code" }),
679
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Name" }),
680
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Type" }),
681
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Pricing" }),
682
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Price" }),
683
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Active" }),
684
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: "Actions" })
685
+ ] }) }),
686
+ /* @__PURE__ */ jsx("tbody", { children: charges.map((charge) => {
687
+ const incomplete = !isChargeComplete(charge);
688
+ const tableRef = charge.pricingTableId ? pricingTables.find((pt) => pt.id === charge.pricingTableId) : null;
689
+ const qtyAttr = charge.quantityAttributeCode ? attributes.find((a) => a.code === charge.quantityAttributeCode) : null;
690
+ const isFlat = charge.pricingMethod === "flat" || charge.pricingMethod === "fixed";
691
+ return /* @__PURE__ */ jsxs("tr", { className: "border-b", children: [
692
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 font-mono text-xs", children: charge.code }),
693
+ /* @__PURE__ */ jsxs("td", { className: "px-4 py-3", children: [
694
+ charge.name,
695
+ incomplete && /* @__PURE__ */ jsx(Tag, { variant: "error", className: "ml-2 px-2 text-[10px]", dot: true, children: "Incomplete" })
696
+ ] }),
697
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: /* @__PURE__ */ jsx(Tag, { variant: chargeTypeMap[charge.chargeType] ?? "neutral", className: "px-2 text-xs", children: charge.chargeType.toUpperCase() }) }),
698
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-xs", children: PRICING_METHOD_LABELS[charge.pricingMethod] ?? charge.pricingMethod }),
699
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-xs", children: isFlat ? charge.fixedPrice != null ? `${charge.currencyCode ?? "USD"} ${charge.fixedPrice}` : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\u2014" }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
700
+ /* @__PURE__ */ jsxs("span", { children: [
701
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "Table:" }),
702
+ " ",
703
+ tableRef ? `${tableRef.name} \u2192 ${charge.priceColumnKey ?? "?"}` : /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "missing" })
704
+ ] }),
705
+ /* @__PURE__ */ jsxs("span", { children: [
706
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "\xD7 Qty:" }),
707
+ " ",
708
+ qtyAttr ? `${qtyAttr.name} (${qtyAttr.code})` : /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "missing" })
709
+ ] })
710
+ ] }) }),
711
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-3", children: charge.isActive ? "Yes" : "No" }),
712
+ /* @__PURE__ */ jsxs("td", { className: "px-4 py-3 text-right", children: [
713
+ /* @__PURE__ */ jsx("button", { onClick: () => startEditCharge(charge), className: "text-xs text-primary hover:underline mr-2", children: "Edit" }),
714
+ /* @__PURE__ */ jsx("button", { onClick: () => deleteCharge(charge.id), className: "text-xs text-destructive hover:underline", children: "Delete" })
715
+ ] })
716
+ ] }, charge.id);
717
+ }) })
718
+ ] }) })
719
+ ] }),
720
+ tab === "components" && !isNew && form.offeringType === "bundle" && /* @__PURE__ */ jsx("div", { className: "space-y-4", children: bundleSlots.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No bundle slots defined on this specification. Add slots on the specification first." }) : /* @__PURE__ */ jsx(Fragment, { children: bundleSlots.map((slot) => {
721
+ const slotComponents = components.filter((c) => c.slotId === slot.id);
722
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card overflow-hidden", children: [
723
+ /* @__PURE__ */ jsxs("div", { className: "bg-muted/50 px-4 py-3 border-b flex items-center justify-between", children: [
724
+ /* @__PURE__ */ jsxs("div", { children: [
725
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: slot.name }),
726
+ /* @__PURE__ */ jsx(Tag, { variant: "info", className: "ml-2 px-2 text-[10px]", children: slot.componentGroup }),
727
+ /* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
728
+ slot.cardinalityMin,
729
+ "..",
730
+ slot.cardinalityMax ?? "\u221E"
731
+ ] }),
732
+ slot.targetSpec && /* @__PURE__ */ jsxs("span", { className: "ml-2 text-xs text-muted-foreground", children: [
733
+ "\u2192 ",
734
+ slot.targetSpec.name
735
+ ] })
736
+ ] }),
737
+ /* @__PURE__ */ jsx(
738
+ "button",
739
+ {
740
+ onClick: async () => {
741
+ setAddingComponent({ slotId: slot.id, childOfferingId: "", isDefault: false });
742
+ if (!slotOfferings[slot.targetSpecId]) {
743
+ const res = await fetch(`/api/cpq/product-offerings?specId=${encodeURIComponent(slot.targetSpecId)}&pageSize=100`);
744
+ if (res.ok) {
745
+ const d = await res.json();
746
+ setSlotOfferings((prev) => ({ ...prev, [slot.targetSpecId]: d.items ?? [] }));
747
+ }
748
+ }
749
+ },
750
+ className: "inline-flex items-center justify-center rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90",
751
+ children: "+ Add Component"
752
+ }
753
+ )
754
+ ] }),
755
+ addingComponent?.slotId === slot.id && /* @__PURE__ */ jsxs("div", { className: "p-4 border-b bg-muted/20 space-y-3", children: [
756
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3", children: [
757
+ /* @__PURE__ */ jsxs("div", { className: "col-span-2", children: [
758
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Component Offering" }),
759
+ /* @__PURE__ */ jsxs(
760
+ "select",
761
+ {
762
+ value: addingComponent.childOfferingId,
763
+ onChange: (e) => setAddingComponent({ ...addingComponent, childOfferingId: e.target.value }),
764
+ className: "w-full rounded-md border px-3 py-2 text-sm",
765
+ children: [
766
+ /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Select offering \u2014" }),
767
+ (slotOfferings[slot.targetSpecId] ?? []).map((o) => /* @__PURE__ */ jsxs("option", { value: o.id, children: [
768
+ o.name,
769
+ " (",
770
+ o.code,
771
+ ")",
772
+ o.offeringType === "bundle" ? " [bundle]" : ""
773
+ ] }, o.id))
774
+ ]
775
+ }
776
+ )
777
+ ] }),
778
+ /* @__PURE__ */ jsxs("div", { children: [
779
+ /* @__PURE__ */ jsx("label", { className: "block text-xs font-medium mb-1", children: "Default?" }),
780
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center h-[38px]", children: [
781
+ /* @__PURE__ */ jsx(
782
+ "input",
783
+ {
784
+ type: "checkbox",
785
+ checked: addingComponent.isDefault,
786
+ onChange: (e) => setAddingComponent({ ...addingComponent, isDefault: e.target.checked }),
787
+ className: "rounded border"
788
+ }
789
+ ),
790
+ /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: "Pre-selected in quote" })
791
+ ] })
792
+ ] })
793
+ ] }),
794
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
795
+ /* @__PURE__ */ jsx(
796
+ "button",
797
+ {
798
+ disabled: saving || !addingComponent.childOfferingId,
799
+ onClick: async () => {
800
+ setSaving(true);
801
+ setError(null);
802
+ try {
803
+ const res = await fetch("/api/cpq/offering-components", {
804
+ method: "POST",
805
+ headers: { "Content-Type": "application/json" },
806
+ body: JSON.stringify({
807
+ offeringId,
808
+ slotId: addingComponent.slotId,
809
+ childOfferingId: addingComponent.childOfferingId,
810
+ isDefault: addingComponent.isDefault
811
+ })
812
+ });
813
+ if (!res.ok) {
814
+ const d = await res.json();
815
+ setError(d.error ?? "Failed to add component");
816
+ return;
817
+ }
818
+ setAddingComponent(null);
819
+ const compRes = await fetch(`/api/cpq/offering-components?offeringId=${offeringId}`);
820
+ if (compRes.ok) {
821
+ const d = await compRes.json();
822
+ setComponents(d.items ?? []);
823
+ }
824
+ } catch {
825
+ setError("Failed to add component");
826
+ } finally {
827
+ setSaving(false);
828
+ }
829
+ },
830
+ className: "inline-flex items-center justify-center rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50",
831
+ children: saving ? "Adding..." : "Add"
832
+ }
833
+ ),
834
+ /* @__PURE__ */ jsx(
835
+ "button",
836
+ {
837
+ onClick: () => setAddingComponent(null),
838
+ className: "inline-flex items-center justify-center rounded-md border px-3 py-1.5 text-xs font-medium hover:bg-muted",
839
+ children: "Cancel"
840
+ }
841
+ )
842
+ ] })
843
+ ] }),
844
+ slotComponents.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "p-4 text-center text-xs text-muted-foreground", children: [
845
+ "No components assigned to this slot",
846
+ slot.cardinalityMin > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-status-warning-text font-medium", children: [
847
+ "(required: min ",
848
+ slot.cardinalityMin,
849
+ ")"
850
+ ] })
851
+ ] }) : /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
852
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/30", children: [
853
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left text-xs font-medium", children: "Offering" }),
854
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left text-xs font-medium", children: "Code" }),
855
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left text-xs font-medium", children: "Type" }),
856
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-left text-xs font-medium", children: "Default" }),
857
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-2 text-right text-xs font-medium", children: "Actions" })
858
+ ] }) }),
859
+ /* @__PURE__ */ jsx("tbody", { children: slotComponents.map((comp) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0", children: [
860
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5 font-medium", children: comp.childOffering?.name ?? "Unknown" }),
861
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5 text-muted-foreground text-xs", children: comp.childOffering?.code ?? "\u2014" }),
862
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5", children: comp.childOffering?.offeringType === "bundle" ? /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-2 text-[10px]", children: "bundle" }) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "px-2 text-[10px]", children: "simple" }) }),
863
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5", children: /* @__PURE__ */ jsx(
864
+ "button",
865
+ {
866
+ onClick: async () => {
867
+ try {
868
+ const res = await fetch("/api/cpq/offering-components", {
869
+ method: "PUT",
870
+ headers: { "Content-Type": "application/json" },
871
+ body: JSON.stringify({ id: comp.id, isDefault: !comp.isDefault })
872
+ });
873
+ if (res.ok) {
874
+ setComponents((prev) => prev.map((c) => c.id === comp.id ? { ...c, isDefault: !c.isDefault } : c));
875
+ }
876
+ } catch {
877
+ }
878
+ },
879
+ className: "cursor-pointer",
880
+ children: /* @__PURE__ */ jsx(Tag, { variant: comp.isDefault ? "success" : "neutral", className: "px-2 text-[10px]", children: comp.isDefault ? "Yes" : "No" })
881
+ }
882
+ ) }),
883
+ /* @__PURE__ */ jsx("td", { className: "px-4 py-2.5 text-right", children: /* @__PURE__ */ jsx(
884
+ "button",
885
+ {
886
+ onClick: async () => {
887
+ if (!confirm("Remove this component from the bundle?")) return;
888
+ try {
889
+ const res = await fetch("/api/cpq/offering-components", {
890
+ method: "DELETE",
891
+ headers: { "Content-Type": "application/json" },
892
+ body: JSON.stringify({ id: comp.id })
893
+ });
894
+ if (res.ok) {
895
+ setComponents((prev) => prev.filter((c) => c.id !== comp.id));
896
+ } else {
897
+ const d = await res.json();
898
+ setError(d.error ?? "Failed to remove");
899
+ }
900
+ } catch {
901
+ setError("Failed to remove component");
902
+ }
903
+ },
904
+ className: "text-xs text-destructive hover:underline",
905
+ children: "Remove"
906
+ }
907
+ ) })
908
+ ] }, comp.id)) })
909
+ ] })
910
+ ] }, slot.id);
911
+ }) }) })
912
+ ] });
913
+ }
914
+ export {
915
+ OfferingDetailPage as default
916
+ };
917
+ //# sourceMappingURL=page.js.map