@dainamite/billing 0.20.0 → 0.21.0

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 (598) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/modules/billing/acl.js +13 -1
  3. package/dist/modules/billing/acl.js.map +2 -2
  4. package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
  5. package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
  6. package/dist/modules/billing/api/accounts/route.js +46 -4
  7. package/dist/modules/billing/api/accounts/route.js.map +2 -2
  8. package/dist/modules/billing/api/export/account/[id]/route.js +11 -7
  9. package/dist/modules/billing/api/export/account/[id]/route.js.map +2 -2
  10. package/dist/modules/billing/api/invoices/[id]/memos/route.js +54 -0
  11. package/dist/modules/billing/api/invoices/[id]/memos/route.js.map +7 -0
  12. package/dist/modules/billing/api/invoices/[id]/pdf/route.js +69 -0
  13. package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
  14. package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
  15. package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
  16. package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
  17. package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
  18. package/dist/modules/billing/api/invoices/add-line/route.js +2 -2
  19. package/dist/modules/billing/api/invoices/add-line/route.js.map +2 -2
  20. package/dist/modules/billing/api/invoices/delete-draft/route.js +33 -0
  21. package/dist/modules/billing/api/invoices/delete-draft/route.js.map +7 -0
  22. package/dist/modules/billing/api/invoices/edit-line/route.js +2 -2
  23. package/dist/modules/billing/api/invoices/edit-line/route.js.map +2 -2
  24. package/dist/modules/billing/api/invoices/post/route.js +2 -2
  25. package/dist/modules/billing/api/invoices/post/route.js.map +2 -2
  26. package/dist/modules/billing/api/invoices/remove-line/route.js +2 -2
  27. package/dist/modules/billing/api/invoices/remove-line/route.js.map +2 -2
  28. package/dist/modules/billing/api/invoices/route.js +45 -46
  29. package/dist/modules/billing/api/invoices/route.js.map +2 -2
  30. package/dist/modules/billing/api/items/bulk/route.js +1 -1
  31. package/dist/modules/billing/api/items/bulk/route.js.map +2 -2
  32. package/dist/modules/billing/api/items/change-price/route.js +44 -0
  33. package/dist/modules/billing/api/items/change-price/route.js.map +7 -0
  34. package/dist/modules/billing/api/items/route.js +19 -7
  35. package/dist/modules/billing/api/items/route.js.map +2 -2
  36. package/dist/modules/billing/api/payment-imports/[id]/route.js +49 -0
  37. package/dist/modules/billing/api/payment-imports/[id]/route.js.map +7 -0
  38. package/dist/modules/billing/api/payment-imports/route.js +57 -0
  39. package/dist/modules/billing/api/payment-imports/route.js.map +7 -0
  40. package/dist/modules/billing/api/payments/[id]/route.js +60 -0
  41. package/dist/modules/billing/api/payments/[id]/route.js.map +7 -0
  42. package/dist/modules/billing/api/payments/allocate/route.js +35 -0
  43. package/dist/modules/billing/api/payments/allocate/route.js.map +7 -0
  44. package/dist/modules/billing/api/payments/assign/route.js +36 -0
  45. package/dist/modules/billing/api/payments/assign/route.js.map +7 -0
  46. package/dist/modules/billing/api/payments/convert-to-topup/route.js +35 -0
  47. package/dist/modules/billing/api/payments/convert-to-topup/route.js.map +7 -0
  48. package/dist/modules/billing/api/payments/ignore/route.js +35 -0
  49. package/dist/modules/billing/api/payments/ignore/route.js.map +7 -0
  50. package/dist/modules/billing/api/payments/import/route.js +63 -0
  51. package/dist/modules/billing/api/payments/import/route.js.map +7 -0
  52. package/dist/modules/billing/api/payments/restore/route.js +35 -0
  53. package/dist/modules/billing/api/payments/restore/route.js.map +7 -0
  54. package/dist/modules/billing/api/payments/route.js +114 -0
  55. package/dist/modules/billing/api/payments/route.js.map +7 -0
  56. package/dist/modules/billing/api/payments/unallocate/route.js +35 -0
  57. package/dist/modules/billing/api/payments/unallocate/route.js.map +7 -0
  58. package/dist/modules/billing/api/payments/unassign/route.js +35 -0
  59. package/dist/modules/billing/api/payments/unassign/route.js.map +7 -0
  60. package/dist/modules/billing/api/run-outcomes/route.js +43 -6
  61. package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
  62. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js +61 -0
  63. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js.map +7 -0
  64. package/dist/modules/billing/api/runs/[id]/route.js +53 -0
  65. package/dist/modules/billing/api/runs/[id]/route.js.map +7 -0
  66. package/dist/modules/billing/api/runs/retry-failed/route.js +1 -1
  67. package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
  68. package/dist/modules/billing/api/runs/route.js +9 -3
  69. package/dist/modules/billing/api/runs/route.js.map +2 -2
  70. package/dist/modules/billing/api/settings/number-preview/route.js +42 -0
  71. package/dist/modules/billing/api/settings/number-preview/route.js.map +7 -0
  72. package/dist/modules/billing/api/settings/route.js +92 -0
  73. package/dist/modules/billing/api/settings/route.js.map +7 -0
  74. package/dist/modules/billing/api/statements/route.js +3 -0
  75. package/dist/modules/billing/api/statements/route.js.map +2 -2
  76. package/dist/modules/billing/api/test-invoices/wipe/route.js +2 -2
  77. package/dist/modules/billing/api/test-invoices/wipe/route.js.map +2 -2
  78. package/dist/modules/billing/backend/billing/accounts/[id]/page.js +121 -232
  79. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +2 -2
  80. package/dist/modules/billing/backend/billing/accounts/create/page.js +6 -0
  81. package/dist/modules/billing/backend/billing/accounts/create/page.js.map +2 -2
  82. package/dist/modules/billing/backend/billing/accounts/page.js +63 -4
  83. package/dist/modules/billing/backend/billing/accounts/page.js.map +2 -2
  84. package/dist/modules/billing/backend/billing/invoices/[id]/page.js +297 -65
  85. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +3 -3
  86. package/dist/modules/billing/backend/billing/invoices/page.js +6 -172
  87. package/dist/modules/billing/backend/billing/invoices/page.js.map +2 -2
  88. package/dist/modules/billing/backend/billing/items/[id]/page.js +51 -7
  89. package/dist/modules/billing/backend/billing/items/[id]/page.js.map +2 -2
  90. package/dist/modules/billing/backend/billing/items/create/page.js +9 -6
  91. package/dist/modules/billing/backend/billing/items/create/page.js.map +2 -2
  92. package/dist/modules/billing/backend/billing/items/page.js +8 -2
  93. package/dist/modules/billing/backend/billing/items/page.js.map +2 -2
  94. package/dist/modules/billing/backend/billing/payments/[id]/page.js +297 -0
  95. package/dist/modules/billing/backend/billing/payments/[id]/page.js.map +7 -0
  96. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js +21 -0
  97. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js.map +7 -0
  98. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js +74 -0
  99. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js.map +7 -0
  100. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js +21 -0
  101. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js.map +7 -0
  102. package/dist/modules/billing/backend/billing/payments/page.js +58 -0
  103. package/dist/modules/billing/backend/billing/payments/page.js.map +7 -0
  104. package/dist/modules/billing/backend/billing/payments/page.meta.js +18 -0
  105. package/dist/modules/billing/backend/billing/payments/page.meta.js.map +7 -0
  106. package/dist/modules/billing/backend/billing/runs/[id]/page.js +49 -14
  107. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +2 -2
  108. package/dist/modules/billing/backend/billing/runs/page.js +58 -7
  109. package/dist/modules/billing/backend/billing/runs/page.js.map +2 -2
  110. package/dist/modules/billing/backend/billing/statements/[id]/page.js +61 -6
  111. package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +2 -2
  112. package/dist/modules/billing/backend/billing/statements/page.js +1 -1
  113. package/dist/modules/billing/backend/billing/statements/page.js.map +2 -2
  114. package/dist/modules/billing/backend/billing/topups/page.js +1 -1
  115. package/dist/modules/billing/backend/billing/topups/page.js.map +2 -2
  116. package/dist/modules/billing/backend/billing/transactions/page.js +1 -1
  117. package/dist/modules/billing/backend/billing/transactions/page.js.map +2 -2
  118. package/dist/modules/billing/backend/billing/usage/create/page.js +3 -2
  119. package/dist/modules/billing/backend/billing/usage/create/page.js.map +2 -2
  120. package/dist/modules/billing/backend/config/billing/page.js +472 -0
  121. package/dist/modules/billing/backend/config/billing/page.js.map +7 -0
  122. package/dist/modules/billing/backend/config/billing/page.meta.js +19 -0
  123. package/dist/modules/billing/backend/config/billing/page.meta.js.map +7 -0
  124. package/dist/modules/billing/cli.js +22 -1
  125. package/dist/modules/billing/cli.js.map +2 -2
  126. package/dist/modules/billing/commands/accounts.js +6 -0
  127. package/dist/modules/billing/commands/accounts.js.map +2 -2
  128. package/dist/modules/billing/commands/index.js +3 -0
  129. package/dist/modules/billing/commands/index.js.map +2 -2
  130. package/dist/modules/billing/commands/invoices.js +269 -167
  131. package/dist/modules/billing/commands/invoices.js.map +2 -2
  132. package/dist/modules/billing/commands/items.js +108 -0
  133. package/dist/modules/billing/commands/items.js.map +2 -2
  134. package/dist/modules/billing/commands/memos.js +179 -0
  135. package/dist/modules/billing/commands/memos.js.map +7 -0
  136. package/dist/modules/billing/commands/payments.js +379 -0
  137. package/dist/modules/billing/commands/payments.js.map +7 -0
  138. package/dist/modules/billing/commands/runs.js +151 -4
  139. package/dist/modules/billing/commands/runs.js.map +2 -2
  140. package/dist/modules/billing/commands/settings.js +59 -0
  141. package/dist/modules/billing/commands/settings.js.map +7 -0
  142. package/dist/modules/billing/commands/topups.js +9 -2
  143. package/dist/modules/billing/commands/topups.js.map +2 -2
  144. package/dist/modules/billing/commands/transactions.js +2 -2
  145. package/dist/modules/billing/commands/transactions.js.map +2 -2
  146. package/dist/modules/billing/components/AccountForm.js +149 -42
  147. package/dist/modules/billing/components/AccountForm.js.map +2 -2
  148. package/dist/modules/billing/components/AccountInvoicesSection.js +64 -0
  149. package/dist/modules/billing/components/AccountInvoicesSection.js.map +7 -0
  150. package/dist/modules/billing/components/AccountPaymentsSection.js +43 -0
  151. package/dist/modules/billing/components/AccountPaymentsSection.js.map +7 -0
  152. package/dist/modules/billing/components/AccountPicker.js +56 -0
  153. package/dist/modules/billing/components/AccountPicker.js.map +7 -0
  154. package/dist/modules/billing/components/AllocatePaymentDialog.js +134 -0
  155. package/dist/modules/billing/components/AllocatePaymentDialog.js.map +7 -0
  156. package/dist/modules/billing/components/BillingDataTable.js +21 -0
  157. package/dist/modules/billing/components/BillingDataTable.js.map +7 -0
  158. package/dist/modules/billing/components/BillingSettingsLink.js +19 -0
  159. package/dist/modules/billing/components/BillingSettingsLink.js.map +7 -0
  160. package/dist/modules/billing/components/ChangeItemPriceDialog.js +158 -0
  161. package/dist/modules/billing/components/ChangeItemPriceDialog.js.map +7 -0
  162. package/dist/modules/billing/components/CreateMemoDialog.js +200 -0
  163. package/dist/modules/billing/components/CreateMemoDialog.js.map +7 -0
  164. package/dist/modules/billing/components/CustomerLabel.js +33 -0
  165. package/dist/modules/billing/components/CustomerLabel.js.map +7 -0
  166. package/dist/modules/billing/components/InvoicesTable.js +270 -0
  167. package/dist/modules/billing/components/InvoicesTable.js.map +7 -0
  168. package/dist/modules/billing/components/ItemForm.js +176 -44
  169. package/dist/modules/billing/components/ItemForm.js.map +2 -2
  170. package/dist/modules/billing/components/ItemPriceField.js +46 -0
  171. package/dist/modules/billing/components/ItemPriceField.js.map +7 -0
  172. package/dist/modules/billing/components/LineFormDialog.js +25 -2
  173. package/dist/modules/billing/components/LineFormDialog.js.map +2 -2
  174. package/dist/modules/billing/components/ManualPaymentDialog.js +122 -0
  175. package/dist/modules/billing/components/ManualPaymentDialog.js.map +7 -0
  176. package/dist/modules/billing/components/MoneyInput.js +14 -0
  177. package/dist/modules/billing/components/MoneyInput.js.map +7 -0
  178. package/dist/modules/billing/components/PaymentUploadDialog.js +115 -0
  179. package/dist/modules/billing/components/PaymentUploadDialog.js.map +7 -0
  180. package/dist/modules/billing/components/PaymentsTable.js +171 -0
  181. package/dist/modules/billing/components/PaymentsTable.js.map +7 -0
  182. package/dist/modules/billing/components/PostRunDraftsButton.js +105 -0
  183. package/dist/modules/billing/components/PostRunDraftsButton.js.map +7 -0
  184. package/dist/modules/billing/components/PrepaidPanel.js +14 -4
  185. package/dist/modules/billing/components/PrepaidPanel.js.map +2 -2
  186. package/dist/modules/billing/components/RunCyclesSection.js +209 -0
  187. package/dist/modules/billing/components/RunCyclesSection.js.map +7 -0
  188. package/dist/modules/billing/components/TestRunActions.js +127 -0
  189. package/dist/modules/billing/components/TestRunActions.js.map +7 -0
  190. package/dist/modules/billing/components/account/AccountItemsTab.js +157 -0
  191. package/dist/modules/billing/components/account/AccountItemsTab.js.map +7 -0
  192. package/dist/modules/billing/components/account/AccountRunBillingDialog.js +122 -0
  193. package/dist/modules/billing/components/account/AccountRunBillingDialog.js.map +7 -0
  194. package/dist/modules/billing/components/account/AccountRunsTab.js +152 -0
  195. package/dist/modules/billing/components/account/AccountRunsTab.js.map +7 -0
  196. package/dist/modules/billing/components/account/AccountStatementsTab.js +98 -0
  197. package/dist/modules/billing/components/account/AccountStatementsTab.js.map +7 -0
  198. package/dist/modules/billing/components/account/AccountSummaryCards.js +125 -0
  199. package/dist/modules/billing/components/account/AccountSummaryCards.js.map +7 -0
  200. package/dist/modules/billing/components/account/AccountUsageTab.js +91 -0
  201. package/dist/modules/billing/components/account/AccountUsageTab.js.map +7 -0
  202. package/dist/modules/billing/components/account/AccountView.js +73 -0
  203. package/dist/modules/billing/components/account/AccountView.js.map +7 -0
  204. package/dist/modules/billing/components/account/accountRow.js +39 -0
  205. package/dist/modules/billing/components/account/accountRow.js.map +7 -0
  206. package/dist/modules/billing/components/customerNames.js +42 -0
  207. package/dist/modules/billing/components/customerNames.js.map +7 -0
  208. package/dist/modules/billing/data/entities.js +560 -4
  209. package/dist/modules/billing/data/entities.js.map +2 -2
  210. package/dist/modules/billing/data/entityIds.js +10 -1
  211. package/dist/modules/billing/data/entityIds.js.map +2 -2
  212. package/dist/modules/billing/data/validators.js +269 -15
  213. package/dist/modules/billing/data/validators.js.map +2 -2
  214. package/dist/modules/billing/events.js +50 -0
  215. package/dist/modules/billing/events.js.map +2 -2
  216. package/dist/modules/billing/i18n/en.json +457 -21
  217. package/dist/modules/billing/i18n/pl.json +457 -21
  218. package/dist/modules/billing/index.js +6 -2
  219. package/dist/modules/billing/index.js.map +2 -2
  220. package/dist/modules/billing/lib/accountFinancials.js +58 -0
  221. package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
  222. package/dist/modules/billing/lib/accountLock.js +14 -0
  223. package/dist/modules/billing/lib/accountLock.js.map +7 -0
  224. package/dist/modules/billing/lib/accountStatus.js +9 -7
  225. package/dist/modules/billing/lib/accountStatus.js.map +2 -2
  226. package/dist/modules/billing/lib/accountSummary.js +106 -0
  227. package/dist/modules/billing/lib/accountSummary.js.map +7 -0
  228. package/dist/modules/billing/lib/allocation.js +356 -0
  229. package/dist/modules/billing/lib/allocation.js.map +7 -0
  230. package/dist/modules/billing/lib/allocationEvents.js +22 -0
  231. package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
  232. package/dist/modules/billing/lib/billPeriod.js +15 -1
  233. package/dist/modules/billing/lib/billPeriod.js.map +2 -2
  234. package/dist/modules/billing/lib/billRunEngine.js +237 -102
  235. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  236. package/dist/modules/billing/lib/billingSettings.js +116 -0
  237. package/dist/modules/billing/lib/billingSettings.js.map +7 -0
  238. package/dist/modules/billing/lib/commandRoute.js +81 -0
  239. package/dist/modules/billing/lib/commandRoute.js.map +7 -0
  240. package/dist/modules/billing/lib/customerLookup.js +97 -0
  241. package/dist/modules/billing/lib/customerLookup.js.map +7 -0
  242. package/dist/modules/billing/lib/dateOnly.js +13 -0
  243. package/dist/modules/billing/lib/dateOnly.js.map +7 -0
  244. package/dist/modules/billing/lib/documentParties.js +16 -0
  245. package/dist/modules/billing/lib/documentParties.js.map +7 -0
  246. package/dist/modules/billing/lib/documentTypes.js +27 -0
  247. package/dist/modules/billing/lib/documentTypes.js.map +7 -0
  248. package/dist/modules/billing/lib/dueDate.js +18 -0
  249. package/dist/modules/billing/lib/dueDate.js.map +7 -0
  250. package/dist/modules/billing/lib/iban.js +29 -0
  251. package/dist/modules/billing/lib/iban.js.map +7 -0
  252. package/dist/modules/billing/lib/invoiceDelivery.js +131 -0
  253. package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
  254. package/dist/modules/billing/lib/invoiceLanguages.js +16 -0
  255. package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
  256. package/dist/modules/billing/lib/invoiceWriter.js +107 -99
  257. package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
  258. package/dist/modules/billing/lib/itemBillingState.js +27 -0
  259. package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
  260. package/dist/modules/billing/lib/itemPriceChange.js +18 -0
  261. package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
  262. package/dist/modules/billing/lib/itemSelector.js.map +2 -2
  263. package/dist/modules/billing/lib/lineAmounts.js +32 -0
  264. package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
  265. package/dist/modules/billing/lib/memoGuards.js +25 -0
  266. package/dist/modules/billing/lib/memoGuards.js.map +7 -0
  267. package/dist/modules/billing/lib/navigation.js +48 -0
  268. package/dist/modules/billing/lib/navigation.js.map +7 -0
  269. package/dist/modules/billing/lib/numbering.js +120 -0
  270. package/dist/modules/billing/lib/numbering.js.map +7 -0
  271. package/dist/modules/billing/lib/overdue.js +36 -0
  272. package/dist/modules/billing/lib/overdue.js.map +7 -0
  273. package/dist/modules/billing/lib/paymentCsv.js +125 -0
  274. package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
  275. package/dist/modules/billing/lib/paymentGateway.js +15 -0
  276. package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
  277. package/dist/modules/billing/lib/paymentImport.js +164 -0
  278. package/dist/modules/billing/lib/paymentImport.js.map +7 -0
  279. package/dist/modules/billing/lib/paymentMatcher.js +36 -0
  280. package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
  281. package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
  282. package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
  283. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
  284. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
  285. package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
  286. package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
  287. package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
  288. package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
  289. package/dist/modules/billing/lib/pdf/invoiceTemplate.js +274 -0
  290. package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
  291. package/dist/modules/billing/lib/prepaidConfig.js +17 -25
  292. package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
  293. package/dist/modules/billing/lib/prepaidConsume.js +26 -7
  294. package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
  295. package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
  296. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
  297. package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
  298. package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
  299. package/dist/modules/billing/lib/seeds/demo.js +51 -3
  300. package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
  301. package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
  302. package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
  303. package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
  304. package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
  305. package/dist/modules/billing/lib/servicePeriods.js +121 -0
  306. package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
  307. package/dist/modules/billing/lib/testRunCleanup.js +24 -0
  308. package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
  309. package/dist/modules/billing/lib/topupCapture.js +2 -2
  310. package/dist/modules/billing/lib/topupCapture.js.map +2 -2
  311. package/dist/modules/billing/lib/topupReceipt.js +58 -79
  312. package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
  313. package/dist/modules/billing/lib/usageRater.js.map +1 -1
  314. package/dist/modules/billing/lib/usageRunner.js +14 -6
  315. package/dist/modules/billing/lib/usageRunner.js.map +2 -2
  316. package/dist/modules/billing/lib/vat.js +39 -0
  317. package/dist/modules/billing/lib/vat.js.map +7 -0
  318. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
  319. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
  320. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
  321. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
  322. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
  323. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
  324. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
  325. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
  326. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
  327. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
  328. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
  329. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
  330. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
  331. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
  332. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
  333. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
  334. package/dist/modules/billing/setup.js +7 -184
  335. package/dist/modules/billing/setup.js.map +2 -2
  336. package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
  337. package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
  338. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
  339. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
  340. package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
  341. package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
  342. package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
  343. package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
  344. package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
  345. package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
  346. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
  347. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
  348. package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
  349. package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
  350. package/generated/entities/billing_account/index.ts +3 -0
  351. package/generated/entities/billing_allocation/index.ts +18 -0
  352. package/generated/entities/billing_document_sequence/index.ts +11 -0
  353. package/generated/entities/billing_invoice/index.ts +38 -0
  354. package/generated/entities/billing_invoice_line/index.ts +24 -0
  355. package/generated/entities/billing_item/index.ts +4 -0
  356. package/generated/entities/billing_payment/index.ts +25 -0
  357. package/generated/entities/billing_payment_import/index.ts +14 -0
  358. package/generated/entities/billing_run/index.ts +0 -1
  359. package/generated/entities/billing_run_outcome/index.ts +1 -0
  360. package/generated/entities/billing_settings/index.ts +26 -0
  361. package/generated/entities/billing_statement/index.ts +1 -0
  362. package/generated/entities.ids.generated.ts +7 -0
  363. package/package.json +5 -1
  364. package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +170 -0
  365. package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
  366. package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
  367. package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
  368. package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
  369. package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
  370. package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
  371. package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
  372. package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
  373. package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
  374. package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
  375. package/src/modules/billing/__tests__/commands/invoices.test.ts +436 -354
  376. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
  377. package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
  378. package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
  379. package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
  380. package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
  381. package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
  382. package/src/modules/billing/__tests__/entityRegistry.test.ts +8 -1
  383. package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
  384. package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
  385. package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
  386. package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
  387. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +228 -17
  388. package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
  389. package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
  390. package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
  391. package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
  392. package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +189 -0
  393. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
  394. package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
  395. package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
  396. package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
  397. package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
  398. package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
  399. package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
  400. package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
  401. package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
  402. package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
  403. package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +199 -0
  404. package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
  405. package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
  406. package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
  407. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
  408. package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
  409. package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
  410. package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
  411. package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
  412. package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
  413. package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
  414. package/src/modules/billing/__tests__/setup.test.ts +13 -4
  415. package/src/modules/billing/__tests__/validators.test.ts +193 -3
  416. package/src/modules/billing/acl.ts +12 -0
  417. package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
  418. package/src/modules/billing/api/accounts/route.ts +58 -4
  419. package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
  420. package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
  421. package/src/modules/billing/api/invoices/[id]/pdf/route.ts +79 -0
  422. package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
  423. package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
  424. package/src/modules/billing/api/invoices/add-line/route.ts +2 -2
  425. package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
  426. package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
  427. package/src/modules/billing/api/invoices/post/route.ts +7 -6
  428. package/src/modules/billing/api/invoices/remove-line/route.ts +2 -2
  429. package/src/modules/billing/api/invoices/route.ts +81 -86
  430. package/src/modules/billing/api/items/bulk/route.ts +2 -1
  431. package/src/modules/billing/api/items/change-price/route.ts +54 -0
  432. package/src/modules/billing/api/items/route.ts +23 -7
  433. package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
  434. package/src/modules/billing/api/payment-imports/route.ts +57 -0
  435. package/src/modules/billing/api/payments/[id]/route.ts +63 -0
  436. package/src/modules/billing/api/payments/allocate/route.ts +35 -0
  437. package/src/modules/billing/api/payments/assign/route.ts +36 -0
  438. package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
  439. package/src/modules/billing/api/payments/ignore/route.ts +35 -0
  440. package/src/modules/billing/api/payments/import/route.ts +72 -0
  441. package/src/modules/billing/api/payments/restore/route.ts +35 -0
  442. package/src/modules/billing/api/payments/route.ts +147 -0
  443. package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
  444. package/src/modules/billing/api/payments/unassign/route.ts +35 -0
  445. package/src/modules/billing/api/run-outcomes/route.ts +49 -6
  446. package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
  447. package/src/modules/billing/api/runs/[id]/route.ts +59 -0
  448. package/src/modules/billing/api/runs/retry-failed/route.ts +2 -1
  449. package/src/modules/billing/api/runs/route.ts +15 -7
  450. package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
  451. package/src/modules/billing/api/settings/route.ts +116 -0
  452. package/src/modules/billing/api/statements/route.ts +5 -0
  453. package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
  454. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
  455. package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
  456. package/src/modules/billing/backend/billing/accounts/page.tsx +70 -4
  457. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +444 -95
  458. package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
  459. package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
  460. package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
  461. package/src/modules/billing/backend/billing/items/page.tsx +18 -2
  462. package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
  463. package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
  464. package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
  465. package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
  466. package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
  467. package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
  468. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +49 -18
  469. package/src/modules/billing/backend/billing/runs/page.tsx +59 -8
  470. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +86 -5
  471. package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
  472. package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
  473. package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
  474. package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
  475. package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
  476. package/src/modules/billing/backend/config/billing/page.tsx +549 -0
  477. package/src/modules/billing/cli.ts +31 -1
  478. package/src/modules/billing/commands/accounts.ts +6 -0
  479. package/src/modules/billing/commands/index.ts +3 -0
  480. package/src/modules/billing/commands/invoices.ts +364 -231
  481. package/src/modules/billing/commands/items.ts +143 -0
  482. package/src/modules/billing/commands/memos.ts +218 -0
  483. package/src/modules/billing/commands/payments.ts +428 -0
  484. package/src/modules/billing/commands/runs.ts +205 -3
  485. package/src/modules/billing/commands/settings.ts +77 -0
  486. package/src/modules/billing/commands/topups.ts +12 -3
  487. package/src/modules/billing/commands/transactions.ts +2 -2
  488. package/src/modules/billing/components/AccountForm.tsx +199 -53
  489. package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
  490. package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
  491. package/src/modules/billing/components/AccountPicker.tsx +70 -0
  492. package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
  493. package/src/modules/billing/components/BillingDataTable.tsx +34 -0
  494. package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
  495. package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
  496. package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
  497. package/src/modules/billing/components/CustomerLabel.tsx +53 -0
  498. package/src/modules/billing/components/InvoicesTable.tsx +344 -0
  499. package/src/modules/billing/components/ItemForm.tsx +233 -44
  500. package/src/modules/billing/components/ItemPriceField.tsx +81 -0
  501. package/src/modules/billing/components/LineFormDialog.tsx +25 -1
  502. package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
  503. package/src/modules/billing/components/MoneyInput.tsx +26 -0
  504. package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
  505. package/src/modules/billing/components/PaymentsTable.tsx +213 -0
  506. package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
  507. package/src/modules/billing/components/PrepaidPanel.tsx +19 -6
  508. package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
  509. package/src/modules/billing/components/TestRunActions.tsx +152 -0
  510. package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
  511. package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
  512. package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
  513. package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
  514. package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
  515. package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
  516. package/src/modules/billing/components/account/AccountView.tsx +101 -0
  517. package/src/modules/billing/components/account/accountRow.ts +70 -0
  518. package/src/modules/billing/components/customerNames.ts +62 -0
  519. package/src/modules/billing/data/entities.ts +654 -5
  520. package/src/modules/billing/data/entityIds.ts +9 -0
  521. package/src/modules/billing/data/validators.ts +388 -8
  522. package/src/modules/billing/events.ts +56 -3
  523. package/src/modules/billing/i18n/en.json +457 -21
  524. package/src/modules/billing/i18n/pl.json +457 -21
  525. package/src/modules/billing/index.ts +6 -2
  526. package/src/modules/billing/lib/accountFinancials.ts +84 -0
  527. package/src/modules/billing/lib/accountLock.ts +32 -0
  528. package/src/modules/billing/lib/accountStatus.ts +11 -9
  529. package/src/modules/billing/lib/accountSummary.ts +172 -0
  530. package/src/modules/billing/lib/allocation.ts +515 -0
  531. package/src/modules/billing/lib/allocationEvents.ts +26 -0
  532. package/src/modules/billing/lib/billPeriod.ts +19 -0
  533. package/src/modules/billing/lib/billRunEngine.ts +350 -150
  534. package/src/modules/billing/lib/billingSettings.ts +145 -0
  535. package/src/modules/billing/lib/commandRoute.ts +112 -0
  536. package/src/modules/billing/lib/customerLookup.ts +157 -0
  537. package/src/modules/billing/lib/dateOnly.ts +22 -0
  538. package/src/modules/billing/lib/documentParties.ts +23 -0
  539. package/src/modules/billing/lib/documentTypes.ts +37 -0
  540. package/src/modules/billing/lib/dueDate.ts +26 -0
  541. package/src/modules/billing/lib/iban.ts +36 -0
  542. package/src/modules/billing/lib/invoiceDelivery.ts +197 -0
  543. package/src/modules/billing/lib/invoiceLanguages.ts +34 -0
  544. package/src/modules/billing/lib/invoiceWriter.ts +162 -148
  545. package/src/modules/billing/lib/itemBillingState.ts +67 -0
  546. package/src/modules/billing/lib/itemPriceChange.ts +56 -0
  547. package/src/modules/billing/lib/itemSelector.ts +4 -1
  548. package/src/modules/billing/lib/lineAmounts.ts +61 -0
  549. package/src/modules/billing/lib/memoGuards.ts +41 -0
  550. package/src/modules/billing/lib/navigation.ts +64 -0
  551. package/src/modules/billing/lib/numbering.ts +174 -0
  552. package/src/modules/billing/lib/overdue.ts +60 -0
  553. package/src/modules/billing/lib/paymentCsv.ts +171 -0
  554. package/src/modules/billing/lib/paymentGateway.ts +52 -0
  555. package/src/modules/billing/lib/paymentImport.ts +228 -0
  556. package/src/modules/billing/lib/paymentMatcher.ts +88 -0
  557. package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
  558. package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
  559. package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
  560. package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
  561. package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
  562. package/src/modules/billing/lib/pdf/invoiceModel.ts +328 -0
  563. package/src/modules/billing/lib/pdf/invoicePdf.ts +216 -0
  564. package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
  565. package/src/modules/billing/lib/prepaidConfig.ts +27 -36
  566. package/src/modules/billing/lib/prepaidConsume.ts +29 -6
  567. package/src/modules/billing/lib/prepaidPeriodClose.ts +407 -239
  568. package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
  569. package/src/modules/billing/lib/seeds/demo.ts +79 -6
  570. package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
  571. package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
  572. package/src/modules/billing/lib/servicePeriods.ts +226 -0
  573. package/src/modules/billing/lib/testRunCleanup.ts +42 -0
  574. package/src/modules/billing/lib/topupCapture.ts +3 -4
  575. package/src/modules/billing/lib/topupReceipt.ts +70 -103
  576. package/src/modules/billing/lib/usageRater.ts +1 -1
  577. package/src/modules/billing/lib/usageRunner.ts +17 -5
  578. package/src/modules/billing/lib/vat.ts +56 -0
  579. package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
  580. package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
  581. package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
  582. package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
  583. package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
  584. package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
  585. package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
  586. package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
  587. package/src/modules/billing/setup.ts +11 -237
  588. package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
  589. package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
  590. package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
  591. package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
  592. package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
  593. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
  594. package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
  595. package/dist/modules/billing/lib/invoiceStatus.js +0 -38
  596. package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
  597. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
  598. package/src/modules/billing/lib/invoiceStatus.ts +0 -61
