@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,253 @@
1
+ "use client"
2
+ import * as React from 'react'
3
+ import { useRouter } from 'next/navigation'
4
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
5
+ import type { ColumnDef } from '@tanstack/react-table'
6
+ import { Button } from '@open-mercato/ui/primitives/button'
7
+ import { Tag } from '@open-mercato/ui/primitives/tag'
8
+ import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
9
+ import { CpqListView, useCpqListData } from '../../../components/CpqListView'
10
+ import {
11
+ PriceRuleForm,
12
+ RULE_TYPE_LABELS,
13
+ emptyPriceRuleForm,
14
+ priceRuleFormToPayload,
15
+ type PriceRule,
16
+ type PriceRuleFormData,
17
+ type ProductOffering,
18
+ } from './PriceRuleForm'
19
+
20
+ const PAGE_SIZE = 50
21
+
22
+ function buildFilterParams(values: FilterValues, params: URLSearchParams) {
23
+ if (typeof values.ruleType === 'string' && values.ruleType) {
24
+ params.set('ruleType', values.ruleType)
25
+ }
26
+ if (typeof values.chargeTypeFilter === 'string' && values.chargeTypeFilter) {
27
+ params.set('chargeTypeFilter', values.chargeTypeFilter)
28
+ }
29
+ if (values.globalOnly === true) params.set('globalOnly', 'true')
30
+ if (values.isActive === true) params.set('isActive', 'true')
31
+ if (values.isActive === false) params.set('isActive', 'false')
32
+ }
33
+
34
+ function formatValue(rule: PriceRule) {
35
+ if (rule.ruleType.includes('percent')) return `${rule.value}%`
36
+ if (rule.ruleType === 'price_override') return `= ${rule.value}`
37
+ return `${rule.value}`
38
+ }
39
+
40
+ export default function PriceRulesPage() {
41
+ const t = useT()
42
+ const router = useRouter()
43
+
44
+ const data = useCpqListData<PriceRule>({
45
+ endpoint: '/api/cpq/price-rules',
46
+ pageSize: PAGE_SIZE,
47
+ initialSorting: [{ id: 'sortOrder', desc: false }],
48
+ buildFilterParams,
49
+ loadErrorMessage: t('cpq.priceRules.list.error.load', 'Failed to load price rules'),
50
+ })
51
+
52
+ const [showForm, setShowForm] = React.useState(false)
53
+ const [form, setForm] = React.useState<PriceRuleFormData>(emptyPriceRuleForm)
54
+ const [saving, setSaving] = React.useState(false)
55
+ const [error, setError] = React.useState<string | null>(null)
56
+ const [offerings, setOfferings] = React.useState<ProductOffering[]>([])
57
+
58
+ React.useEffect(() => {
59
+ fetch('/api/cpq/product-offerings?pageSize=100')
60
+ .then((r) => r.json())
61
+ .then((d) => setOfferings(d.items ?? []))
62
+ .catch(() => {})
63
+ }, [])
64
+
65
+ const ruleTypeOptions = React.useMemo(
66
+ () => Object.entries(RULE_TYPE_LABELS).map(([value, label]) => ({ value, label })),
67
+ [],
68
+ )
69
+
70
+ const chargeTypeOptions = React.useMemo(
71
+ () => [
72
+ { value: 'nrc', label: 'NRC' },
73
+ { value: 'mrc', label: 'MRC' },
74
+ { value: 'usage', label: 'Usage' },
75
+ ],
76
+ [],
77
+ )
78
+
79
+ const filters = React.useMemo<FilterDef[]>(
80
+ () => [
81
+ {
82
+ id: 'ruleType',
83
+ label: t('cpq.priceRules.filters.ruleType', 'Rule Type'),
84
+ type: 'select',
85
+ options: ruleTypeOptions,
86
+ },
87
+ {
88
+ id: 'chargeTypeFilter',
89
+ label: t('cpq.priceRules.filters.chargeType', 'Charge Type'),
90
+ type: 'select',
91
+ options: chargeTypeOptions,
92
+ },
93
+ {
94
+ id: 'globalOnly',
95
+ label: t('cpq.priceRules.filters.globalOnly', 'Global only'),
96
+ type: 'checkbox',
97
+ },
98
+ {
99
+ id: 'isActive',
100
+ label: t('cpq.priceRules.filters.isActive', 'Active'),
101
+ type: 'checkbox',
102
+ },
103
+ ],
104
+ [chargeTypeOptions, ruleTypeOptions, t],
105
+ )
106
+
107
+ const openCreate = React.useCallback(() => {
108
+ setForm(emptyPriceRuleForm)
109
+ setError(null)
110
+ setShowForm(true)
111
+ }, [])
112
+
113
+ const handleSave = React.useCallback(async () => {
114
+ setSaving(true)
115
+ setError(null)
116
+ try {
117
+ const payload = priceRuleFormToPayload(form)
118
+ const res = await fetch('/api/cpq/price-rules', {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json' },
121
+ body: JSON.stringify(payload),
122
+ })
123
+ if (!res.ok) {
124
+ const err = await res.json()
125
+ throw new Error(err.details ? JSON.stringify(err.details) : err.error ?? 'Failed to create')
126
+ }
127
+ setShowForm(false)
128
+ data.reload()
129
+ } catch (err: unknown) {
130
+ setError(err instanceof Error ? err.message : 'Unknown error')
131
+ } finally {
132
+ setSaving(false)
133
+ }
134
+ }, [data, form])
135
+
136
+ const columns = React.useMemo<ColumnDef<PriceRule>[]>(
137
+ () => [
138
+ {
139
+ accessorKey: 'sortOrder',
140
+ header: t('cpq.priceRules.table.order', 'Order'),
141
+ cell: ({ row }) => <span className="text-muted-foreground">{row.original.sortOrder}</span>,
142
+ },
143
+ {
144
+ accessorKey: 'name',
145
+ header: t('cpq.priceRules.table.name', 'Name'),
146
+ cell: ({ row }) => <span className="font-medium">{row.original.name}</span>,
147
+ },
148
+ {
149
+ accessorKey: 'code',
150
+ header: t('cpq.priceRules.table.code', 'Code'),
151
+ cell: ({ row }) => <span className="font-mono text-xs">{row.original.code}</span>,
152
+ },
153
+ {
154
+ accessorKey: 'ruleType',
155
+ header: t('cpq.priceRules.table.type', 'Type'),
156
+ cell: ({ row }) => (
157
+ <span>{RULE_TYPE_LABELS[row.original.ruleType] ?? row.original.ruleType}</span>
158
+ ),
159
+ },
160
+ {
161
+ id: 'value',
162
+ header: t('cpq.priceRules.table.value', 'Value'),
163
+ cell: ({ row }) => <span className="font-mono">{formatValue(row.original)}</span>,
164
+ },
165
+ {
166
+ id: 'scope',
167
+ header: t('cpq.priceRules.table.scope', 'Scope'),
168
+ cell: ({ row }) => (
169
+ <span className="text-xs">
170
+ {row.original.productOfferingId
171
+ ? row.original.productOfferingName ?? 'Product-scoped'
172
+ : 'Global'}
173
+ </span>
174
+ ),
175
+ },
176
+ {
177
+ id: 'filter',
178
+ header: t('cpq.priceRules.table.filter', 'Filter'),
179
+ cell: ({ row }) => {
180
+ const rule = row.original
181
+ const parts: string[] = []
182
+ if (rule.chargeTypeFilter) parts.push(rule.chargeTypeFilter)
183
+ if (rule.chargeCodeFilter) parts.push(rule.chargeCodeFilter)
184
+ const cond = rule.applicabilityCondition as
185
+ | { attribute?: string; operator?: string; value?: string }
186
+ | null
187
+ if (cond?.attribute) {
188
+ const op = cond.operator === 'neq' ? '≠' : '='
189
+ parts.push(`${cond.attribute} ${op} ${cond.value ?? '""'}`)
190
+ }
191
+ return (
192
+ <span className="text-xs text-muted-foreground">
193
+ {parts.length > 0 ? parts.join(' / ') : '—'}
194
+ </span>
195
+ )
196
+ },
197
+ meta: { truncate: true, maxWidth: 240 },
198
+ },
199
+ {
200
+ accessorKey: 'isActive',
201
+ header: t('cpq.priceRules.table.status', 'Status'),
202
+ cell: ({ row }) => (
203
+ <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>
204
+ {row.original.isActive ? 'Active' : 'Inactive'}
205
+ </Tag>
206
+ ),
207
+ },
208
+ ],
209
+ [t],
210
+ )
211
+
212
+ return (
213
+ <CpqListView<PriceRule>
214
+ title={t('cpq.priceRules.list.title', 'Price Rules')}
215
+ tableId="cpq.price-rules.list"
216
+ data={data}
217
+ columns={columns}
218
+ filters={filters}
219
+ pageSize={PAGE_SIZE}
220
+ searchPlaceholder={t('cpq.priceRules.search.placeholder', 'Search price rules...')}
221
+ actions={
222
+ <Button type="button" onClick={openCreate}>
223
+ {t('cpq.priceRules.create', 'Create Rule')}
224
+ </Button>
225
+ }
226
+ crud={{
227
+ endpoint: '/api/cpq/price-rules',
228
+ entityName: t('cpq.priceRules.entityName', 'price rule'),
229
+ editHref: (row) => `/backend/cpq/price-rules/${row.id}`,
230
+ }}
231
+ onRowClick={(row) => router.push(`/backend/cpq/price-rules/${row.id}`)}
232
+ toolbarContent={
233
+ showForm ? (
234
+ <PriceRuleForm
235
+ editingId={null}
236
+ form={form}
237
+ onFormChange={setForm}
238
+ offerings={offerings}
239
+ saving={saving}
240
+ error={error}
241
+ onSave={handleSave}
242
+ onCancel={() => setShowForm(false)}
243
+ />
244
+ ) : null
245
+ }
246
+ emptyState={
247
+ <div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
248
+ No price rules found. Click &quot;Create Rule&quot; to add one.
249
+ </div>
250
+ }
251
+ />
252
+ )
253
+ }
@@ -0,0 +1,431 @@
1
+ "use client"
2
+ import * as React from 'react'
3
+ import { Alert } from '@open-mercato/ui/primitives/alert'
4
+ import { Button } from '@open-mercato/ui/primitives/button'
5
+ import { Input } from '@open-mercato/ui/primitives/input'
6
+ import {
7
+ Select,
8
+ SelectContent,
9
+ SelectItem,
10
+ SelectTrigger,
11
+ SelectValue,
12
+ } from '@open-mercato/ui/primitives/select'
13
+ import { Trash2 } from 'lucide-react'
14
+
15
+ // ─── Types ──────────────────────────────────────────────────────
16
+
17
+ export type Dimension = { key: string; label: string }
18
+ export type PriceColumn = { key: string; label: string }
19
+
20
+ export type PricingTableShape = {
21
+ id: string
22
+ dimensions: Dimension[]
23
+ priceColumns: PriceColumn[]
24
+ currencyCodeList: string[]
25
+ }
26
+
27
+ export type Entry = {
28
+ id: string
29
+ dimensionValues: Record<string, string>
30
+ tierNumber: number | null
31
+ rangeFrom: string | null
32
+ rangeTo: string | null
33
+ currencyCode: string
34
+ prices: Record<string, number>
35
+ }
36
+
37
+ type DraftEntry = Entry & {
38
+ // null = unchanged, 'new' = added locally, 'modified' = field edited,
39
+ // 'deleted' = pending delete (hidden from main grid but kept for save diff).
40
+ _state: null | 'new' | 'modified' | 'deleted'
41
+ _localId: string
42
+ }
43
+
44
+ type SaveResult = { ok: true } | { ok: false; error: string }
45
+
46
+ // ─── Component ──────────────────────────────────────────────────
47
+
48
+ export function EntriesEditor({
49
+ table,
50
+ initialEntries,
51
+ onSaved,
52
+ }: {
53
+ table: PricingTableShape
54
+ initialEntries: Entry[]
55
+ onSaved: (entries: Entry[]) => void
56
+ }) {
57
+ const [drafts, setDrafts] = React.useState<DraftEntry[]>(() =>
58
+ initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })),
59
+ )
60
+ const [mode, setMode] = React.useState<'view' | 'edit'>('view')
61
+ const [saving, setSaving] = React.useState(false)
62
+ const [error, setError] = React.useState<string | null>(null)
63
+
64
+ // Sync external changes (e.g. parent reloads after config save).
65
+ React.useEffect(() => {
66
+ setDrafts(initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })))
67
+ }, [initialEntries])
68
+
69
+ const hasTieredEntries = drafts.some((d) => d.tierNumber != null && d._state !== 'deleted')
70
+ const visibleDrafts = drafts.filter((d) => d._state !== 'deleted')
71
+ const dirty = drafts.some((d) => d._state !== null)
72
+
73
+ // ─── Mutation helpers ─────────────────────────────────────────
74
+
75
+ const newLocalId = React.useCallback(() => `new-${Math.random().toString(36).slice(2, 10)}`, [])
76
+
77
+ const addRow = (asTiered: boolean) => {
78
+ const seed: DraftEntry = {
79
+ id: '',
80
+ _localId: newLocalId(),
81
+ _state: 'new',
82
+ dimensionValues: Object.fromEntries(table.dimensions.map((d) => [d.key, ''])),
83
+ tierNumber: asTiered ? (drafts.filter((d) => d.tierNumber != null).length + 1) : null,
84
+ rangeFrom: asTiered ? '0' : null,
85
+ rangeTo: asTiered ? null : null,
86
+ currencyCode: table.currencyCodeList[0] ?? 'USD',
87
+ prices: Object.fromEntries(table.priceColumns.map((c) => [c.key, 0])),
88
+ }
89
+ setDrafts((prev) => [...prev, seed])
90
+ }
91
+
92
+ const updateRow = (localId: string, patch: Partial<DraftEntry>) => {
93
+ setDrafts((prev) =>
94
+ prev.map((d) =>
95
+ d._localId === localId
96
+ ? { ...d, ...patch, _state: d._state === 'new' ? 'new' : 'modified' }
97
+ : d,
98
+ ),
99
+ )
100
+ }
101
+
102
+ const deleteRow = (localId: string) => {
103
+ setDrafts((prev) =>
104
+ prev.flatMap((d) => {
105
+ if (d._localId !== localId) return [d]
106
+ // New (not yet persisted) → drop entirely. Persisted → mark for delete.
107
+ if (d._state === 'new') return []
108
+ return [{ ...d, _state: 'deleted' }]
109
+ }),
110
+ )
111
+ }
112
+
113
+ const cancelEdit = () => {
114
+ // Revert to initial — drop any local mutations.
115
+ setDrafts(initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })))
116
+ setError(null)
117
+ setMode('view')
118
+ }
119
+
120
+ // ─── Save (diff against initialEntries) ───────────────────────
121
+
122
+ const save = async (): Promise<SaveResult> => {
123
+ setSaving(true)
124
+ setError(null)
125
+ try {
126
+ // POST new entries (batch — route accepts arrays)
127
+ const news = drafts.filter((d) => d._state === 'new')
128
+ if (news.length > 0) {
129
+ const payload = news.map((d) => ({
130
+ pricingTableId: table.id,
131
+ dimensionValues: d.dimensionValues,
132
+ tierNumber: d.tierNumber ?? undefined,
133
+ rangeFrom: d.rangeFrom != null ? Number(d.rangeFrom) : undefined,
134
+ rangeTo: d.rangeTo != null ? Number(d.rangeTo) : undefined,
135
+ currencyCode: d.currencyCode,
136
+ prices: d.prices,
137
+ }))
138
+ const res = await fetch('/api/cpq/pricing-table-entries', {
139
+ method: 'POST',
140
+ headers: { 'Content-Type': 'application/json' },
141
+ body: JSON.stringify(payload),
142
+ })
143
+ if (!res.ok) {
144
+ const body = await res.json().catch(() => ({}))
145
+ return { ok: false, error: body.error ?? 'Failed to create entries' }
146
+ }
147
+ }
148
+
149
+ // PUT modified entries (one-by-one, route doesn't batch updates)
150
+ const mods = drafts.filter((d) => d._state === 'modified')
151
+ for (const d of mods) {
152
+ const res = await fetch('/api/cpq/pricing-table-entries', {
153
+ method: 'PUT',
154
+ headers: { 'Content-Type': 'application/json' },
155
+ body: JSON.stringify({
156
+ id: d.id,
157
+ dimensionValues: d.dimensionValues,
158
+ tierNumber: d.tierNumber ?? null,
159
+ rangeFrom: d.rangeFrom != null ? Number(d.rangeFrom) : null,
160
+ rangeTo: d.rangeTo != null ? Number(d.rangeTo) : null,
161
+ currencyCode: d.currencyCode,
162
+ prices: d.prices,
163
+ }),
164
+ })
165
+ if (!res.ok) {
166
+ const body = await res.json().catch(() => ({}))
167
+ return { ok: false, error: body.error ?? `Failed to update entry ${d.id}` }
168
+ }
169
+ }
170
+
171
+ // DELETE removed entries
172
+ const dels = drafts.filter((d) => d._state === 'deleted')
173
+ for (const d of dels) {
174
+ const res = await fetch('/api/cpq/pricing-table-entries', {
175
+ method: 'DELETE',
176
+ headers: { 'Content-Type': 'application/json' },
177
+ body: JSON.stringify({ id: d.id }),
178
+ })
179
+ if (!res.ok) {
180
+ const body = await res.json().catch(() => ({}))
181
+ return { ok: false, error: body.error ?? `Failed to delete entry ${d.id}` }
182
+ }
183
+ }
184
+
185
+ return { ok: true }
186
+ } catch (err) {
187
+ return { ok: false, error: err instanceof Error ? err.message : 'Failed to save entries' }
188
+ } finally {
189
+ setSaving(false)
190
+ }
191
+ }
192
+
193
+ const handleSave = async () => {
194
+ const r = await save()
195
+ if (!r.ok) {
196
+ setError(r.error)
197
+ return
198
+ }
199
+ // Reload from server via parent (which re-fetches the pricing table
200
+ // and pushes a fresh `initialEntries` down — useEffect resets drafts).
201
+ onSaved([])
202
+ setMode('view')
203
+ }
204
+
205
+ // ─── Render ─────────────────────────────────────────────────
206
+
207
+ return (
208
+ <div className="space-y-3">
209
+ <div className="flex items-center justify-between">
210
+ <h2 className="text-lg font-semibold">
211
+ Entries ({visibleDrafts.length})
212
+ </h2>
213
+ <div className="flex items-center gap-2">
214
+ {mode === 'view' ? (
215
+ <Button type="button" variant="outline" size="sm" onClick={() => setMode('edit')}>
216
+ Edit Entries
217
+ </Button>
218
+ ) : (
219
+ <>
220
+ <Button type="button" variant="outline" size="sm" onClick={() => addRow(false)}>
221
+ + Add Row
222
+ </Button>
223
+ <Button type="button" variant="outline" size="sm" onClick={() => addRow(true)}>
224
+ + Add Tier
225
+ </Button>
226
+ <Button type="button" size="sm" onClick={handleSave} disabled={saving || !dirty}>
227
+ {saving ? 'Saving...' : 'Save'}
228
+ </Button>
229
+ <Button type="button" variant="outline" size="sm" onClick={cancelEdit} disabled={saving}>
230
+ Cancel
231
+ </Button>
232
+ </>
233
+ )}
234
+ </div>
235
+ </div>
236
+
237
+ {error && <Alert variant="destructive">{error}</Alert>}
238
+
239
+ {visibleDrafts.length === 0 ? (
240
+ <div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
241
+ {mode === 'edit'
242
+ ? 'No entries yet — use "+ Add Row" (dimensions lookup) or "+ Add Tier" (range lookup).'
243
+ : 'No entries in this pricing table.'}
244
+ </div>
245
+ ) : (
246
+ <div className="rounded-lg border overflow-x-auto">
247
+ <table className="w-full text-sm">
248
+ <thead>
249
+ <tr className="border-b bg-muted/50">
250
+ {hasTieredEntries && (
251
+ <>
252
+ <th className="px-3 py-3 text-left font-medium whitespace-nowrap">Tier #</th>
253
+ <th className="px-3 py-3 text-left font-medium whitespace-nowrap">Range From</th>
254
+ <th className="px-3 py-3 text-left font-medium whitespace-nowrap">Range To</th>
255
+ </>
256
+ )}
257
+ {table.dimensions.map((dim) => (
258
+ <th key={dim.key} className="px-3 py-3 text-left font-medium whitespace-nowrap">
259
+ {dim.label}
260
+ </th>
261
+ ))}
262
+ {table.priceColumns.map((col) => (
263
+ <th key={col.key} className="px-3 py-3 text-right font-medium whitespace-nowrap">
264
+ {col.label}
265
+ </th>
266
+ ))}
267
+ <th className="px-3 py-3 text-left font-medium">Currency</th>
268
+ {mode === 'edit' && <th className="px-3 py-3 w-8" aria-label="Actions" />}
269
+ </tr>
270
+ </thead>
271
+ <tbody>
272
+ {visibleDrafts.map((entry) => (
273
+ <EntryRow
274
+ key={entry._localId}
275
+ entry={entry}
276
+ table={table}
277
+ hasTieredEntries={hasTieredEntries}
278
+ mode={mode}
279
+ onChange={(patch) => updateRow(entry._localId, patch)}
280
+ onDelete={() => deleteRow(entry._localId)}
281
+ />
282
+ ))}
283
+ </tbody>
284
+ </table>
285
+ </div>
286
+ )}
287
+ </div>
288
+ )
289
+ }
290
+
291
+ // ─── Row sub-component ──────────────────────────────────────────
292
+
293
+ function EntryRow({
294
+ entry,
295
+ table,
296
+ hasTieredEntries,
297
+ mode,
298
+ onChange,
299
+ onDelete,
300
+ }: {
301
+ entry: DraftEntry
302
+ table: PricingTableShape
303
+ hasTieredEntries: boolean
304
+ mode: 'view' | 'edit'
305
+ onChange: (patch: Partial<DraftEntry>) => void
306
+ onDelete: () => void
307
+ }) {
308
+ const readOnly = mode === 'view'
309
+ const rowBg = entry._state === 'new'
310
+ ? 'bg-success/5'
311
+ : entry._state === 'modified'
312
+ ? 'bg-warning/5'
313
+ : ''
314
+
315
+ return (
316
+ <tr className={`border-b hover:bg-muted/30 ${rowBg}`}>
317
+ {hasTieredEntries && (
318
+ <>
319
+ <td className="px-3 py-2">
320
+ {readOnly ? (
321
+ entry.tierNumber ?? '—'
322
+ ) : (
323
+ <Input
324
+ type="number"
325
+ value={entry.tierNumber ?? ''}
326
+ onChange={(e) => onChange({ tierNumber: e.target.value === '' ? null : Number(e.target.value) })}
327
+ className="w-20 h-8"
328
+ />
329
+ )}
330
+ </td>
331
+ <td className="px-3 py-2">
332
+ {readOnly ? (
333
+ entry.rangeFrom ?? '—'
334
+ ) : (
335
+ <Input
336
+ type="number"
337
+ value={entry.rangeFrom ?? ''}
338
+ onChange={(e) => onChange({ rangeFrom: e.target.value === '' ? null : e.target.value })}
339
+ className="w-24 h-8"
340
+ />
341
+ )}
342
+ </td>
343
+ <td className="px-3 py-2">
344
+ {readOnly ? (
345
+ entry.rangeTo ?? '—'
346
+ ) : (
347
+ <Input
348
+ type="number"
349
+ value={entry.rangeTo ?? ''}
350
+ onChange={(e) => onChange({ rangeTo: e.target.value === '' ? null : e.target.value })}
351
+ placeholder="∞"
352
+ className="w-24 h-8"
353
+ />
354
+ )}
355
+ </td>
356
+ </>
357
+ )}
358
+ {table.dimensions.map((dim) => (
359
+ <td key={dim.key} className="px-3 py-2">
360
+ {readOnly ? (
361
+ entry.dimensionValues?.[dim.key] ?? '—'
362
+ ) : (
363
+ <Input
364
+ value={entry.dimensionValues?.[dim.key] ?? ''}
365
+ onChange={(e) =>
366
+ onChange({
367
+ dimensionValues: { ...entry.dimensionValues, [dim.key]: e.target.value },
368
+ })
369
+ }
370
+ className="w-28 h-8"
371
+ />
372
+ )}
373
+ </td>
374
+ ))}
375
+ {table.priceColumns.map((col) => (
376
+ <td key={col.key} className="px-3 py-2 text-right font-mono">
377
+ {readOnly ? (
378
+ entry.prices?.[col.key] != null ? Number(entry.prices[col.key]).toFixed(2) : '—'
379
+ ) : (
380
+ <Input
381
+ type="number"
382
+ step="0.01"
383
+ value={entry.prices?.[col.key] ?? ''}
384
+ onChange={(e) =>
385
+ onChange({
386
+ prices: {
387
+ ...entry.prices,
388
+ [col.key]: e.target.value === '' ? 0 : Number(e.target.value),
389
+ },
390
+ })
391
+ }
392
+ className="w-24 h-8 text-right"
393
+ />
394
+ )}
395
+ </td>
396
+ ))}
397
+ <td className="px-3 py-2">
398
+ {readOnly ? (
399
+ entry.currencyCode
400
+ ) : (
401
+ <Select
402
+ value={entry.currencyCode}
403
+ onValueChange={(value) => onChange({ currencyCode: value })}
404
+ >
405
+ <SelectTrigger className="w-24 h-8">
406
+ <SelectValue />
407
+ </SelectTrigger>
408
+ <SelectContent>
409
+ {table.currencyCodeList.map((c) => (
410
+ <SelectItem key={c} value={c}>{c}</SelectItem>
411
+ ))}
412
+ </SelectContent>
413
+ </Select>
414
+ )}
415
+ </td>
416
+ {mode === 'edit' && (
417
+ <td className="px-3 py-2">
418
+ <button
419
+ type="button"
420
+ onClick={onDelete}
421
+ className="text-destructive hover:bg-destructive/10 rounded p-1"
422
+ aria-label="Delete row"
423
+ >
424
+ <Trash2 className="h-4 w-4" />
425
+ </button>
426
+ </td>
427
+ )}
428
+ </tr>
429
+ )
430
+ }
431
+