@dainamite/billing 0.19.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 (614) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +14 -0
  3. package/dist/modules/billing/acl.js +13 -1
  4. package/dist/modules/billing/acl.js.map +2 -2
  5. package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
  6. package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
  7. package/dist/modules/billing/api/accounts/route.js +46 -4
  8. package/dist/modules/billing/api/accounts/route.js.map +2 -2
  9. package/dist/modules/billing/api/export/account/[id]/route.js +16 -12
  10. package/dist/modules/billing/api/export/account/[id]/route.js.map +2 -2
  11. package/dist/modules/billing/api/invoices/[id]/memos/route.js +54 -0
  12. package/dist/modules/billing/api/invoices/[id]/memos/route.js.map +7 -0
  13. package/dist/modules/billing/api/invoices/[id]/pdf/route.js +69 -0
  14. package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
  15. package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
  16. package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
  17. package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
  18. package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
  19. package/dist/modules/billing/api/invoices/add-line/route.js +3 -3
  20. package/dist/modules/billing/api/invoices/add-line/route.js.map +2 -2
  21. package/dist/modules/billing/api/invoices/delete-draft/route.js +33 -0
  22. package/dist/modules/billing/api/invoices/delete-draft/route.js.map +7 -0
  23. package/dist/modules/billing/api/invoices/edit-line/route.js +3 -3
  24. package/dist/modules/billing/api/invoices/edit-line/route.js.map +2 -2
  25. package/dist/modules/billing/api/invoices/post/route.js +2 -2
  26. package/dist/modules/billing/api/invoices/post/route.js.map +2 -2
  27. package/dist/modules/billing/api/invoices/remove-line/route.js +3 -3
  28. package/dist/modules/billing/api/invoices/remove-line/route.js.map +2 -2
  29. package/dist/modules/billing/api/invoices/route.js +45 -46
  30. package/dist/modules/billing/api/invoices/route.js.map +2 -2
  31. package/dist/modules/billing/api/items/bulk/route.js +1 -1
  32. package/dist/modules/billing/api/items/bulk/route.js.map +2 -2
  33. package/dist/modules/billing/api/items/change-price/route.js +44 -0
  34. package/dist/modules/billing/api/items/change-price/route.js.map +7 -0
  35. package/dist/modules/billing/api/items/route.js +19 -7
  36. package/dist/modules/billing/api/items/route.js.map +2 -2
  37. package/dist/modules/billing/api/payment-imports/[id]/route.js +49 -0
  38. package/dist/modules/billing/api/payment-imports/[id]/route.js.map +7 -0
  39. package/dist/modules/billing/api/payment-imports/route.js +57 -0
  40. package/dist/modules/billing/api/payment-imports/route.js.map +7 -0
  41. package/dist/modules/billing/api/payments/[id]/route.js +60 -0
  42. package/dist/modules/billing/api/payments/[id]/route.js.map +7 -0
  43. package/dist/modules/billing/api/payments/allocate/route.js +35 -0
  44. package/dist/modules/billing/api/payments/allocate/route.js.map +7 -0
  45. package/dist/modules/billing/api/payments/assign/route.js +36 -0
  46. package/dist/modules/billing/api/payments/assign/route.js.map +7 -0
  47. package/dist/modules/billing/api/payments/convert-to-topup/route.js +35 -0
  48. package/dist/modules/billing/api/payments/convert-to-topup/route.js.map +7 -0
  49. package/dist/modules/billing/api/payments/ignore/route.js +35 -0
  50. package/dist/modules/billing/api/payments/ignore/route.js.map +7 -0
  51. package/dist/modules/billing/api/payments/import/route.js +63 -0
  52. package/dist/modules/billing/api/payments/import/route.js.map +7 -0
  53. package/dist/modules/billing/api/payments/restore/route.js +35 -0
  54. package/dist/modules/billing/api/payments/restore/route.js.map +7 -0
  55. package/dist/modules/billing/api/payments/route.js +114 -0
  56. package/dist/modules/billing/api/payments/route.js.map +7 -0
  57. package/dist/modules/billing/api/payments/unallocate/route.js +35 -0
  58. package/dist/modules/billing/api/payments/unallocate/route.js.map +7 -0
  59. package/dist/modules/billing/api/payments/unassign/route.js +35 -0
  60. package/dist/modules/billing/api/payments/unassign/route.js.map +7 -0
  61. package/dist/modules/billing/api/run-outcomes/route.js +44 -7
  62. package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
  63. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js +61 -0
  64. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js.map +7 -0
  65. package/dist/modules/billing/api/runs/[id]/route.js +53 -0
  66. package/dist/modules/billing/api/runs/[id]/route.js.map +7 -0
  67. package/dist/modules/billing/api/runs/retry-failed/route.js +3 -3
  68. package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
  69. package/dist/modules/billing/api/runs/route.js +11 -5
  70. package/dist/modules/billing/api/runs/route.js.map +2 -2
  71. package/dist/modules/billing/api/settings/number-preview/route.js +42 -0
  72. package/dist/modules/billing/api/settings/number-preview/route.js.map +7 -0
  73. package/dist/modules/billing/api/settings/route.js +92 -0
  74. package/dist/modules/billing/api/settings/route.js.map +7 -0
  75. package/dist/modules/billing/api/statements/route.js +3 -0
  76. package/dist/modules/billing/api/statements/route.js.map +2 -2
  77. package/dist/modules/billing/api/test-invoices/wipe/route.js +2 -2
  78. package/dist/modules/billing/api/test-invoices/wipe/route.js.map +2 -2
  79. package/dist/modules/billing/backend/billing/accounts/[id]/page.js +121 -232
  80. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +2 -2
  81. package/dist/modules/billing/backend/billing/accounts/create/page.js +6 -0
  82. package/dist/modules/billing/backend/billing/accounts/create/page.js.map +2 -2
  83. package/dist/modules/billing/backend/billing/accounts/page.js +63 -4
  84. package/dist/modules/billing/backend/billing/accounts/page.js.map +2 -2
  85. package/dist/modules/billing/backend/billing/invoices/[id]/page.js +297 -65
  86. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +3 -3
  87. package/dist/modules/billing/backend/billing/invoices/page.js +6 -172
  88. package/dist/modules/billing/backend/billing/invoices/page.js.map +2 -2
  89. package/dist/modules/billing/backend/billing/items/[id]/page.js +51 -7
  90. package/dist/modules/billing/backend/billing/items/[id]/page.js.map +2 -2
  91. package/dist/modules/billing/backend/billing/items/create/page.js +9 -6
  92. package/dist/modules/billing/backend/billing/items/create/page.js.map +2 -2
  93. package/dist/modules/billing/backend/billing/items/page.js +8 -2
  94. package/dist/modules/billing/backend/billing/items/page.js.map +2 -2
  95. package/dist/modules/billing/backend/billing/payments/[id]/page.js +297 -0
  96. package/dist/modules/billing/backend/billing/payments/[id]/page.js.map +7 -0
  97. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js +21 -0
  98. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js.map +7 -0
  99. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js +74 -0
  100. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js.map +7 -0
  101. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js +21 -0
  102. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js.map +7 -0
  103. package/dist/modules/billing/backend/billing/payments/page.js +58 -0
  104. package/dist/modules/billing/backend/billing/payments/page.js.map +7 -0
  105. package/dist/modules/billing/backend/billing/payments/page.meta.js +18 -0
  106. package/dist/modules/billing/backend/billing/payments/page.meta.js.map +7 -0
  107. package/dist/modules/billing/backend/billing/runs/[id]/page.js +49 -14
  108. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +2 -2
  109. package/dist/modules/billing/backend/billing/runs/page.js +58 -7
  110. package/dist/modules/billing/backend/billing/runs/page.js.map +2 -2
  111. package/dist/modules/billing/backend/billing/statements/[id]/page.js +61 -6
  112. package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +2 -2
  113. package/dist/modules/billing/backend/billing/statements/page.js +1 -1
  114. package/dist/modules/billing/backend/billing/statements/page.js.map +2 -2
  115. package/dist/modules/billing/backend/billing/topups/page.js +1 -1
  116. package/dist/modules/billing/backend/billing/topups/page.js.map +2 -2
  117. package/dist/modules/billing/backend/billing/transactions/page.js +1 -1
  118. package/dist/modules/billing/backend/billing/transactions/page.js.map +2 -2
  119. package/dist/modules/billing/backend/billing/usage/create/page.js +3 -2
  120. package/dist/modules/billing/backend/billing/usage/create/page.js.map +2 -2
  121. package/dist/modules/billing/backend/config/billing/page.js +472 -0
  122. package/dist/modules/billing/backend/config/billing/page.js.map +7 -0
  123. package/dist/modules/billing/backend/config/billing/page.meta.js +19 -0
  124. package/dist/modules/billing/backend/config/billing/page.meta.js.map +7 -0
  125. package/dist/modules/billing/ce.js.map +2 -2
  126. package/dist/modules/billing/cli.js +22 -1
  127. package/dist/modules/billing/cli.js.map +2 -2
  128. package/dist/modules/billing/commands/accounts.js +6 -0
  129. package/dist/modules/billing/commands/accounts.js.map +2 -2
  130. package/dist/modules/billing/commands/index.js +3 -0
  131. package/dist/modules/billing/commands/index.js.map +2 -2
  132. package/dist/modules/billing/commands/invoices.js +272 -170
  133. package/dist/modules/billing/commands/invoices.js.map +2 -2
  134. package/dist/modules/billing/commands/items.js +108 -0
  135. package/dist/modules/billing/commands/items.js.map +2 -2
  136. package/dist/modules/billing/commands/memos.js +179 -0
  137. package/dist/modules/billing/commands/memos.js.map +7 -0
  138. package/dist/modules/billing/commands/payments.js +379 -0
  139. package/dist/modules/billing/commands/payments.js.map +7 -0
  140. package/dist/modules/billing/commands/runs.js +154 -7
  141. package/dist/modules/billing/commands/runs.js.map +2 -2
  142. package/dist/modules/billing/commands/settings.js +59 -0
  143. package/dist/modules/billing/commands/settings.js.map +7 -0
  144. package/dist/modules/billing/commands/topups.js +9 -2
  145. package/dist/modules/billing/commands/topups.js.map +2 -2
  146. package/dist/modules/billing/commands/transactions.js +2 -2
  147. package/dist/modules/billing/commands/transactions.js.map +2 -2
  148. package/dist/modules/billing/components/AccountForm.js +149 -42
  149. package/dist/modules/billing/components/AccountForm.js.map +2 -2
  150. package/dist/modules/billing/components/AccountInvoicesSection.js +64 -0
  151. package/dist/modules/billing/components/AccountInvoicesSection.js.map +7 -0
  152. package/dist/modules/billing/components/AccountPaymentsSection.js +43 -0
  153. package/dist/modules/billing/components/AccountPaymentsSection.js.map +7 -0
  154. package/dist/modules/billing/components/AccountPicker.js +56 -0
  155. package/dist/modules/billing/components/AccountPicker.js.map +7 -0
  156. package/dist/modules/billing/components/AllocatePaymentDialog.js +134 -0
  157. package/dist/modules/billing/components/AllocatePaymentDialog.js.map +7 -0
  158. package/dist/modules/billing/components/BillingDataTable.js +21 -0
  159. package/dist/modules/billing/components/BillingDataTable.js.map +7 -0
  160. package/dist/modules/billing/components/BillingSettingsLink.js +19 -0
  161. package/dist/modules/billing/components/BillingSettingsLink.js.map +7 -0
  162. package/dist/modules/billing/components/ChangeItemPriceDialog.js +158 -0
  163. package/dist/modules/billing/components/ChangeItemPriceDialog.js.map +7 -0
  164. package/dist/modules/billing/components/CreateMemoDialog.js +200 -0
  165. package/dist/modules/billing/components/CreateMemoDialog.js.map +7 -0
  166. package/dist/modules/billing/components/CustomerLabel.js +33 -0
  167. package/dist/modules/billing/components/CustomerLabel.js.map +7 -0
  168. package/dist/modules/billing/components/InvoicesTable.js +270 -0
  169. package/dist/modules/billing/components/InvoicesTable.js.map +7 -0
  170. package/dist/modules/billing/components/ItemForm.js +176 -44
  171. package/dist/modules/billing/components/ItemForm.js.map +2 -2
  172. package/dist/modules/billing/components/ItemPriceField.js +46 -0
  173. package/dist/modules/billing/components/ItemPriceField.js.map +7 -0
  174. package/dist/modules/billing/components/LineFormDialog.js +25 -2
  175. package/dist/modules/billing/components/LineFormDialog.js.map +2 -2
  176. package/dist/modules/billing/components/ManualPaymentDialog.js +122 -0
  177. package/dist/modules/billing/components/ManualPaymentDialog.js.map +7 -0
  178. package/dist/modules/billing/components/MoneyInput.js +14 -0
  179. package/dist/modules/billing/components/MoneyInput.js.map +7 -0
  180. package/dist/modules/billing/components/PaymentUploadDialog.js +115 -0
  181. package/dist/modules/billing/components/PaymentUploadDialog.js.map +7 -0
  182. package/dist/modules/billing/components/PaymentsTable.js +171 -0
  183. package/dist/modules/billing/components/PaymentsTable.js.map +7 -0
  184. package/dist/modules/billing/components/PostRunDraftsButton.js +105 -0
  185. package/dist/modules/billing/components/PostRunDraftsButton.js.map +7 -0
  186. package/dist/modules/billing/components/PrepaidPanel.js +14 -4
  187. package/dist/modules/billing/components/PrepaidPanel.js.map +2 -2
  188. package/dist/modules/billing/components/RunCyclesSection.js +209 -0
  189. package/dist/modules/billing/components/RunCyclesSection.js.map +7 -0
  190. package/dist/modules/billing/components/TestRunActions.js +127 -0
  191. package/dist/modules/billing/components/TestRunActions.js.map +7 -0
  192. package/dist/modules/billing/components/account/AccountItemsTab.js +157 -0
  193. package/dist/modules/billing/components/account/AccountItemsTab.js.map +7 -0
  194. package/dist/modules/billing/components/account/AccountRunBillingDialog.js +122 -0
  195. package/dist/modules/billing/components/account/AccountRunBillingDialog.js.map +7 -0
  196. package/dist/modules/billing/components/account/AccountRunsTab.js +152 -0
  197. package/dist/modules/billing/components/account/AccountRunsTab.js.map +7 -0
  198. package/dist/modules/billing/components/account/AccountStatementsTab.js +98 -0
  199. package/dist/modules/billing/components/account/AccountStatementsTab.js.map +7 -0
  200. package/dist/modules/billing/components/account/AccountSummaryCards.js +125 -0
  201. package/dist/modules/billing/components/account/AccountSummaryCards.js.map +7 -0
  202. package/dist/modules/billing/components/account/AccountUsageTab.js +91 -0
  203. package/dist/modules/billing/components/account/AccountUsageTab.js.map +7 -0
  204. package/dist/modules/billing/components/account/AccountView.js +73 -0
  205. package/dist/modules/billing/components/account/AccountView.js.map +7 -0
  206. package/dist/modules/billing/components/account/accountRow.js +39 -0
  207. package/dist/modules/billing/components/account/accountRow.js.map +7 -0
  208. package/dist/modules/billing/components/customerNames.js +42 -0
  209. package/dist/modules/billing/components/customerNames.js.map +7 -0
  210. package/dist/modules/billing/data/entities.js +615 -59
  211. package/dist/modules/billing/data/entities.js.map +2 -2
  212. package/dist/modules/billing/data/entityIds.js +10 -1
  213. package/dist/modules/billing/data/entityIds.js.map +2 -2
  214. package/dist/modules/billing/data/validators.js +269 -15
  215. package/dist/modules/billing/data/validators.js.map +2 -2
  216. package/dist/modules/billing/events.js +70 -0
  217. package/dist/modules/billing/events.js.map +2 -2
  218. package/dist/modules/billing/i18n/en.json +457 -21
  219. package/dist/modules/billing/i18n/pl.json +457 -21
  220. package/dist/modules/billing/index.js +6 -2
  221. package/dist/modules/billing/index.js.map +2 -2
  222. package/dist/modules/billing/lib/accountFinancials.js +58 -0
  223. package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
  224. package/dist/modules/billing/lib/accountLock.js +14 -0
  225. package/dist/modules/billing/lib/accountLock.js.map +7 -0
  226. package/dist/modules/billing/lib/accountStatus.js +9 -7
  227. package/dist/modules/billing/lib/accountStatus.js.map +2 -2
  228. package/dist/modules/billing/lib/accountSummary.js +106 -0
  229. package/dist/modules/billing/lib/accountSummary.js.map +7 -0
  230. package/dist/modules/billing/lib/allocation.js +356 -0
  231. package/dist/modules/billing/lib/allocation.js.map +7 -0
  232. package/dist/modules/billing/lib/allocationEvents.js +22 -0
  233. package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
  234. package/dist/modules/billing/lib/billPeriod.js +15 -1
  235. package/dist/modules/billing/lib/billPeriod.js.map +2 -2
  236. package/dist/modules/billing/lib/billRunEngine.js +241 -106
  237. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  238. package/dist/modules/billing/lib/billRunLabel.js.map +1 -1
  239. package/dist/modules/billing/lib/billingSettings.js +116 -0
  240. package/dist/modules/billing/lib/billingSettings.js.map +7 -0
  241. package/dist/modules/billing/lib/commandRoute.js +81 -0
  242. package/dist/modules/billing/lib/commandRoute.js.map +7 -0
  243. package/dist/modules/billing/lib/customerLookup.js +97 -0
  244. package/dist/modules/billing/lib/customerLookup.js.map +7 -0
  245. package/dist/modules/billing/lib/dateOnly.js +13 -0
  246. package/dist/modules/billing/lib/dateOnly.js.map +7 -0
  247. package/dist/modules/billing/lib/documentParties.js +16 -0
  248. package/dist/modules/billing/lib/documentParties.js.map +7 -0
  249. package/dist/modules/billing/lib/documentTypes.js +27 -0
  250. package/dist/modules/billing/lib/documentTypes.js.map +7 -0
  251. package/dist/modules/billing/lib/dueDate.js +18 -0
  252. package/dist/modules/billing/lib/dueDate.js.map +7 -0
  253. package/dist/modules/billing/lib/iban.js +29 -0
  254. package/dist/modules/billing/lib/iban.js.map +7 -0
  255. package/dist/modules/billing/lib/invoiceDelivery.js +131 -0
  256. package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
  257. package/dist/modules/billing/lib/invoiceLanguages.js +16 -0
  258. package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
  259. package/dist/modules/billing/lib/invoiceWriter.js +107 -99
  260. package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
  261. package/dist/modules/billing/lib/itemBillingState.js +27 -0
  262. package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
  263. package/dist/modules/billing/lib/itemPriceChange.js +18 -0
  264. package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
  265. package/dist/modules/billing/lib/itemSelector.js.map +2 -2
  266. package/dist/modules/billing/lib/lineAmounts.js +32 -0
  267. package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
  268. package/dist/modules/billing/lib/memoGuards.js +25 -0
  269. package/dist/modules/billing/lib/memoGuards.js.map +7 -0
  270. package/dist/modules/billing/lib/navigation.js +48 -0
  271. package/dist/modules/billing/lib/navigation.js.map +7 -0
  272. package/dist/modules/billing/lib/numbering.js +120 -0
  273. package/dist/modules/billing/lib/numbering.js.map +7 -0
  274. package/dist/modules/billing/lib/overdue.js +36 -0
  275. package/dist/modules/billing/lib/overdue.js.map +7 -0
  276. package/dist/modules/billing/lib/paymentCsv.js +125 -0
  277. package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
  278. package/dist/modules/billing/lib/paymentGateway.js +15 -0
  279. package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
  280. package/dist/modules/billing/lib/paymentImport.js +164 -0
  281. package/dist/modules/billing/lib/paymentImport.js.map +7 -0
  282. package/dist/modules/billing/lib/paymentMatcher.js +36 -0
  283. package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
  284. package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
  285. package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
  286. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
  287. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
  288. package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
  289. package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
  290. package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
  291. package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
  292. package/dist/modules/billing/lib/pdf/invoiceTemplate.js +274 -0
  293. package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
  294. package/dist/modules/billing/lib/prepaidConfig.js +17 -25
  295. package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
  296. package/dist/modules/billing/lib/prepaidConsume.js +26 -7
  297. package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
  298. package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
  299. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
  300. package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
  301. package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
  302. package/dist/modules/billing/lib/seeds/demo.js +51 -3
  303. package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
  304. package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
  305. package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
  306. package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
  307. package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
  308. package/dist/modules/billing/lib/servicePeriods.js +121 -0
  309. package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
  310. package/dist/modules/billing/lib/tenantLock.js.map +1 -1
  311. package/dist/modules/billing/lib/testRunCleanup.js +24 -0
  312. package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
  313. package/dist/modules/billing/lib/topupCapture.js +2 -2
  314. package/dist/modules/billing/lib/topupCapture.js.map +2 -2
  315. package/dist/modules/billing/lib/topupReceipt.js +58 -79
  316. package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
  317. package/dist/modules/billing/lib/usageRater.js.map +1 -1
  318. package/dist/modules/billing/lib/usageRunner.js +14 -6
  319. package/dist/modules/billing/lib/usageRunner.js.map +2 -2
  320. package/dist/modules/billing/lib/vat.js +39 -0
  321. package/dist/modules/billing/lib/vat.js.map +7 -0
  322. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
  323. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
  324. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
  325. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
  326. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
  327. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
  328. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
  329. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
  330. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
  331. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
  332. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
  333. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
  334. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
  335. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
  336. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
  337. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
  338. package/dist/modules/billing/setup.js +7 -184
  339. package/dist/modules/billing/setup.js.map +2 -2
  340. package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
  341. package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
  342. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
  343. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
  344. package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
  345. package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
  346. package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
  347. package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
  348. package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
  349. package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
  350. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
  351. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
  352. package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
  353. package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
  354. package/dist/modules/billing/workers/reap-stale-bill-runs.js +2 -2
  355. package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +2 -2
  356. package/generated/entities/billing_account/index.ts +26 -0
  357. package/generated/entities/billing_account_balance/index.ts +13 -0
  358. package/generated/entities/billing_account_transaction/index.ts +21 -0
  359. package/generated/entities/billing_account_usage/index.ts +18 -0
  360. package/generated/entities/billing_allocation/index.ts +18 -0
  361. package/generated/entities/billing_document_sequence/index.ts +11 -0
  362. package/generated/entities/billing_draft_invoice_edit/index.ts +14 -0
  363. package/generated/entities/billing_invoice/index.ts +38 -0
  364. package/generated/entities/billing_invoice_line/index.ts +24 -0
  365. package/generated/entities/billing_item/index.ts +26 -0
  366. package/generated/entities/billing_payment/index.ts +25 -0
  367. package/generated/entities/billing_payment_import/index.ts +14 -0
  368. package/generated/entities/billing_run/index.ts +20 -0
  369. package/generated/entities/billing_run_outcome/index.ts +18 -0
  370. package/generated/entities/billing_settings/index.ts +26 -0
  371. package/generated/entities/billing_statement/index.ts +22 -0
  372. package/generated/entities/billing_topup/index.ts +20 -0
  373. package/generated/entities.ids.generated.ts +35 -0
  374. package/package.json +15 -10
  375. package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +170 -0
  376. package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
  377. package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
  378. package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
  379. package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
  380. package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
  381. package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
  382. package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
  383. package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
  384. package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
  385. package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
  386. package/src/modules/billing/__tests__/commands/invoices.test.ts +437 -355
  387. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
  388. package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
  389. package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
  390. package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
  391. package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
  392. package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
  393. package/src/modules/billing/__tests__/entityRegistry.test.ts +326 -0
  394. package/src/modules/billing/__tests__/events.test.ts +142 -0
  395. package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
  396. package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
  397. package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
  398. package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
  399. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +231 -20
  400. package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
  401. package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
  402. package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
  403. package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
  404. package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +189 -0
  405. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
  406. package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
  407. package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
  408. package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
  409. package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
  410. package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
  411. package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
  412. package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
  413. package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
  414. package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
  415. package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +199 -0
  416. package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
  417. package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
  418. package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
  419. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
  420. package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
  421. package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
  422. package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
  423. package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
  424. package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
  425. package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
  426. package/src/modules/billing/__tests__/setup.test.ts +13 -4
  427. package/src/modules/billing/__tests__/validators.test.ts +193 -3
  428. package/src/modules/billing/acl.ts +12 -0
  429. package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
  430. package/src/modules/billing/api/accounts/route.ts +58 -4
  431. package/src/modules/billing/api/export/account/[id]/route.ts +20 -18
  432. package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
  433. package/src/modules/billing/api/invoices/[id]/pdf/route.ts +79 -0
  434. package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
  435. package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
  436. package/src/modules/billing/api/invoices/add-line/route.ts +3 -3
  437. package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
  438. package/src/modules/billing/api/invoices/edit-line/route.ts +4 -4
  439. package/src/modules/billing/api/invoices/post/route.ts +7 -6
  440. package/src/modules/billing/api/invoices/remove-line/route.ts +3 -3
  441. package/src/modules/billing/api/invoices/route.ts +81 -86
  442. package/src/modules/billing/api/items/bulk/route.ts +2 -1
  443. package/src/modules/billing/api/items/change-price/route.ts +54 -0
  444. package/src/modules/billing/api/items/route.ts +23 -7
  445. package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
  446. package/src/modules/billing/api/payment-imports/route.ts +57 -0
  447. package/src/modules/billing/api/payments/[id]/route.ts +63 -0
  448. package/src/modules/billing/api/payments/allocate/route.ts +35 -0
  449. package/src/modules/billing/api/payments/assign/route.ts +36 -0
  450. package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
  451. package/src/modules/billing/api/payments/ignore/route.ts +35 -0
  452. package/src/modules/billing/api/payments/import/route.ts +72 -0
  453. package/src/modules/billing/api/payments/restore/route.ts +35 -0
  454. package/src/modules/billing/api/payments/route.ts +147 -0
  455. package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
  456. package/src/modules/billing/api/payments/unassign/route.ts +35 -0
  457. package/src/modules/billing/api/run-outcomes/route.ts +50 -7
  458. package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
  459. package/src/modules/billing/api/runs/[id]/route.ts +59 -0
  460. package/src/modules/billing/api/runs/retry-failed/route.ts +4 -3
  461. package/src/modules/billing/api/runs/route.ts +17 -9
  462. package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
  463. package/src/modules/billing/api/settings/route.ts +116 -0
  464. package/src/modules/billing/api/statements/route.ts +5 -0
  465. package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
  466. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
  467. package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
  468. package/src/modules/billing/backend/billing/accounts/page.tsx +71 -5
  469. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +445 -96
  470. package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
  471. package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
  472. package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
  473. package/src/modules/billing/backend/billing/items/page.tsx +19 -3
  474. package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
  475. package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
  476. package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
  477. package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
  478. package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
  479. package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
  480. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +50 -19
  481. package/src/modules/billing/backend/billing/runs/page.tsx +60 -9
  482. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +87 -6
  483. package/src/modules/billing/backend/billing/statements/page.tsx +2 -2
  484. package/src/modules/billing/backend/billing/topups/page.tsx +2 -2
  485. package/src/modules/billing/backend/billing/transactions/page.tsx +2 -2
  486. package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
  487. package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
  488. package/src/modules/billing/backend/config/billing/page.tsx +549 -0
  489. package/src/modules/billing/ce.ts +8 -7
  490. package/src/modules/billing/cli.ts +31 -1
  491. package/src/modules/billing/commands/accounts.ts +6 -0
  492. package/src/modules/billing/commands/index.ts +3 -0
  493. package/src/modules/billing/commands/invoices.ts +371 -238
  494. package/src/modules/billing/commands/items.ts +143 -0
  495. package/src/modules/billing/commands/memos.ts +218 -0
  496. package/src/modules/billing/commands/payments.ts +428 -0
  497. package/src/modules/billing/commands/runs.ts +208 -6
  498. package/src/modules/billing/commands/settings.ts +77 -0
  499. package/src/modules/billing/commands/topups.ts +12 -3
  500. package/src/modules/billing/commands/transactions.ts +2 -2
  501. package/src/modules/billing/components/AccountForm.tsx +199 -53
  502. package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
  503. package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
  504. package/src/modules/billing/components/AccountPicker.tsx +70 -0
  505. package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
  506. package/src/modules/billing/components/BillingDataTable.tsx +34 -0
  507. package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
  508. package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
  509. package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
  510. package/src/modules/billing/components/CustomerLabel.tsx +53 -0
  511. package/src/modules/billing/components/InvoicesTable.tsx +344 -0
  512. package/src/modules/billing/components/ItemForm.tsx +233 -44
  513. package/src/modules/billing/components/ItemPriceField.tsx +81 -0
  514. package/src/modules/billing/components/LineFormDialog.tsx +25 -1
  515. package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
  516. package/src/modules/billing/components/MoneyInput.tsx +26 -0
  517. package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
  518. package/src/modules/billing/components/PaymentsTable.tsx +213 -0
  519. package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
  520. package/src/modules/billing/components/PrepaidPanel.tsx +20 -7
  521. package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
  522. package/src/modules/billing/components/TestRunActions.tsx +152 -0
  523. package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
  524. package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
  525. package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
  526. package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
  527. package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
  528. package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
  529. package/src/modules/billing/components/account/AccountView.tsx +101 -0
  530. package/src/modules/billing/components/account/accountRow.ts +70 -0
  531. package/src/modules/billing/components/customerNames.ts +62 -0
  532. package/src/modules/billing/data/entities.ts +685 -15
  533. package/src/modules/billing/data/entityIds.ts +25 -11
  534. package/src/modules/billing/data/validators.ts +388 -8
  535. package/src/modules/billing/events.ts +82 -3
  536. package/src/modules/billing/i18n/en.json +457 -21
  537. package/src/modules/billing/i18n/pl.json +457 -21
  538. package/src/modules/billing/index.ts +6 -2
  539. package/src/modules/billing/lib/accountFinancials.ts +84 -0
  540. package/src/modules/billing/lib/accountLock.ts +32 -0
  541. package/src/modules/billing/lib/accountStatus.ts +11 -9
  542. package/src/modules/billing/lib/accountSummary.ts +172 -0
  543. package/src/modules/billing/lib/allocation.ts +515 -0
  544. package/src/modules/billing/lib/allocationEvents.ts +26 -0
  545. package/src/modules/billing/lib/billPeriod.ts +19 -0
  546. package/src/modules/billing/lib/billRunEngine.ts +362 -162
  547. package/src/modules/billing/lib/billRunLabel.ts +1 -1
  548. package/src/modules/billing/lib/billingSettings.ts +145 -0
  549. package/src/modules/billing/lib/commandRoute.ts +112 -0
  550. package/src/modules/billing/lib/customerLookup.ts +157 -0
  551. package/src/modules/billing/lib/dateOnly.ts +22 -0
  552. package/src/modules/billing/lib/documentParties.ts +23 -0
  553. package/src/modules/billing/lib/documentTypes.ts +37 -0
  554. package/src/modules/billing/lib/dueDate.ts +26 -0
  555. package/src/modules/billing/lib/iban.ts +36 -0
  556. package/src/modules/billing/lib/invoiceDelivery.ts +197 -0
  557. package/src/modules/billing/lib/invoiceLanguages.ts +34 -0
  558. package/src/modules/billing/lib/invoiceWriter.ts +162 -148
  559. package/src/modules/billing/lib/itemBillingState.ts +67 -0
  560. package/src/modules/billing/lib/itemPriceChange.ts +56 -0
  561. package/src/modules/billing/lib/itemSelector.ts +4 -1
  562. package/src/modules/billing/lib/lineAmounts.ts +61 -0
  563. package/src/modules/billing/lib/memoGuards.ts +41 -0
  564. package/src/modules/billing/lib/navigation.ts +64 -0
  565. package/src/modules/billing/lib/numbering.ts +174 -0
  566. package/src/modules/billing/lib/overdue.ts +60 -0
  567. package/src/modules/billing/lib/paymentCsv.ts +171 -0
  568. package/src/modules/billing/lib/paymentGateway.ts +52 -0
  569. package/src/modules/billing/lib/paymentImport.ts +228 -0
  570. package/src/modules/billing/lib/paymentMatcher.ts +88 -0
  571. package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
  572. package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
  573. package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
  574. package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
  575. package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
  576. package/src/modules/billing/lib/pdf/invoiceModel.ts +328 -0
  577. package/src/modules/billing/lib/pdf/invoicePdf.ts +216 -0
  578. package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
  579. package/src/modules/billing/lib/prepaidConfig.ts +27 -36
  580. package/src/modules/billing/lib/prepaidConsume.ts +29 -6
  581. package/src/modules/billing/lib/prepaidPeriodClose.ts +410 -242
  582. package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
  583. package/src/modules/billing/lib/seeds/demo.ts +79 -6
  584. package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
  585. package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
  586. package/src/modules/billing/lib/servicePeriods.ts +226 -0
  587. package/src/modules/billing/lib/tenantLock.ts +1 -1
  588. package/src/modules/billing/lib/testRunCleanup.ts +42 -0
  589. package/src/modules/billing/lib/topupCapture.ts +3 -4
  590. package/src/modules/billing/lib/topupReceipt.ts +70 -103
  591. package/src/modules/billing/lib/usageRater.ts +1 -1
  592. package/src/modules/billing/lib/usageRunner.ts +17 -5
  593. package/src/modules/billing/lib/vat.ts +56 -0
  594. package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
  595. package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
  596. package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
  597. package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
  598. package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
  599. package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
  600. package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
  601. package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
  602. package/src/modules/billing/setup.ts +11 -237
  603. package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
  604. package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
  605. package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
  606. package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
  607. package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
  608. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
  609. package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
  610. package/src/modules/billing/workers/reap-stale-bill-runs.ts +4 -4
  611. package/dist/modules/billing/lib/invoiceStatus.js +0 -38
  612. package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
  613. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
  614. package/src/modules/billing/lib/invoiceStatus.ts +0 -61