@@ -0,0 +1,428 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import type { CommandHandler, CommandRuntimeContext } from '@open-mercato/shared/lib/commands/types'
3
+ import { registerCommand } from '@open-mercato/shared/lib/commands'
4
+ import { ensureOrganizationScope, ensureTenantScope } from '@open-mercato/shared/lib/commands/scope'
5
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
6
+ import { BillingAccount, BillingPayment, BillingTopup } from '../data/entities'
7
+ import {
8
+ billingAllocationRemoveSchema,
9
+ billingPaymentAllocateSchema,
10
+ billingPaymentAssignSchema,
11
+ billingPaymentConvertSchema,
12
+ billingPaymentCreateSchema,
13
+ billingPaymentIdSchema,
14
+ billingPaymentImportSchema,
15
+ type BillingAllocationRemoveInput,
16
+ type BillingPaymentAllocateInput,
17
+ type BillingPaymentAssignInput,
18
+ type BillingPaymentConvertInput,
19
+ type BillingPaymentCreateInput,
20
+ type BillingPaymentIdInput,
21
+ type BillingPaymentImportInput,
22
+ } from '../data/validators'
23
+ import { emitBillingEvent } from '../events'
24
+ import { withAccountLock } from '../lib/accountLock'
25
+ import {
26
+ allocate,
27
+ applyAccountCredit,
28
+ emptyEffects,
29
+ mergeEffects,
30
+ toCents,
31
+ unallocate,
32
+ type AllocationEffects,
33
+ type ArScope,
34
+ } from '../lib/allocation'
35
+ import { emitAllocationEffects } from '../lib/allocationEvents'
36
+ import { getBillingSettings, receiptTaxRate } from '../lib/billingSettings'
37
+ import { importPayments, type ImportSummary } from '../lib/paymentImport'
38
+ import { captureTopup } from '../lib/topupCapture'
39
+ import { createHash } from 'node:crypto'
40
+
41
+ /**
42
+ * Payment commands (Spec C). Every receivables change runs inside the
43
+ * per-account lock (`withAccountLock`); events fire after commit.
44
+ *
45
+ * Reversals are explicit inverse commands, consistent with the rest of the
46
+ * billing module (which does not use the framework undo log): allocate ↔
47
+ * unallocate, assign ↔ unassign, ignore ↔ restore. Convert-to-top-up is
48
+ * terminal (reverse through the prepaid adjustment flow, C-D3).
49
+ */
50
+
51
+ function getEm(ctx: CommandRuntimeContext): EntityManager {
52
+ return (ctx.container.resolve('em') as EntityManager).fork()
53
+ }
54
+
55
+ function userIdOf(ctx: CommandRuntimeContext): string | null {
56
+ return (ctx.auth?.sub as string | undefined) ?? null
57
+ }
58
+
59
+ function scopeOf(input: { tenantId: string; organizationId: string }): ArScope {
60
+ return { tenantId: input.tenantId, organizationId: input.organizationId }
61
+ }
62
+
63
+ async function loadPayment(em: EntityManager, scope: ArScope, paymentId: string): Promise<BillingPayment> {
64
+ const payment = await em.findOne(BillingPayment, { id: paymentId, ...scope, deletedAt: null } as never)
65
+ if (!payment) throw new CrudHttpError(404, { error: 'Payment not found', code: 'billing.payment.not_found' })
66
+ return payment
67
+ }
68
+
69
+ async function loadAccount(em: EntityManager, scope: ArScope, billAccountId: string): Promise<BillingAccount> {
70
+ const account = await em.findOne(BillingAccount, { id: billAccountId, ...scope, deletedAt: null } as never)
71
+ if (!account) throw new CrudHttpError(404, { error: 'Billing account not found', code: 'billing.payment.account_missing' })
72
+ return account
73
+ }
74
+
75
+ async function countActiveAllocations(em: EntityManager, scope: ArScope, paymentId: string): Promise<number> {
76
+ const rows = (await em.execute(
77
+ `SELECT COUNT(*)::int AS n FROM billing_allocations
78
+ WHERE tenant_id = ? AND organization_id = ? AND source_type = 'payment' AND source_id = ? AND deleted_at IS NULL`,
79
+ [scope.tenantId, scope.organizationId, paymentId],
80
+ )) as unknown as Array<{ n: number }>
81
+ return Number(rows?.[0]?.n ?? 0)
82
+ }
83
+
84
+ export type PaymentCommandResult = { paymentId: string; status: string; unallocatedAmount: string }
85
+
86
+ async function paymentResult(em: EntityManager, scope: ArScope, paymentId: string): Promise<PaymentCommandResult> {
87
+ const rows = (await em.execute(
88
+ `SELECT status, unallocated_amount FROM billing_payments WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
89
+ [paymentId, scope.tenantId, scope.organizationId],
90
+ )) as unknown as Array<{ status: string; unallocated_amount: string }>
91
+ return { paymentId, status: rows?.[0]?.status ?? 'unmatched', unallocatedAmount: rows?.[0]?.unallocated_amount ?? '0' }
92
+ }
93
+
94
+ // ─── Import ──────────────────────────────────────────────────────
95
+
96
+ const importPaymentsCommand: CommandHandler<BillingPaymentImportInput, ImportSummary> = {
97
+ id: 'billing.payments.import',
98
+ async execute(rawInput, ctx) {
99
+ const parsed = billingPaymentImportSchema.parse(rawInput)
100
+ ensureTenantScope(ctx, parsed.tenantId)
101
+ ensureOrganizationScope(ctx, parsed.organizationId)
102
+ const scope = scopeOf(parsed)
103
+ const { summary, effects, paymentIds } = await importPayments(getEm(ctx), {
104
+ scope,
105
+ fileName: parsed.fileName,
106
+ content: parsed.content,
107
+ userId: userIdOf(ctx),
108
+ })
109
+ for (const paymentId of paymentIds) await emitBillingEvent('billing.payment.created', { ...scope, paymentId, source: 'csv' })
110
+ await emitAllocationEffects(scope, effects)
111
+ await emitBillingEvent('billing.payment_import.completed', { ...scope, importId: summary.importId, counts: summary.counts })
112
+ return summary
113
+ },
114
+ }
115
+
116
+ // ─── Manual entry ────────────────────────────────────────────────
117
+
118
+ const createPaymentCommand: CommandHandler<BillingPaymentCreateInput, PaymentCommandResult> = {
119
+ id: 'billing.payments.create',
120
+ async execute(rawInput, ctx) {
121
+ const parsed = billingPaymentCreateSchema.parse(rawInput)
122
+ ensureTenantScope(ctx, parsed.tenantId)
123
+ ensureOrganizationScope(ctx, parsed.organizationId)
124
+ const scope = scopeOf(parsed)
125
+ const em = getEm(ctx)
126
+ if (parsed.billAccountId) {
127
+ const account = await loadAccount(em, scope, parsed.billAccountId)
128
+ if (account.currencyCode !== parsed.currencyCode) {
129
+ throw new CrudHttpError(409, { error: 'Payment currency differs from the account', code: 'billing.payment.currency_mismatch' })
130
+ }
131
+ }
132
+ const amount = parsed.amount.toFixed(4)
133
+ const now = new Date()
134
+ const payment = em.create(BillingPayment, {
135
+ ...scope,
136
+ receivedAt: new Date(`${parsed.receivedAt}T00:00:00Z`),
137
+ amount,
138
+ currencyCode: parsed.currencyCode,
139
+ reference: parsed.reference ?? null,
140
+ payerName: parsed.payerName ?? null,
141
+ payerAccount: parsed.payerAccount ?? null,
142
+ source: 'manual',
143
+ billAccountId: parsed.billAccountId ?? null,
144
+ status: parsed.billAccountId ? 'matched' : 'unmatched',
145
+ unallocatedAmount: amount,
146
+ matchReason: parsed.billAccountId ? null : 'manual',
147
+ sourceRef: `manual:${createHash('sha256').update(`${scope.tenantId}|${now.toISOString()}|${Math.random()}`).digest('hex')}`,
148
+ createdAt: now,
149
+ updatedAt: now,
150
+ })
151
+ em.persist(payment)
152
+ await em.flush()
153
+ let effects = emptyEffects()
154
+ if (parsed.billAccountId) {
155
+ effects = await withAccountLock(em, { tenantId: scope.tenantId, billAccountId: parsed.billAccountId }, (tem) =>
156
+ applyAccountCredit(tem, scope, { id: parsed.billAccountId!, currencyCode: parsed.currencyCode }, {
157
+ onlySources: [{ type: 'payment', id: payment.id }],
158
+ }),
159
+ )
160
+ }
161
+ await emitBillingEvent('billing.payment.created', { ...scope, paymentId: payment.id, source: 'manual' })
162
+ await emitAllocationEffects(scope, effects)
163
+ return paymentResult(em, scope, payment.id)
164
+ },
165
+ }
166
+
167
+ // ─── Assign / unassign ───────────────────────────────────────────
168
+
169
+ const assignPaymentCommand: CommandHandler<BillingPaymentAssignInput, PaymentCommandResult> = {
170
+ id: 'billing.payments.assign',
171
+ async execute(rawInput, ctx) {
172
+ const parsed = billingPaymentAssignSchema.parse(rawInput)
173
+ ensureTenantScope(ctx, parsed.tenantId)
174
+ ensureOrganizationScope(ctx, parsed.organizationId)
175
+ const scope = scopeOf(parsed)
176
+ const em = getEm(ctx)
177
+ const [payment, account] = await Promise.all([
178
+ loadPayment(em, scope, parsed.paymentId),
179
+ loadAccount(em, scope, parsed.billAccountId),
180
+ ])
181
+ if (payment.status !== 'unmatched') {
182
+ throw new CrudHttpError(409, { error: 'Only unmatched payments can be assigned', code: 'billing.payment.not_unmatched' })
183
+ }
184
+ if (account.billingMode === 'prepaid') {
185
+ throw new CrudHttpError(409, {
186
+ error: 'Prepaid accounts take payments as top-ups — use "Convert to top-up"',
187
+ code: 'billing.payment.prepaid_account',
188
+ })
189
+ }
190
+ if (account.currencyCode !== payment.currencyCode) {
191
+ throw new CrudHttpError(409, { error: 'Payment currency differs from the account', code: 'billing.payment.currency_mismatch' })
192
+ }
193
+ const effects = await withAccountLock(em, { tenantId: scope.tenantId, billAccountId: account.id }, async (tem) => {
194
+ await tem.execute(
195
+ `UPDATE billing_payments SET bill_account_id = ?, status = 'matched', match_reason = 'manual', updated_at = now()
196
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND status = 'unmatched'`,
197
+ [account.id, payment.id, scope.tenantId, scope.organizationId],
198
+ )
199
+ return applyAccountCredit(tem, scope, { id: account.id, currencyCode: account.currencyCode }, {
200
+ onlySources: [{ type: 'payment', id: payment.id }],
201
+ })
202
+ })
203
+ await emitAllocationEffects(scope, effects)
204
+ return paymentResult(em, scope, payment.id)
205
+ },
206
+ }
207
+
208
+ const unassignPaymentCommand: CommandHandler<BillingPaymentIdInput, PaymentCommandResult> = {
209
+ id: 'billing.payments.unassign',
210
+ async execute(rawInput, ctx) {
211
+ const parsed = billingPaymentIdSchema.parse(rawInput)
212
+ ensureTenantScope(ctx, parsed.tenantId)
213
+ ensureOrganizationScope(ctx, parsed.organizationId)
214
+ const scope = scopeOf(parsed)
215
+ const em = getEm(ctx)
216
+ const payment = await loadPayment(em, scope, parsed.paymentId)
217
+ if (payment.status !== 'matched' || !payment.billAccountId) {
218
+ throw new CrudHttpError(409, { error: 'Only fully unallocated matched payments can be unassigned', code: 'billing.payment.not_matched' })
219
+ }
220
+ if ((await countActiveAllocations(em, scope, payment.id)) > 0) {
221
+ throw new CrudHttpError(409, { error: 'Un-allocate the payment first', code: 'billing.payment.has_allocations' })
222
+ }
223
+ await em.execute(
224
+ `UPDATE billing_payments SET bill_account_id = NULL, status = 'unmatched', match_reason = 'manual', updated_at = now()
225
+ WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
226
+ [payment.id, scope.tenantId, scope.organizationId],
227
+ )
228
+ return paymentResult(em, scope, payment.id)
229
+ },
230
+ }
231
+
232
+ // ─── Allocate / unallocate ───────────────────────────────────────
233
+
234
+ const allocatePaymentCommand: CommandHandler<BillingPaymentAllocateInput, PaymentCommandResult> = {
235
+ id: 'billing.payments.allocate',
236
+ async execute(rawInput, ctx) {
237
+ const parsed = billingPaymentAllocateSchema.parse(rawInput)
238
+ ensureTenantScope(ctx, parsed.tenantId)
239
+ ensureOrganizationScope(ctx, parsed.organizationId)
240
+ const scope = scopeOf(parsed)
241
+ const em = getEm(ctx)
242
+ const payment = await loadPayment(em, scope, parsed.paymentId)
243
+ if (!payment.billAccountId) {
244
+ throw new CrudHttpError(409, { error: 'Assign the payment to an account first', code: 'billing.payment.unassigned' })
245
+ }
246
+ const effects = await withAccountLock(em, { tenantId: scope.tenantId, billAccountId: payment.billAccountId }, async (tem) => {
247
+ const all = emptyEffects()
248
+ for (const entry of parsed.allocations) {
249
+ mergeEffects(
250
+ all,
251
+ await allocate(tem, scope, {
252
+ source: { type: 'payment', id: payment.id },
253
+ invoiceId: entry.invoiceId,
254
+ amountCents: toCents(entry.amount),
255
+ by: 'user',
256
+ userId: userIdOf(ctx),
257
+ }),
258
+ )
259
+ }
260
+ return all
261
+ })
262
+ await emitAllocationEffects(scope, effects)
263
+ return paymentResult(em, scope, payment.id)
264
+ },
265
+ }
266
+
267
+ /** Source-agnostic: removes a payment OR credit memo allocation (Spec D P-D10). */
268
+ const unallocateCommand: CommandHandler<BillingAllocationRemoveInput, { allocationId: string; effects: AllocationEffects }> = {
269
+ id: 'billing.payments.unallocate',
270
+ async execute(rawInput, ctx) {
271
+ const parsed = billingAllocationRemoveSchema.parse(rawInput)
272
+ ensureTenantScope(ctx, parsed.tenantId)
273
+ ensureOrganizationScope(ctx, parsed.organizationId)
274
+ const scope = scopeOf(parsed)
275
+ const em = getEm(ctx)
276
+ const rows = (await em.execute(
277
+ `SELECT bill_account_id FROM billing_allocations
278
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`,
279
+ [parsed.allocationId, scope.tenantId, scope.organizationId],
280
+ )) as unknown as Array<{ bill_account_id: string }>
281
+ const billAccountId = rows?.[0]?.bill_account_id
282
+ if (!billAccountId) throw new CrudHttpError(404, { error: 'Allocation not found', code: 'billing.allocation.not_found' })
283
+ const effects = await withAccountLock(em, { tenantId: scope.tenantId, billAccountId }, (tem) =>
284
+ unallocate(tem, scope, { allocationId: parsed.allocationId, userId: userIdOf(ctx) }),
285
+ )
286
+ await emitAllocationEffects(scope, effects)
287
+ return { allocationId: parsed.allocationId, effects }
288
+ },
289
+ }
290
+
291
+ // ─── Ignore / restore ────────────────────────────────────────────
292
+
293
+ const ignorePaymentCommand: CommandHandler<BillingPaymentIdInput, PaymentCommandResult> = {
294
+ id: 'billing.payments.ignore',
295
+ async execute(rawInput, ctx) {
296
+ const parsed = billingPaymentIdSchema.parse(rawInput)
297
+ ensureTenantScope(ctx, parsed.tenantId)
298
+ ensureOrganizationScope(ctx, parsed.organizationId)
299
+ const scope = scopeOf(parsed)
300
+ const em = getEm(ctx)
301
+ const payment = await loadPayment(em, scope, parsed.paymentId)
302
+ if (!['unmatched', 'matched'].includes(payment.status) || (await countActiveAllocations(em, scope, payment.id)) > 0) {
303
+ throw new CrudHttpError(409, { error: 'Only unallocated payments can be ignored', code: 'billing.payment.has_allocations' })
304
+ }
305
+ await em.execute(
306
+ `UPDATE billing_payments SET status = 'ignored', metadata = COALESCE(metadata, '{}'::jsonb) || jsonb_build_object('ignored_from', ?::text), updated_at = now()
307
+ WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
308
+ [payment.status, payment.id, scope.tenantId, scope.organizationId],
309
+ )
310
+ return paymentResult(em, scope, payment.id)
311
+ },
312
+ }
313
+
314
+ const restorePaymentCommand: CommandHandler<BillingPaymentIdInput, PaymentCommandResult> = {
315
+ id: 'billing.payments.restore',
316
+ async execute(rawInput, ctx) {
317
+ const parsed = billingPaymentIdSchema.parse(rawInput)
318
+ ensureTenantScope(ctx, parsed.tenantId)
319
+ ensureOrganizationScope(ctx, parsed.organizationId)
320
+ const scope = scopeOf(parsed)
321
+ const em = getEm(ctx)
322
+ const payment = await loadPayment(em, scope, parsed.paymentId)
323
+ if (payment.status !== 'ignored') {
324
+ throw new CrudHttpError(409, { error: 'Only ignored payments can be restored', code: 'billing.payment.not_ignored' })
325
+ }
326
+ const status = payment.billAccountId ? 'matched' : 'unmatched'
327
+ let effects = emptyEffects()
328
+ if (payment.billAccountId) {
329
+ const account = await loadAccount(em, scope, payment.billAccountId)
330
+ effects = await withAccountLock(em, { tenantId: scope.tenantId, billAccountId: account.id }, async (tem) => {
331
+ await tem.execute(`UPDATE billing_payments SET status = ?, updated_at = now() WHERE id = ?`, [status, payment.id])
332
+ return applyAccountCredit(tem, scope, { id: account.id, currencyCode: account.currencyCode }, {
333
+ onlySources: [{ type: 'payment', id: payment.id }],
334
+ })
335
+ })
336
+ } else {
337
+ await em.execute(`UPDATE billing_payments SET status = ?, updated_at = now() WHERE id = ?`, [status, payment.id])
338
+ }
339
+ await emitAllocationEffects(scope, effects)
340
+ return paymentResult(em, scope, payment.id)
341
+ },
342
+ }
343
+
344
+ // ─── Convert to top-up (C-D3) ────────────────────────────────────
345
+
346
+ const convertToTopupCommand: CommandHandler<BillingPaymentConvertInput, PaymentCommandResult & { topupId: string }> = {
347
+ id: 'billing.payments.convert_to_topup',
348
+ async execute(rawInput, ctx) {
349
+ const parsed = billingPaymentConvertSchema.parse(rawInput)
350
+ ensureTenantScope(ctx, parsed.tenantId)
351
+ ensureOrganizationScope(ctx, parsed.organizationId)
352
+ const scope = scopeOf(parsed)
353
+ const em = getEm(ctx)
354
+ const [payment, account] = await Promise.all([
355
+ loadPayment(em, scope, parsed.paymentId),
356
+ loadAccount(em, scope, parsed.billAccountId),
357
+ ])
358
+ if (account.billingMode !== 'prepaid') {
359
+ throw new CrudHttpError(409, { error: 'Only prepaid accounts take top-ups', code: 'billing.payment.not_prepaid' })
360
+ }
361
+ if (account.currencyCode !== payment.currencyCode) {
362
+ throw new CrudHttpError(409, { error: 'Payment currency differs from the account', code: 'billing.payment.currency_mismatch' })
363
+ }
364
+ const sourceRef = `topup-${payment.id}`
365
+ if (payment.status === 'converted_to_topup' && payment.topupId) {
366
+ return { ...(await paymentResult(em, scope, payment.id)), topupId: payment.topupId }
367
+ }
368
+ if (payment.status !== 'unmatched' || toCents(payment.unallocatedAmount) !== toCents(payment.amount)) {
369
+ throw new CrudHttpError(409, { error: 'Only unmatched, unallocated payments can be converted', code: 'billing.payment.not_convertible' })
370
+ }
371
+ let topup = await em.findOne(BillingTopup, { ...scope, sourceRef } as never)
372
+ if (!topup) {
373
+ const now = new Date()
374
+ topup = em.create(BillingTopup, {
375
+ ...scope,
376
+ billAccountId: account.id,
377
+ status: 'pending',
378
+ amount: payment.amount,
379
+ currencyCode: payment.currencyCode,
380
+ providerKey: 'bank_transfer',
381
+ paymentId: payment.id,
382
+ sourceRef,
383
+ metadata: { billing_payment_id: payment.id, reference: payment.reference ?? null },
384
+ createdAt: now,
385
+ updatedAt: now,
386
+ } as never)
387
+ em.persist(topup)
388
+ await em.flush()
389
+ }
390
+ const settings = await getBillingSettings(em, scope)
391
+ await captureTopup(em, {
392
+ topup,
393
+ account: { id: account.id, organizationId: account.organizationId, tenantId: account.tenantId, currencyCode: account.currencyCode },
394
+ taxRate: receiptTaxRate(settings),
395
+ gatewayTransactionId: payment.reference ?? null,
396
+ })
397
+ await em.execute(
398
+ `UPDATE billing_payments
399
+ SET status = 'converted_to_topup', bill_account_id = ?, topup_id = ?, unallocated_amount = 0, updated_at = now()
400
+ WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
401
+ [account.id, topup.id, payment.id, scope.tenantId, scope.organizationId],
402
+ )
403
+ await emitBillingEvent('billing.payment.converted_to_topup', { ...scope, paymentId: payment.id, topupId: topup.id, billAccountId: account.id })
404
+ return { ...(await paymentResult(em, scope, payment.id)), topupId: topup.id }
405
+ },
406
+ }
407
+
408
+ registerCommand(importPaymentsCommand)
409
+ registerCommand(createPaymentCommand)
410
+ registerCommand(assignPaymentCommand)
411
+ registerCommand(unassignPaymentCommand)
412
+ registerCommand(allocatePaymentCommand)
413
+ registerCommand(unallocateCommand)
414
+ registerCommand(ignorePaymentCommand)
415
+ registerCommand(restorePaymentCommand)
416
+ registerCommand(convertToTopupCommand)
417
+
418
+ export {
419
+ importPaymentsCommand,
420
+ createPaymentCommand,
421
+ assignPaymentCommand,
422
+ unassignPaymentCommand,
423
+ allocatePaymentCommand,
424
+ unallocateCommand,
425
+ ignorePaymentCommand,
426
+ restorePaymentCommand,
427
+ convertToTopupCommand,
428
+ }
@@ -8,13 +8,21 @@ import {
8
8
  } from '@open-mercato/shared/lib/commands/scope'
9
9
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
10
10
  import { BillingRun, BillingRunOutcome } from '../data/entities'
11
+ import type { CommandBus } from '@open-mercato/shared/lib/commands'
12
+ import { isCrudHttpError } from '@open-mercato/shared/lib/crud/errors'
11
13
  import {
14
+ billingRunDeleteSchema,
15
+ billingRunPostDraftsSchema,
16
+ type BillingRunPostDraftsInput,
12
17
  billingRunRetryFailedSchema,
13
18
  billingRunTriggerSchema,
19
+ type BillingRunDeleteInput,
14
20
  type BillingRunRetryFailedInput,
15
21
  type BillingRunTriggerInput,
16
22
  } from '../data/validators'
23
+ import { emitBillingEvent } from '../events'
17
24
  import { runBillRun } from '../lib/billRunEngine'
25
+ import { deleteTestRunArtifacts } from '../lib/testRunCleanup'
18
26
  import { TenantLockBusyError } from '../lib/tenantLock'
19
27
 
20
28
  /**
@@ -71,7 +79,6 @@ const triggerRunCommand: CommandHandler<BillingRunTriggerInput, TriggerResult> =
71
79
  triggeredBy: 'manual',
72
80
  mode: parsed.mode,
73
81
  scopedAccountIds: parsed.scopedAccountIds,
74
- catchUp: parsed.catchUp,
75
82
  })
76
83
  return {
77
84
  billRunId: billRun.id,
@@ -139,7 +146,6 @@ const retryFailedCommand: CommandHandler<BillingRunRetryFailedInput, TriggerResu
139
146
  mode: parsed.mode,
140
147
  scopedAccountIds,
141
148
  parentRunId: parentRun.id,
142
- catchUp: parentRun.catchUp,
143
149
  })
144
150
  return {
145
151
  billRunId: billRun.id,
@@ -160,7 +166,203 @@ const retryFailedCommand: CommandHandler<BillingRunRetryFailedInput, TriggerResu
160
166
  },
161
167
  }
162
168
 
169
+ // ─── Delete (Spec F, F-D1) ───────────────────────────────────────
170
+ //
171
+ // Test and dry runs are scratch data: deleting one removes its test
172
+ // invoices (with lines and draft-edit audit rows), its outcomes and the run
173
+ // row, in one transaction. Real runs are the audit trail and are refused.
174
+ // Retry children keep existing; their parent link is cleared. Not undoable,
175
+ // like the test-invoice wipe.
176
+
177
+ type DeleteRunResult = { ok: true; invoicesRemoved: number }
178
+
179
+ export function runMode(run: Pick<BillingRun, 'dryRun' | 'testMode'>): 'real' | 'test' | 'dry' {
180
+ if (run.dryRun) return 'dry'
181
+ if (run.testMode) return 'test'
182
+ return 'real'
183
+ }
184
+
185
+ const deleteRunCommand: CommandHandler<BillingRunDeleteInput, DeleteRunResult> = {
186
+ id: 'billing.runs.delete',
187
+
188
+ async execute(rawInput, ctx) {
189
+ const parsed = billingRunDeleteSchema.parse(rawInput)
190
+ ensureTenantScope(ctx, parsed.tenantId)
191
+ ensureOrganizationScope(ctx, parsed.organizationId)
192
+
193
+ const em = getEm(ctx)
194
+ const outcome = await em.transactional(async (tem) => {
195
+ const run = await tem.findOne(BillingRun, {
196
+ id: parsed.billRunId,
197
+ tenantId: parsed.tenantId,
198
+ organizationId: parsed.organizationId,
199
+ deletedAt: null,
200
+ })
201
+ // 404 for another organization's run too (no existence leak).
202
+ if (!run) throw new CrudHttpError(404, { error: 'Bill Run not found', code: 'billing.run.not_found' })
203
+ const mode = runMode(run)
204
+ if (mode === 'real') {
205
+ throw new CrudHttpError(422, {
206
+ error: 'Real Bill Runs cannot be deleted',
207
+ code: 'billing.run.delete_real_forbidden',
208
+ })
209
+ }
210
+ if (run.status === 'running') {
211
+ throw new CrudHttpError(409, { error: 'The Bill Run is still running', code: 'billing.run.running' })
212
+ }
213
+ const scope = { tenantId: parsed.tenantId, organizationId: parsed.organizationId }
214
+ const invoicesRemoved = await deleteTestRunArtifacts(tem as EntityManager, { ...scope, billRunId: run.id })
215
+ await tem.execute(
216
+ 'UPDATE billing_runs SET parent_run_id = NULL WHERE tenant_id = ? AND organization_id = ? AND parent_run_id = ?',
217
+ [scope.tenantId, scope.organizationId, run.id],
218
+ )
219
+ await tem.execute(
220
+ 'DELETE FROM billing_run_outcomes WHERE tenant_id = ? AND organization_id = ? AND bill_run_id = ?',
221
+ [scope.tenantId, scope.organizationId, run.id],
222
+ )
223
+ await tem.execute('DELETE FROM billing_runs WHERE tenant_id = ? AND organization_id = ? AND id = ?', [
224
+ scope.tenantId,
225
+ scope.organizationId,
226
+ run.id,
227
+ ])
228
+ return { mode, invoicesRemoved }
229
+ })
230
+
231
+ // After commit: subscribers never see a phantom delete.
232
+ await emitBillingEvent(
233
+ 'billing.run.deleted',
234
+ {
235
+ billRunId: parsed.billRunId,
236
+ tenantId: parsed.tenantId,
237
+ organizationId: parsed.organizationId,
238
+ mode: outcome.mode,
239
+ invoicesRemoved: outcome.invoicesRemoved,
240
+ },
241
+ { persistent: true },
242
+ )
243
+ return { ok: true, invoicesRemoved: outcome.invoicesRemoved }
244
+ },
245
+ }
246
+
247
+ // ─── Post the run's drafts ──────────────────────────────────────
248
+ //
249
+ // Posts the draft invoices a REAL run wrote, in bounded batches (ascending
250
+ // id, keyset cursor) so a run with thousands of invoices never becomes one
251
+ // long request. Each invoice goes through `billing.invoices.post` on its own
252
+ // — numbering, frozen PDF, due date, credit auto-apply, auto-send and events
253
+ // stay exactly as for a single post — so one failure never undoes the
254
+ // others. A seller-settings failure would fail every invoice, so the batch
255
+ // stops at the first one and says so.
256
+
257
+ export const POST_DRAFTS_DEFAULT_LIMIT = 25
258
+
259
+ type PostDraftsFailure = { invoiceId: string; number: string; error: string; code: string | null }
260
+
261
+ export type PostDraftsResult = {
262
+ posted: number
263
+ failed: PostDraftsFailure[]
264
+ /** Last invoice id handled — pass back as `afterInvoiceId`. Null when nothing was processed. */
265
+ nextCursor: string | null
266
+ /** Drafts of the run after the cursor that are still to process. */
267
+ remaining: number
268
+ /** Set when the batch stopped early (e.g. `billing.settings.incomplete`). */
269
+ stoppedReason: string | null
270
+ }
271
+
272
+ const DRAFTS_AFTER_SQL = `SELECT id, number FROM billing_invoices
273
+ WHERE tenant_id = ? AND organization_id = ? AND bill_run_id = ?
274
+ AND status = 'draft' AND test_run = false AND document_type = 'invoice' AND deleted_at IS NULL
275
+ AND (?::uuid IS NULL OR id > ?::uuid)
276
+ ORDER BY id
277
+ LIMIT ?`
278
+
279
+ const DRAFTS_REMAINING_SQL = `SELECT COUNT(*)::int AS remaining FROM billing_invoices
280
+ WHERE tenant_id = ? AND organization_id = ? AND bill_run_id = ?
281
+ AND status = 'draft' AND test_run = false AND document_type = 'invoice' AND deleted_at IS NULL
282
+ AND (?::uuid IS NULL OR id > ?::uuid)`
283
+
284
+ const postDraftsCommand: CommandHandler<BillingRunPostDraftsInput, PostDraftsResult> = {
285
+ id: 'billing.runs.post_drafts',
286
+
287
+ async execute(rawInput, ctx) {
288
+ const parsed = billingRunPostDraftsSchema.parse(rawInput)
289
+ ensureTenantScope(ctx, parsed.tenantId)
290
+ ensureOrganizationScope(ctx, parsed.organizationId)
291
+
292
+ const em = getEm(ctx)
293
+ const run = await em.findOne(BillingRun, {
294
+ id: parsed.billRunId,
295
+ tenantId: parsed.tenantId,
296
+ organizationId: parsed.organizationId,
297
+ deletedAt: null,
298
+ })
299
+ if (!run) throw new CrudHttpError(404, { error: 'Bill Run not found', code: 'billing.run.not_found' })
300
+ if (runMode(run) !== 'real') {
301
+ throw new CrudHttpError(422, { error: 'Only real Bill Runs have invoices to post', code: 'billing.run.not_real' })
302
+ }
303
+ if (run.status === 'running') {
304
+ throw new CrudHttpError(409, { error: 'The Bill Run is still running', code: 'billing.run.running' })
305
+ }
306
+
307
+ const scope = [parsed.tenantId, parsed.organizationId, run.id] as const
308
+ const cursor = parsed.afterInvoiceId ?? null
309
+ const limit = parsed.limit ?? POST_DRAFTS_DEFAULT_LIMIT
310
+ const drafts = (await em.execute(DRAFTS_AFTER_SQL, [...scope, cursor, cursor, limit])) as unknown as Array<{
311
+ id: string
312
+ number: string
313
+ }>
314
+
315
+ const commandBus = ctx.container.resolve('commandBus') as CommandBus
316
+ const failed: PostDraftsFailure[] = []
317
+ let posted = 0
318
+ let nextCursor: string | null = null
319
+ let stoppedReason: string | null = null
320
+ for (const draft of drafts ?? []) {
321
+ nextCursor = draft.id
322
+ try {
323
+ await commandBus.execute('billing.invoices.post', {
324
+ input: { tenantId: parsed.tenantId, organizationId: parsed.organizationId, invoiceId: draft.id },
325
+ ctx,
326
+ })
327
+ posted += 1
328
+ } catch (err) {
329
+ const body = isCrudHttpError(err) ? (err.body as { error?: string; code?: string }) : null
330
+ const code = body?.code ?? null
331
+ failed.push({
332
+ invoiceId: draft.id,
333
+ number: draft.number,
334
+ error: body?.error ?? (err instanceof Error ? err.message : String(err)),
335
+ code,
336
+ })
337
+ if (code === 'billing.settings.incomplete') {
338
+ // Every other draft would fail the same way — stop. The returned
339
+ // cursor stays where this call started; drafts posted in this call
340
+ // are no longer drafts, so a retry after fixing the settings picks
341
+ // up exactly the ones still pending.
342
+ stoppedReason = code
343
+ break
344
+ }
345
+ }
346
+ }
347
+
348
+ const remainingRows = (await em.execute(DRAFTS_REMAINING_SQL, [
349
+ ...scope,
350
+ stoppedReason ? cursor : nextCursor,
351
+ stoppedReason ? cursor : nextCursor,
352
+ ])) as unknown as Array<{ remaining: number }>
353
+ return {
354
+ posted,
355
+ failed,
356
+ nextCursor: stoppedReason ? cursor : nextCursor,
357
+ remaining: Number(remainingRows?.[0]?.remaining ?? 0),
358
+ stoppedReason,
359
+ }
360
+ },
361
+ }
362
+
163
363
  registerCommand(triggerRunCommand)
164
364
  registerCommand(retryFailedCommand)
365
+ registerCommand(deleteRunCommand)
366
+ registerCommand(postDraftsCommand)
165
367
 
166
- export { triggerRunCommand, retryFailedCommand }
368
+ export { triggerRunCommand, retryFailedCommand, deleteRunCommand, postDraftsCommand }