@@ -0,0 +1,515 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
+ import { BillingAllocation } from '../data/entities'
4
+ import type { AllocatedBy, AllocationSourceType } from '../data/validators'
5
+
6
+ /**
7
+ * Allocation service (Spec C; generalised for Spec D credit memos).
8
+ *
9
+ * An allocation moves money from a SOURCE (`payment` → `billing_payments`, or
10
+ * `credit_memo` → a posted credit memo in `billing_invoices`) to a receivable
11
+ * (`invoice` / `debit_memo`). Rules:
12
+ *
13
+ * - Receivable amounts are RECOMPUTED from Σ active allocations, never
14
+ * incremented: `paid_total` (payment sources), `credited_total` (credit
15
+ * memo sources), `outstanding = gross − paid − credited`, status
16
+ * `posted | partially_paid | paid | credited` (P-D2, Spec D P-D3).
17
+ * - A source's unallocated amount (payment `unallocated_amount`, credit memo
18
+ * `outstanding`) is decremented with a guarded UPDATE, so it can never go
19
+ * negative even if a caller forgets the account lock.
20
+ * - Callers hold `withAccountLock` (P-D3); every function here takes the
21
+ * transactional EntityManager of that lock.
22
+ * - Unallocated money of an account is ACCOUNT CREDIT; `applyAccountCredit`
23
+ * spends it on open receivables, oldest due date first (D12, P-D5).
24
+ *
25
+ * Money is handled in integer cents internally (2 dp documents).
26
+ */
27
+
28
+ // ─── Pure helpers ────────────────────────────────────────────────
29
+
30
+ export function toCents(value: string | number | null | undefined): number {
31
+ const n = typeof value === 'number' ? value : Number.parseFloat(value ?? '0')
32
+ return Number.isFinite(n) ? Math.round(n * 100) : 0
33
+ }
34
+
35
+ export function fromCents(cents: number): string {
36
+ return (cents / 100).toFixed(4)
37
+ }
38
+
39
+ export type OpenReceivable = {
40
+ id: string
41
+ outstandingCents: number
42
+ dueDate: string | null
43
+ issueDate: string | null
44
+ number: string
45
+ }
46
+
47
+ /** Oldest due date first (nulls last), then issue date, then number. */
48
+ export function sortOpenReceivables<T extends Omit<OpenReceivable, 'outstandingCents'>>(rows: T[]): T[] {
49
+ return [...rows].sort((a, b) => {
50
+ if (a.dueDate !== b.dueDate) {
51
+ if (!a.dueDate) return 1
52
+ if (!b.dueDate) return -1
53
+ return a.dueDate < b.dueDate ? -1 : 1
54
+ }
55
+ if (a.issueDate !== b.issueDate) {
56
+ if (!a.issueDate) return 1
57
+ if (!b.issueDate) return -1
58
+ return a.issueDate < b.issueDate ? -1 : 1
59
+ }
60
+ return a.number.localeCompare(b.number)
61
+ })
62
+ }
63
+
64
+ /** Spreads `amountCents` over receivables in the given order, never above outstanding. */
65
+ export function planAllocation(
66
+ amountCents: number,
67
+ receivables: ReadonlyArray<Pick<OpenReceivable, 'id' | 'outstandingCents'>>,
68
+ ): { allocations: Array<{ invoiceId: string; amountCents: number }>; remainderCents: number } {
69
+ let remaining = Math.max(0, amountCents)
70
+ const allocations: Array<{ invoiceId: string; amountCents: number }> = []
71
+ for (const receivable of receivables) {
72
+ if (remaining <= 0) break
73
+ const take = Math.min(remaining, Math.max(0, receivable.outstandingCents))
74
+ if (take <= 0) continue
75
+ allocations.push({ invoiceId: receivable.id, amountCents: take })
76
+ remaining -= take
77
+ }
78
+ return { allocations, remainderCents: remaining }
79
+ }
80
+
81
+ export type ReceivableState = {
82
+ status: string
83
+ totalGrossCents: number
84
+ paidCents: number
85
+ creditedCents: number
86
+ }
87
+
88
+ /** Paid / credited / outstanding / status of a receivable from its allocation sums. */
89
+ export function recomputeReceivable(state: ReceivableState): {
90
+ paidCents: number
91
+ creditedCents: number
92
+ outstandingCents: number
93
+ status: string
94
+ } {
95
+ const { totalGrossCents: gross, paidCents: paid, creditedCents: credited } = state
96
+ const outstandingCents = Math.max(0, gross - paid - credited)
97
+ if (state.status === 'draft') return { paidCents: paid, creditedCents: credited, outstandingCents, status: 'draft' }
98
+ let status: string
99
+ if (gross > 0 && outstandingCents === 0) status = credited >= gross ? 'credited' : 'paid'
100
+ else if (paid + credited > 0) status = 'partially_paid'
101
+ else status = 'posted'
102
+ return { paidCents: paid, creditedCents: credited, outstandingCents, status }
103
+ }
104
+
105
+ /** Payment status from its allocation state (terminal / unmatched states are kept). */
106
+ export function paymentStatusFor(payment: {
107
+ status: string
108
+ billAccountId: string | null | undefined
109
+ amountCents: number
110
+ unallocatedCents: number
111
+ }): string {
112
+ if (payment.status === 'ignored' || payment.status === 'converted_to_topup') return payment.status
113
+ if (!payment.billAccountId) return 'unmatched'
114
+ if (payment.unallocatedCents <= 0) return 'allocated'
115
+ if (payment.unallocatedCents < payment.amountCents) return 'partially_allocated'
116
+ return 'matched'
117
+ }
118
+
119
+ // ─── Effects (emitted by callers after commit) ───────────────────
120
+
121
+ export type InvoiceStatusChange = { invoiceId: string; from: string; to: string }
122
+
123
+ export type AllocationEffects = {
124
+ created: Array<{ allocationId: string; sourceType: AllocationSourceType; sourceId: string; invoiceId: string; amount: string }>
125
+ removed: Array<{ allocationId: string; sourceType: AllocationSourceType; sourceId: string; invoiceId: string; amount: string }>
126
+ invoiceStatusChanges: InvoiceStatusChange[]
127
+ }
128
+
129
+ export function emptyEffects(): AllocationEffects {
130
+ return { created: [], removed: [], invoiceStatusChanges: [] }
131
+ }
132
+
133
+ export function mergeEffects(target: AllocationEffects, extra: AllocationEffects): AllocationEffects {
134
+ target.created.push(...extra.created)
135
+ target.removed.push(...extra.removed)
136
+ target.invoiceStatusChanges.push(...extra.invoiceStatusChanges)
137
+ return target
138
+ }
139
+
140
+ // ─── DB service (inside withAccountLock) ─────────────────────────
141
+
142
+ export type ArScope = { tenantId: string; organizationId: string }
143
+
144
+ export type AllocationSource = { type: AllocationSourceType; id: string }
145
+
146
+ type ReceivableRow = {
147
+ id: string
148
+ bill_account_id: string
149
+ currency_code: string
150
+ status: string
151
+ document_type: string
152
+ test_run: boolean
153
+ total_gross: string
154
+ outstanding: string
155
+ due_date: string | null
156
+ issue_date: string | null
157
+ number: string
158
+ }
159
+
160
+ type SourceRow = {
161
+ id: string
162
+ bill_account_id: string | null
163
+ currency_code: string
164
+ unallocated: string
165
+ status: string
166
+ }
167
+
168
+ const RECEIVABLE_TYPES = ['invoice', 'debit_memo']
169
+ const OPEN_STATUSES = ['posted', 'partially_paid']
170
+
171
+ function isoDay(value: unknown): string | null {
172
+ if (!value) return null
173
+ if (value instanceof Date) return value.toISOString().slice(0, 10)
174
+ return String(value).slice(0, 10)
175
+ }
176
+
177
+ async function loadReceivable(tem: EntityManager, scope: ArScope, invoiceId: string): Promise<ReceivableRow | null> {
178
+ const rows = (await tem.execute(
179
+ `SELECT id, bill_account_id, currency_code, status, document_type, test_run, total_gross, outstanding,
180
+ due_date, issue_date, number
181
+ FROM billing_invoices
182
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`,
183
+ [invoiceId, scope.tenantId, scope.organizationId],
184
+ )) as unknown as ReceivableRow[]
185
+ return rows?.[0] ?? null
186
+ }
187
+
188
+ async function loadSource(tem: EntityManager, scope: ArScope, source: AllocationSource): Promise<SourceRow | null> {
189
+ const sql =
190
+ source.type === 'payment'
191
+ ? `SELECT id, bill_account_id, currency_code, unallocated_amount AS unallocated, status
192
+ FROM billing_payments
193
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`
194
+ : `SELECT id, bill_account_id, currency_code, outstanding AS unallocated, status
195
+ FROM billing_invoices
196
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL
197
+ AND document_type = 'credit_memo'`
198
+ const rows = (await tem.execute(sql, [source.id, scope.tenantId, scope.organizationId])) as unknown as SourceRow[]
199
+ return rows?.[0] ?? null
200
+ }
201
+
202
+ /** Guarded decrement (negative `deltaCents` restores). 0 rows → insufficient. */
203
+ async function adjustSourceUnallocated(
204
+ tem: EntityManager,
205
+ scope: ArScope,
206
+ source: AllocationSource,
207
+ deltaCents: number,
208
+ ): Promise<boolean> {
209
+ const amount = fromCents(Math.abs(deltaCents))
210
+ if (source.type === 'payment') {
211
+ const rows = (await tem.execute(
212
+ deltaCents >= 0
213
+ ? `UPDATE billing_payments
214
+ SET unallocated_amount = unallocated_amount - ?::numeric, updated_at = now()
215
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND unallocated_amount >= ?::numeric
216
+ RETURNING id`
217
+ : `UPDATE billing_payments
218
+ SET unallocated_amount = LEAST(amount, unallocated_amount + ?::numeric), updated_at = now()
219
+ WHERE id = ? AND tenant_id = ? AND organization_id = ?
220
+ RETURNING id`,
221
+ deltaCents >= 0
222
+ ? [amount, source.id, scope.tenantId, scope.organizationId, amount]
223
+ : [amount, source.id, scope.tenantId, scope.organizationId],
224
+ )) as unknown as Array<{ id: string }>
225
+ if (!rows?.length) return false
226
+ await tem.execute(
227
+ `UPDATE billing_payments
228
+ SET status = CASE
229
+ WHEN unallocated_amount <= 0 THEN 'allocated'
230
+ WHEN unallocated_amount < amount THEN 'partially_allocated'
231
+ ELSE 'matched' END
232
+ WHERE id = ? AND status IN ('matched', 'partially_allocated', 'allocated')`,
233
+ [source.id],
234
+ )
235
+ return true
236
+ }
237
+ // Credit memo: its `outstanding` is the unallocated credit (Spec D, P-D5).
238
+ const rows = (await tem.execute(
239
+ deltaCents >= 0
240
+ ? `UPDATE billing_invoices
241
+ SET outstanding = outstanding - ?::numeric,
242
+ status = CASE WHEN outstanding - ?::numeric <= 0 THEN 'paid' ELSE 'posted' END,
243
+ updated_at = now()
244
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND document_type = 'credit_memo'
245
+ AND outstanding >= ?::numeric
246
+ RETURNING id`
247
+ : `UPDATE billing_invoices
248
+ SET outstanding = LEAST(total_gross, outstanding + ?::numeric), status = 'posted', updated_at = now()
249
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND document_type = 'credit_memo'
250
+ RETURNING id`,
251
+ deltaCents >= 0
252
+ ? [amount, amount, source.id, scope.tenantId, scope.organizationId, amount]
253
+ : [amount, source.id, scope.tenantId, scope.organizationId],
254
+ )) as unknown as Array<{ id: string }>
255
+ return Boolean(rows?.length)
256
+ }
257
+
258
+ /** Recomputes paid / credited / outstanding / status of a receivable from its active allocations. */
259
+ export async function recomputeInvoice(
260
+ tem: EntityManager,
261
+ scope: ArScope,
262
+ invoiceId: string,
263
+ ): Promise<InvoiceStatusChange | null> {
264
+ const invoice = await loadReceivable(tem, scope, invoiceId)
265
+ if (!invoice || !RECEIVABLE_TYPES.includes(invoice.document_type)) return null
266
+ const sums = (await tem.execute(
267
+ `SELECT COALESCE(SUM(amount) FILTER (WHERE source_type = 'payment'), 0) AS paid,
268
+ COALESCE(SUM(amount) FILTER (WHERE source_type = 'credit_memo'), 0) AS credited
269
+ FROM billing_allocations
270
+ WHERE invoice_id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL`,
271
+ [invoiceId, scope.tenantId, scope.organizationId],
272
+ )) as unknown as Array<{ paid: string; credited: string }>
273
+ const next = recomputeReceivable({
274
+ status: invoice.status,
275
+ totalGrossCents: toCents(invoice.total_gross),
276
+ paidCents: toCents(sums?.[0]?.paid),
277
+ creditedCents: toCents(sums?.[0]?.credited),
278
+ })
279
+ await tem.execute(
280
+ `UPDATE billing_invoices
281
+ SET paid_total = ?::numeric, credited_total = ?::numeric, outstanding = ?::numeric, status = ?, updated_at = now()
282
+ WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
283
+ [
284
+ fromCents(next.paidCents),
285
+ fromCents(next.creditedCents),
286
+ fromCents(next.outstandingCents),
287
+ next.status,
288
+ invoiceId,
289
+ scope.tenantId,
290
+ scope.organizationId,
291
+ ],
292
+ )
293
+ return next.status !== invoice.status ? { invoiceId, from: invoice.status, to: next.status } : null
294
+ }
295
+
296
+ export async function allocate(
297
+ tem: EntityManager,
298
+ scope: ArScope,
299
+ params: { source: AllocationSource; invoiceId: string; amountCents: number; by: AllocatedBy; userId?: string | null },
300
+ ): Promise<AllocationEffects> {
301
+ const effects = emptyEffects()
302
+ if (params.amountCents <= 0) {
303
+ throw new CrudHttpError(400, { error: 'Allocation amount must be positive', code: 'billing.allocation.invalid_amount' })
304
+ }
305
+ const [source, invoice] = await Promise.all([
306
+ loadSource(tem, scope, params.source),
307
+ loadReceivable(tem, scope, params.invoiceId),
308
+ ])
309
+ if (!source) throw new CrudHttpError(404, { error: 'Payment source not found', code: 'billing.allocation.source_missing' })
310
+ if (!invoice) throw new CrudHttpError(404, { error: 'Invoice not found', code: 'billing.allocation.invoice_missing' })
311
+ if (!source.bill_account_id || source.bill_account_id !== invoice.bill_account_id) {
312
+ throw new CrudHttpError(409, {
313
+ error: 'Payment and invoice belong to different accounts',
314
+ code: 'billing.allocation.account_mismatch',
315
+ })
316
+ }
317
+ if (source.currency_code !== invoice.currency_code) {
318
+ throw new CrudHttpError(409, { error: 'Currencies differ', code: 'billing.allocation.currency_mismatch' })
319
+ }
320
+ if (
321
+ invoice.test_run ||
322
+ !RECEIVABLE_TYPES.includes(invoice.document_type) ||
323
+ !OPEN_STATUSES.includes(invoice.status)
324
+ ) {
325
+ throw new CrudHttpError(409, { error: 'Invoice is not open for payment', code: 'billing.allocation.invoice_not_open' })
326
+ }
327
+ if (params.amountCents > toCents(invoice.outstanding)) {
328
+ throw new CrudHttpError(409, {
329
+ error: 'Allocation exceeds the invoice outstanding amount',
330
+ code: 'billing.allocation.exceeds_outstanding',
331
+ })
332
+ }
333
+ const decremented = await adjustSourceUnallocated(tem, scope, params.source, params.amountCents)
334
+ if (!decremented) {
335
+ throw new CrudHttpError(409, {
336
+ error: 'Not enough unallocated amount',
337
+ code: 'billing.allocation.insufficient_unallocated',
338
+ })
339
+ }
340
+ const now = new Date()
341
+ const row = tem.create(BillingAllocation, {
342
+ organizationId: scope.organizationId,
343
+ tenantId: scope.tenantId,
344
+ sourceType: params.source.type,
345
+ sourceId: params.source.id,
346
+ invoiceId: params.invoiceId,
347
+ billAccountId: invoice.bill_account_id,
348
+ amount: fromCents(params.amountCents),
349
+ currencyCode: invoice.currency_code,
350
+ allocatedBy: params.by,
351
+ userId: params.userId ?? null,
352
+ createdAt: now,
353
+ updatedAt: now,
354
+ })
355
+ tem.persist(row)
356
+ await tem.flush()
357
+ effects.created.push({
358
+ allocationId: row.id,
359
+ sourceType: params.source.type,
360
+ sourceId: params.source.id,
361
+ invoiceId: params.invoiceId,
362
+ amount: row.amount,
363
+ })
364
+ const change = await recomputeInvoice(tem, scope, params.invoiceId)
365
+ if (change) effects.invoiceStatusChanges.push(change)
366
+ return effects
367
+ }
368
+
369
+ export async function unallocate(
370
+ tem: EntityManager,
371
+ scope: ArScope,
372
+ params: { allocationId: string; userId?: string | null },
373
+ ): Promise<AllocationEffects> {
374
+ const effects = emptyEffects()
375
+ const rows = (await tem.execute(
376
+ `UPDATE billing_allocations
377
+ SET deleted_at = now(), updated_at = now(), deallocated_by_user_id = ?
378
+ WHERE id = ? AND tenant_id = ? AND organization_id = ? AND deleted_at IS NULL
379
+ RETURNING id, source_type, source_id, invoice_id, amount`,
380
+ [params.userId ?? null, params.allocationId, scope.tenantId, scope.organizationId],
381
+ )) as unknown as Array<{ id: string; source_type: AllocationSourceType; source_id: string; invoice_id: string; amount: string }>
382
+ const removed = rows?.[0]
383
+ if (!removed) throw new CrudHttpError(404, { error: 'Allocation not found', code: 'billing.allocation.not_found' })
384
+ await adjustSourceUnallocated(tem, scope, { type: removed.source_type, id: removed.source_id }, -toCents(removed.amount))
385
+ effects.removed.push({
386
+ allocationId: removed.id,
387
+ sourceType: removed.source_type,
388
+ sourceId: removed.source_id,
389
+ invoiceId: removed.invoice_id,
390
+ amount: removed.amount,
391
+ })
392
+ const change = await recomputeInvoice(tem, scope, removed.invoice_id)
393
+ if (change) effects.invoiceStatusChanges.push(change)
394
+ return effects
395
+ }
396
+
397
+ /** Open receivables of an account in auto-apply order. */
398
+ export async function loadOpenReceivables(
399
+ tem: EntityManager,
400
+ scope: ArScope,
401
+ account: { id: string; currencyCode: string },
402
+ onlyIds?: string[],
403
+ ): Promise<OpenReceivable[]> {
404
+ const params: unknown[] = [scope.tenantId, scope.organizationId, account.id, account.currencyCode]
405
+ let idFilter = ''
406
+ if (onlyIds && onlyIds.length > 0) {
407
+ idFilter = ` AND id = ANY(?::uuid[])`
408
+ params.push(`{${onlyIds.join(',')}}`)
409
+ }
410
+ const rows = (await tem.execute(
411
+ `SELECT id, outstanding, due_date, issue_date, number
412
+ FROM billing_invoices
413
+ WHERE tenant_id = ? AND organization_id = ? AND bill_account_id = ? AND currency_code = ?
414
+ AND deleted_at IS NULL AND test_run = false
415
+ AND document_type IN ('invoice', 'debit_memo')
416
+ AND status IN ('posted', 'partially_paid')
417
+ AND outstanding > 0${idFilter}`,
418
+ params,
419
+ )) as unknown as Array<{ id: string; outstanding: string; due_date: unknown; issue_date: unknown; number: string }>
420
+ return sortOpenReceivables(
421
+ (rows ?? []).map((row) => ({
422
+ id: row.id,
423
+ outstandingCents: toCents(row.outstanding),
424
+ dueDate: isoDay(row.due_date),
425
+ issueDate: isoDay(row.issue_date),
426
+ number: row.number,
427
+ })),
428
+ )
429
+ }
430
+
431
+ /** Credit sources of an account (unallocated payments + open credit memos), oldest first. */
432
+ export async function loadCreditSources(
433
+ tem: EntityManager,
434
+ scope: ArScope,
435
+ account: { id: string; currencyCode: string },
436
+ ): Promise<Array<{ source: AllocationSource; availableCents: number }>> {
437
+ const rows = (await tem.execute(
438
+ `SELECT 'payment' AS source_type, id, unallocated_amount AS available, received_at AS at, created_at
439
+ FROM billing_payments
440
+ WHERE tenant_id = ? AND organization_id = ? AND bill_account_id = ? AND currency_code = ?
441
+ AND deleted_at IS NULL AND status IN ('matched', 'partially_allocated') AND unallocated_amount > 0
442
+ UNION ALL
443
+ SELECT 'credit_memo' AS source_type, id, outstanding AS available, issue_date AS at, created_at
444
+ FROM billing_invoices
445
+ WHERE tenant_id = ? AND organization_id = ? AND bill_account_id = ? AND currency_code = ?
446
+ AND deleted_at IS NULL AND test_run = false AND document_type = 'credit_memo'
447
+ AND status = 'posted' AND outstanding > 0
448
+ ORDER BY at ASC, created_at ASC`,
449
+ [
450
+ scope.tenantId,
451
+ scope.organizationId,
452
+ account.id,
453
+ account.currencyCode,
454
+ scope.tenantId,
455
+ scope.organizationId,
456
+ account.id,
457
+ account.currencyCode,
458
+ ],
459
+ )) as unknown as Array<{ source_type: AllocationSourceType; id: string; available: string }>
460
+ return (rows ?? []).map((row) => ({
461
+ source: { type: row.source_type, id: row.id },
462
+ availableCents: toCents(row.available),
463
+ }))
464
+ }
465
+
466
+ /**
467
+ * Spends the account's credit on its open receivables (D12): sources oldest
468
+ * first, receivables oldest due first. `onlySources` restricts the spend
469
+ * (e.g. the payment just imported); default = all credit of the account.
470
+ */
471
+ export async function applyAccountCredit(
472
+ tem: EntityManager,
473
+ scope: ArScope,
474
+ account: { id: string; currencyCode: string },
475
+ opts: { onlySources?: AllocationSource[]; by?: AllocatedBy } = {},
476
+ ): Promise<AllocationEffects> {
477
+ const effects = emptyEffects()
478
+ let sources = await loadCreditSources(tem, scope, account)
479
+ if (opts.onlySources) {
480
+ const wanted = new Set(opts.onlySources.map((s) => `${s.type}:${s.id}`))
481
+ sources = sources.filter((s) => wanted.has(`${s.source.type}:${s.source.id}`))
482
+ }
483
+ if (sources.length === 0) return effects
484
+ const receivables = await loadOpenReceivables(tem, scope, account)
485
+ for (const credit of sources) {
486
+ let available = credit.availableCents
487
+ for (const receivable of receivables) {
488
+ if (available <= 0) break
489
+ const take = Math.min(available, receivable.outstandingCents)
490
+ if (take <= 0) continue
491
+ mergeEffects(
492
+ effects,
493
+ await allocate(tem, scope, {
494
+ source: credit.source,
495
+ invoiceId: receivable.id,
496
+ amountCents: take,
497
+ by: opts.by ?? 'credit',
498
+ }),
499
+ )
500
+ receivable.outstandingCents -= take
501
+ available -= take
502
+ }
503
+ }
504
+ return effects
505
+ }
506
+
507
+ /** Unapplied credit of an account (Σ unallocated payments + open credit memos). */
508
+ export async function getAccountCredit(
509
+ em: EntityManager,
510
+ scope: ArScope,
511
+ account: { id: string; currencyCode: string },
512
+ ): Promise<string> {
513
+ const sources = await loadCreditSources(em, scope, account)
514
+ return fromCents(sources.reduce((sum, s) => sum + s.availableCents, 0))
515
+ }
@@ -0,0 +1,26 @@
1
+ import { emitBillingEvent } from '../events'
2
+ import type { AllocationEffects, ArScope } from './allocation'
3
+
4
+ /**
5
+ * Emits the events of an allocation change AFTER the transaction committed
6
+ * (Spec C): one `billing.payment.allocated` / `unallocated` per payment
7
+ * allocation row, and `billing.invoice.paid` / `partially_paid` / `credited` (Spec D) per receivable whose status changed.
8
+ */
9
+ export async function emitAllocationEffects(scope: ArScope, effects: AllocationEffects): Promise<void> {
10
+ for (const row of effects.created) {
11
+ if (row.sourceType !== 'payment') continue
12
+ await emitBillingEvent('billing.payment.allocated', { ...scope, paymentId: row.sourceId, invoiceId: row.invoiceId, allocationId: row.allocationId, amount: row.amount })
13
+ }
14
+ for (const row of effects.removed) {
15
+ if (row.sourceType !== 'payment') continue
16
+ await emitBillingEvent('billing.payment.unallocated', { ...scope, paymentId: row.sourceId, invoiceId: row.invoiceId, allocationId: row.allocationId, amount: row.amount })
17
+ }
18
+ // Last transition per invoice wins (several allocations can touch one invoice).
19
+ const finalStatus = new Map<string, string>()
20
+ for (const change of effects.invoiceStatusChanges) finalStatus.set(change.invoiceId, change.to)
21
+ for (const [invoiceId, status] of finalStatus) {
22
+ if (status === 'paid') await emitBillingEvent('billing.invoice.paid', { ...scope, invoiceId })
23
+ else if (status === 'partially_paid') await emitBillingEvent('billing.invoice.partially_paid', { ...scope, invoiceId })
24
+ else if (status === 'credited') await emitBillingEvent('billing.invoice.credited', { ...scope, invoiceId })
25
+ }
26
+ }
@@ -173,6 +173,25 @@ export function isCycleDue(nextBillDate: Date, asOfDate: Date): boolean {
173
173
  return a <= b
174
174
  }
175
175
 
176
+ /**
177
+ * The cycle boundary `k` cycles away from `anchor` (k may be negative),
178
+ * computed directly from the anchor — never by chaining single steps — so
179
+ * month-end clamping does not drift (Jan 31 → Feb 28 → Mar 31, not Mar 28).
180
+ */
181
+ export function shiftCycles(anchor: Date | string | number, cycle: BillCycle, k: number): Date {
182
+ const base = cloneAtMidnight(anchor)
183
+ switch (cycle) {
184
+ case 'weekly':
185
+ return addDays(base, 7 * k)
186
+ case 'monthly':
187
+ return shiftMonths(base, k)
188
+ case 'quarterly':
189
+ return shiftMonths(base, 3 * k)
190
+ case 'annually':
191
+ return shiftYears(base, k)
192
+ }
193
+ }
194
+
176
195
  /**
177
196
  * Test seam: re-export the day-add helper so the engine can compute
178
197
  * `bill_period_end + 1 day` consistently with this module's calendar