@dainamite/billing 0.20.0 → 0.22.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 (604) hide show
  1. package/CHANGELOG.md +103 -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 +47 -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 +70 -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/de.json +880 -0
  217. package/dist/modules/billing/i18n/en.json +457 -21
  218. package/dist/modules/billing/i18n/es.json +880 -0
  219. package/dist/modules/billing/i18n/pl.json +458 -22
  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 +237 -102
  237. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  238. package/dist/modules/billing/lib/billingSettings.js +116 -0
  239. package/dist/modules/billing/lib/billingSettings.js.map +7 -0
  240. package/dist/modules/billing/lib/commandRoute.js +81 -0
  241. package/dist/modules/billing/lib/commandRoute.js.map +7 -0
  242. package/dist/modules/billing/lib/customerLookup.js +97 -0
  243. package/dist/modules/billing/lib/customerLookup.js.map +7 -0
  244. package/dist/modules/billing/lib/dateOnly.js +13 -0
  245. package/dist/modules/billing/lib/dateOnly.js.map +7 -0
  246. package/dist/modules/billing/lib/documentParties.js +16 -0
  247. package/dist/modules/billing/lib/documentParties.js.map +7 -0
  248. package/dist/modules/billing/lib/documentTypes.js +27 -0
  249. package/dist/modules/billing/lib/documentTypes.js.map +7 -0
  250. package/dist/modules/billing/lib/dueDate.js +18 -0
  251. package/dist/modules/billing/lib/dueDate.js.map +7 -0
  252. package/dist/modules/billing/lib/iban.js +29 -0
  253. package/dist/modules/billing/lib/iban.js.map +7 -0
  254. package/dist/modules/billing/lib/invoiceDelivery.js +133 -0
  255. package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
  256. package/dist/modules/billing/lib/invoiceLanguages.js +28 -0
  257. package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
  258. package/dist/modules/billing/lib/invoiceWriter.js +107 -99
  259. package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
  260. package/dist/modules/billing/lib/itemBillingState.js +27 -0
  261. package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
  262. package/dist/modules/billing/lib/itemPriceChange.js +18 -0
  263. package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
  264. package/dist/modules/billing/lib/itemSelector.js.map +2 -2
  265. package/dist/modules/billing/lib/lineAmounts.js +32 -0
  266. package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
  267. package/dist/modules/billing/lib/memoGuards.js +25 -0
  268. package/dist/modules/billing/lib/memoGuards.js.map +7 -0
  269. package/dist/modules/billing/lib/navigation.js +48 -0
  270. package/dist/modules/billing/lib/navigation.js.map +7 -0
  271. package/dist/modules/billing/lib/numbering.js +120 -0
  272. package/dist/modules/billing/lib/numbering.js.map +7 -0
  273. package/dist/modules/billing/lib/overdue.js +36 -0
  274. package/dist/modules/billing/lib/overdue.js.map +7 -0
  275. package/dist/modules/billing/lib/paymentCsv.js +125 -0
  276. package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
  277. package/dist/modules/billing/lib/paymentGateway.js +15 -0
  278. package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
  279. package/dist/modules/billing/lib/paymentImport.js +164 -0
  280. package/dist/modules/billing/lib/paymentImport.js.map +7 -0
  281. package/dist/modules/billing/lib/paymentMatcher.js +36 -0
  282. package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
  283. package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
  284. package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
  285. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
  286. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
  287. package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
  288. package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
  289. package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
  290. package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
  291. package/dist/modules/billing/lib/pdf/invoiceTemplate.js +276 -0
  292. package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
  293. package/dist/modules/billing/lib/prepaidConfig.js +17 -25
  294. package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
  295. package/dist/modules/billing/lib/prepaidConsume.js +26 -7
  296. package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
  297. package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
  298. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
  299. package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
  300. package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
  301. package/dist/modules/billing/lib/seeds/demo.js +51 -3
  302. package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
  303. package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
  304. package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
  305. package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
  306. package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
  307. package/dist/modules/billing/lib/servicePeriods.js +121 -0
  308. package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
  309. package/dist/modules/billing/lib/testRunCleanup.js +24 -0
  310. package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
  311. package/dist/modules/billing/lib/topupCapture.js +2 -2
  312. package/dist/modules/billing/lib/topupCapture.js.map +2 -2
  313. package/dist/modules/billing/lib/topupReceipt.js +58 -79
  314. package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
  315. package/dist/modules/billing/lib/usageRater.js.map +1 -1
  316. package/dist/modules/billing/lib/usageRunner.js +14 -6
  317. package/dist/modules/billing/lib/usageRunner.js.map +2 -2
  318. package/dist/modules/billing/lib/vat.js +39 -0
  319. package/dist/modules/billing/lib/vat.js.map +7 -0
  320. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
  321. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
  322. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
  323. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
  324. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
  325. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
  326. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
  327. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
  328. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
  329. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
  330. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
  331. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
  332. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
  333. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
  334. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
  335. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
  336. package/dist/modules/billing/setup.js +7 -184
  337. package/dist/modules/billing/setup.js.map +2 -2
  338. package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
  339. package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
  340. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
  341. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
  342. package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
  343. package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
  344. package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
  345. package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
  346. package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
  347. package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
  348. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
  349. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
  350. package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
  351. package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
  352. package/generated/entities/billing_account/index.ts +3 -0
  353. package/generated/entities/billing_allocation/index.ts +18 -0
  354. package/generated/entities/billing_document_sequence/index.ts +11 -0
  355. package/generated/entities/billing_invoice/index.ts +38 -0
  356. package/generated/entities/billing_invoice_line/index.ts +24 -0
  357. package/generated/entities/billing_item/index.ts +4 -0
  358. package/generated/entities/billing_payment/index.ts +25 -0
  359. package/generated/entities/billing_payment_import/index.ts +14 -0
  360. package/generated/entities/billing_run/index.ts +0 -1
  361. package/generated/entities/billing_run_outcome/index.ts +1 -0
  362. package/generated/entities/billing_settings/index.ts +26 -0
  363. package/generated/entities/billing_statement/index.ts +1 -0
  364. package/generated/entities.ids.generated.ts +7 -0
  365. package/package.json +5 -1
  366. package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +171 -0
  367. package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
  368. package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
  369. package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
  370. package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
  371. package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
  372. package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
  373. package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
  374. package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
  375. package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
  376. package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
  377. package/src/modules/billing/__tests__/commands/invoices.test.ts +436 -354
  378. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
  379. package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
  380. package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
  381. package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
  382. package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
  383. package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
  384. package/src/modules/billing/__tests__/entityRegistry.test.ts +8 -1
  385. package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
  386. package/src/modules/billing/__tests__/i18n/dictionaries.test.ts +64 -0
  387. package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
  388. package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
  389. package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
  390. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +228 -17
  391. package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
  392. package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
  393. package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
  394. package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
  395. package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +206 -0
  396. package/src/modules/billing/__tests__/lib/invoiceLanguages.test.ts +49 -0
  397. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
  398. package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
  399. package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
  400. package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
  401. package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
  402. package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
  403. package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
  404. package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
  405. package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
  406. package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
  407. package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +269 -0
  408. package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
  409. package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
  410. package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
  411. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
  412. package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
  413. package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
  414. package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
  415. package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
  416. package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
  417. package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
  418. package/src/modules/billing/__tests__/setup.test.ts +13 -4
  419. package/src/modules/billing/__tests__/validators.test.ts +197 -3
  420. package/src/modules/billing/acl.ts +12 -0
  421. package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
  422. package/src/modules/billing/api/accounts/route.ts +59 -4
  423. package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
  424. package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
  425. package/src/modules/billing/api/invoices/[id]/pdf/route.ts +80 -0
  426. package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
  427. package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
  428. package/src/modules/billing/api/invoices/add-line/route.ts +2 -2
  429. package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
  430. package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
  431. package/src/modules/billing/api/invoices/post/route.ts +7 -6
  432. package/src/modules/billing/api/invoices/remove-line/route.ts +2 -2
  433. package/src/modules/billing/api/invoices/route.ts +81 -86
  434. package/src/modules/billing/api/items/bulk/route.ts +2 -1
  435. package/src/modules/billing/api/items/change-price/route.ts +54 -0
  436. package/src/modules/billing/api/items/route.ts +23 -7
  437. package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
  438. package/src/modules/billing/api/payment-imports/route.ts +57 -0
  439. package/src/modules/billing/api/payments/[id]/route.ts +63 -0
  440. package/src/modules/billing/api/payments/allocate/route.ts +35 -0
  441. package/src/modules/billing/api/payments/assign/route.ts +36 -0
  442. package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
  443. package/src/modules/billing/api/payments/ignore/route.ts +35 -0
  444. package/src/modules/billing/api/payments/import/route.ts +72 -0
  445. package/src/modules/billing/api/payments/restore/route.ts +35 -0
  446. package/src/modules/billing/api/payments/route.ts +147 -0
  447. package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
  448. package/src/modules/billing/api/payments/unassign/route.ts +35 -0
  449. package/src/modules/billing/api/run-outcomes/route.ts +49 -6
  450. package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
  451. package/src/modules/billing/api/runs/[id]/route.ts +59 -0
  452. package/src/modules/billing/api/runs/retry-failed/route.ts +2 -1
  453. package/src/modules/billing/api/runs/route.ts +15 -7
  454. package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
  455. package/src/modules/billing/api/settings/route.ts +116 -0
  456. package/src/modules/billing/api/statements/route.ts +5 -0
  457. package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
  458. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
  459. package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
  460. package/src/modules/billing/backend/billing/accounts/page.tsx +70 -4
  461. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +444 -95
  462. package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
  463. package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
  464. package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
  465. package/src/modules/billing/backend/billing/items/page.tsx +18 -2
  466. package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
  467. package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
  468. package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
  469. package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
  470. package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
  471. package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
  472. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +49 -18
  473. package/src/modules/billing/backend/billing/runs/page.tsx +59 -8
  474. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +86 -5
  475. package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
  476. package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
  477. package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
  478. package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
  479. package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
  480. package/src/modules/billing/backend/config/billing/page.tsx +549 -0
  481. package/src/modules/billing/cli.ts +31 -1
  482. package/src/modules/billing/commands/accounts.ts +6 -0
  483. package/src/modules/billing/commands/index.ts +3 -0
  484. package/src/modules/billing/commands/invoices.ts +364 -231
  485. package/src/modules/billing/commands/items.ts +143 -0
  486. package/src/modules/billing/commands/memos.ts +218 -0
  487. package/src/modules/billing/commands/payments.ts +428 -0
  488. package/src/modules/billing/commands/runs.ts +205 -3
  489. package/src/modules/billing/commands/settings.ts +77 -0
  490. package/src/modules/billing/commands/topups.ts +12 -3
  491. package/src/modules/billing/commands/transactions.ts +2 -2
  492. package/src/modules/billing/components/AccountForm.tsx +199 -53
  493. package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
  494. package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
  495. package/src/modules/billing/components/AccountPicker.tsx +70 -0
  496. package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
  497. package/src/modules/billing/components/BillingDataTable.tsx +34 -0
  498. package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
  499. package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
  500. package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
  501. package/src/modules/billing/components/CustomerLabel.tsx +53 -0
  502. package/src/modules/billing/components/InvoicesTable.tsx +344 -0
  503. package/src/modules/billing/components/ItemForm.tsx +233 -44
  504. package/src/modules/billing/components/ItemPriceField.tsx +81 -0
  505. package/src/modules/billing/components/LineFormDialog.tsx +25 -1
  506. package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
  507. package/src/modules/billing/components/MoneyInput.tsx +26 -0
  508. package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
  509. package/src/modules/billing/components/PaymentsTable.tsx +213 -0
  510. package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
  511. package/src/modules/billing/components/PrepaidPanel.tsx +19 -6
  512. package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
  513. package/src/modules/billing/components/TestRunActions.tsx +152 -0
  514. package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
  515. package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
  516. package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
  517. package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
  518. package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
  519. package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
  520. package/src/modules/billing/components/account/AccountView.tsx +101 -0
  521. package/src/modules/billing/components/account/accountRow.ts +70 -0
  522. package/src/modules/billing/components/customerNames.ts +62 -0
  523. package/src/modules/billing/data/entities.ts +656 -6
  524. package/src/modules/billing/data/entityIds.ts +9 -0
  525. package/src/modules/billing/data/validators.ts +388 -8
  526. package/src/modules/billing/events.ts +56 -3
  527. package/src/modules/billing/i18n/de.json +880 -0
  528. package/src/modules/billing/i18n/en.json +457 -21
  529. package/src/modules/billing/i18n/es.json +880 -0
  530. package/src/modules/billing/i18n/pl.json +458 -22
  531. package/src/modules/billing/index.ts +6 -2
  532. package/src/modules/billing/lib/accountFinancials.ts +84 -0
  533. package/src/modules/billing/lib/accountLock.ts +32 -0
  534. package/src/modules/billing/lib/accountStatus.ts +11 -9
  535. package/src/modules/billing/lib/accountSummary.ts +172 -0
  536. package/src/modules/billing/lib/allocation.ts +515 -0
  537. package/src/modules/billing/lib/allocationEvents.ts +26 -0
  538. package/src/modules/billing/lib/billPeriod.ts +19 -0
  539. package/src/modules/billing/lib/billRunEngine.ts +350 -150
  540. package/src/modules/billing/lib/billingSettings.ts +145 -0
  541. package/src/modules/billing/lib/commandRoute.ts +112 -0
  542. package/src/modules/billing/lib/customerLookup.ts +157 -0
  543. package/src/modules/billing/lib/dateOnly.ts +22 -0
  544. package/src/modules/billing/lib/documentParties.ts +23 -0
  545. package/src/modules/billing/lib/documentTypes.ts +37 -0
  546. package/src/modules/billing/lib/dueDate.ts +26 -0
  547. package/src/modules/billing/lib/iban.ts +36 -0
  548. package/src/modules/billing/lib/invoiceDelivery.ts +196 -0
  549. package/src/modules/billing/lib/invoiceLanguages.ts +57 -0
  550. package/src/modules/billing/lib/invoiceWriter.ts +162 -148
  551. package/src/modules/billing/lib/itemBillingState.ts +67 -0
  552. package/src/modules/billing/lib/itemPriceChange.ts +56 -0
  553. package/src/modules/billing/lib/itemSelector.ts +4 -1
  554. package/src/modules/billing/lib/lineAmounts.ts +61 -0
  555. package/src/modules/billing/lib/memoGuards.ts +41 -0
  556. package/src/modules/billing/lib/navigation.ts +64 -0
  557. package/src/modules/billing/lib/numbering.ts +174 -0
  558. package/src/modules/billing/lib/overdue.ts +60 -0
  559. package/src/modules/billing/lib/paymentCsv.ts +171 -0
  560. package/src/modules/billing/lib/paymentGateway.ts +52 -0
  561. package/src/modules/billing/lib/paymentImport.ts +228 -0
  562. package/src/modules/billing/lib/paymentMatcher.ts +88 -0
  563. package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
  564. package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
  565. package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
  566. package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
  567. package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
  568. package/src/modules/billing/lib/pdf/invoiceModel.ts +329 -0
  569. package/src/modules/billing/lib/pdf/invoicePdf.ts +220 -0
  570. package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
  571. package/src/modules/billing/lib/prepaidConfig.ts +27 -36
  572. package/src/modules/billing/lib/prepaidConsume.ts +29 -6
  573. package/src/modules/billing/lib/prepaidPeriodClose.ts +407 -239
  574. package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
  575. package/src/modules/billing/lib/seeds/demo.ts +79 -6
  576. package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
  577. package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
  578. package/src/modules/billing/lib/servicePeriods.ts +226 -0
  579. package/src/modules/billing/lib/testRunCleanup.ts +42 -0
  580. package/src/modules/billing/lib/topupCapture.ts +3 -4
  581. package/src/modules/billing/lib/topupReceipt.ts +70 -103
  582. package/src/modules/billing/lib/usageRater.ts +1 -1
  583. package/src/modules/billing/lib/usageRunner.ts +17 -5
  584. package/src/modules/billing/lib/vat.ts +56 -0
  585. package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
  586. package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
  587. package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
  588. package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
  589. package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
  590. package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
  591. package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
  592. package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
  593. package/src/modules/billing/setup.ts +11 -237
  594. package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
  595. package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
  596. package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
  597. package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
  598. package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
  599. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
  600. package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
  601. package/dist/modules/billing/lib/invoiceStatus.js +0 -38
  602. package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
  603. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
  604. package/src/modules/billing/lib/invoiceStatus.ts +0 -61
@@ -0,0 +1,76 @@
1
+ import { lineVat, type PriceBasis } from './lineAmounts'
2
+ import { round2 } from './vat'
3
+
4
+ /**
5
+ * Prepaid tax point (Spec F, F-D3 / P-D10) — how much a prepaid charge takes
6
+ * from the balance. Shared by the Bill Run period close and real-time usage
7
+ * consumption so the two paths can never drift apart.
8
+ *
9
+ * - `topup` (default): VAT was charged on the top-up receipt, so the
10
+ * balance holds VAT-inclusive money and the item amount is debited as
11
+ * entered (`debit = net = amount`, `tax = 0`). The item's VAT rate and
12
+ * price basis do not apply.
13
+ * - `consumption`: the receipt carried no VAT; each debit is the line's
14
+ * gross at the item's rate and basis (`lineVat`).
15
+ */
16
+
17
+ export type PrepaidTaxPoint = 'topup' | 'consumption'
18
+
19
+ export type PrepaidDebitInput = {
20
+ /** Extended line amount on `priceBasis` (negative for credits). */
21
+ lineAmount: number | string
22
+ priceBasis: PriceBasis
23
+ /** Item VAT rate in percent. */
24
+ taxRate: number | string
25
+ taxPoint: PrepaidTaxPoint
26
+ }
27
+
28
+ export type PrepaidDebit = {
29
+ /** What leaves the balance (positive charge, negative credit back). */
30
+ debit: number
31
+ net: number
32
+ tax: number
33
+ /** VAT rate applied (0 in `topup` mode). */
34
+ taxRate: number
35
+ }
36
+
37
+ function toNumber(value: number | string): number {
38
+ const n = typeof value === 'number' ? value : Number.parseFloat(value)
39
+ return Number.isFinite(n) ? n : 0
40
+ }
41
+
42
+ export function resolvePrepaidDebit(input: PrepaidDebitInput): PrepaidDebit {
43
+ if (input.taxPoint === 'topup') {
44
+ const amount = round2(toNumber(input.lineAmount))
45
+ return { debit: amount, net: amount, tax: 0, taxRate: 0 }
46
+ }
47
+ const rate = Math.max(0, toNumber(input.taxRate))
48
+ const vat = lineVat({ amount: input.lineAmount, priceBasis: input.priceBasis, taxRate: rate })
49
+ return { debit: vat.gross, net: vat.net, tax: vat.tax, taxRate: rate }
50
+ }
51
+
52
+ export type VatSummaryRow = { rate: string; net: string; tax: string; gross: string }
53
+
54
+ /**
55
+ * Per-rate VAT summary of a set of prepaid debits (statement `vat_summary`).
56
+ * Empty when no debit carried VAT information (`topup` mode).
57
+ */
58
+ export function buildVatSummary(debits: ReadonlyArray<PrepaidDebit>, taxPoint: PrepaidTaxPoint): VatSummaryRow[] {
59
+ if (taxPoint === 'topup') return []
60
+ const byRate = new Map<number, { net: number; tax: number; gross: number }>()
61
+ for (const debit of debits) {
62
+ const row = byRate.get(debit.taxRate) ?? { net: 0, tax: 0, gross: 0 }
63
+ row.net = round2(row.net + debit.net)
64
+ row.tax = round2(row.tax + debit.tax)
65
+ row.gross = round2(row.gross + debit.debit)
66
+ byRate.set(debit.taxRate, row)
67
+ }
68
+ return Array.from(byRate.entries())
69
+ .sort(([a], [b]) => b - a)
70
+ .map(([rate, row]) => ({
71
+ rate: String(rate),
72
+ net: row.net.toFixed(4),
73
+ tax: row.tax.toFixed(4),
74
+ gross: row.gross.toFixed(4),
75
+ }))
76
+ }
@@ -1,5 +1,8 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
2
  import { BillingAccount, BillingItem, BillingAccountUsage } from '../../data/entities'
3
+ import type { CustomerKind } from '../../data/validators'
4
+ import type { InvoiceLanguageCode } from '../invoiceLanguages'
5
+ import { DEFAULT_DEMO_SELLER, applySellerDefaults } from './sellerSettings'
3
6
 
4
7
  /**
5
8
  * Self-contained billing test-data seed.
@@ -10,9 +13,13 @@ import { BillingAccount, BillingItem, BillingAccountUsage } from '../../data/ent
10
13
  * so re-running is a no-op rather than a duplicate.
11
14
  *
12
15
  * The accounts are dated so they are immediately billable: `next_bill_date`
13
- * is the 1st of the current month, which makes a Bill Run with the default
14
- * (today's) as-of date produce an invoice for the previous month — the usage
15
- * record is seeded into exactly that period.
16
+ * is the 1st of the current month. A Bill Run with the default (today's)
17
+ * as-of date then shows every Spec A behaviour on one invoice per account:
18
+ * - recurring items billed IN ADVANCE for this month (the default);
19
+ * - Globex's in-arrears item billed for last month;
20
+ * - Northwind's mid-cycle add-on: a prorated stub for the second half of
21
+ * last month plus this month;
22
+ * - usage (always in arrears) for last month.
16
23
  *
17
24
  * Drives `mercato billing seed`. Kept free of any customer-specific data so
18
25
  * it ships with `@dainamite/billing`.
@@ -32,8 +39,10 @@ const ACCOUNT_NAME_PREFIX = 'Demo — '
32
39
  type DemoAccountSpec = {
33
40
  name: string
34
41
  customerId: string
42
+ // Spec E: 'person' = a B2C account. Omitted → 'company'.
43
+ customerKind?: CustomerKind
35
44
  currencyCode: string
36
- invoiceLanguage: string
45
+ invoiceLanguage: InvoiceLanguageCode
37
46
  }
38
47
 
39
48
  type DemoItemSpec = {
@@ -42,6 +51,18 @@ type DemoItemSpec = {
42
51
  description: string
43
52
  unitPrice: number
44
53
  uomCode?: string
54
+ // Spec B: VAT %. Default 23 (PL standard rate).
55
+ taxRate?: string
56
+ // Spec A. Default 'in_advance'.
57
+ chargeTiming?: 'in_advance' | 'in_arrears'
58
+ // Spec F. Default 'net'. Consumer (B2C) prices are quoted gross.
59
+ priceBasis?: 'net' | 'gross'
60
+ // When the item starts, relative to the seeded bill date:
61
+ // 'billDate' (default) — 1st of this month, billed in advance from day one;
62
+ // 'prevMonth' — 1st of last month (in-arrears items bill it now);
63
+ // 'prevMonthMid' — 15th of last month: an add-on bought mid-cycle,
64
+ // so the first run shows a prorated stub line.
65
+ start?: 'billDate' | 'prevMonth' | 'prevMonthMid'
45
66
  }
46
67
 
47
68
  type DemoUsageSpec = {
@@ -65,7 +86,15 @@ const ACCOUNTS: Array<{
65
86
  },
66
87
  items: [
67
88
  { ref: `${SRC}-nw-rec`, type: 'recurring', description: 'Platform subscription (monthly)', unitPrice: 199 },
68
- { ref: `${SRC}-nw-usg`, type: 'usage', description: 'Data transfer (per GB)', unitPrice: 0.5, uomCode: 'gb' },
89
+ {
90
+ ref: `${SRC}-nw-addon`,
91
+ type: 'recurring',
92
+ description: 'Priority support add-on (monthly)',
93
+ unitPrice: 60,
94
+ taxRate: '8',
95
+ start: 'prevMonthMid',
96
+ },
97
+ { ref: `${SRC}-nw-usg`, type: 'usage', description: 'Data transfer (per GB)', unitPrice: 0.5, uomCode: 'gb', start: 'prevMonth' },
69
98
  ],
70
99
  usage: [
71
100
  { ref: `${SRC}-nw-usage`, uomCode: 'gb', quantity: 120, lineDescription: 'Data transfer (prev. month)' },
@@ -80,11 +109,43 @@ const ACCOUNTS: Array<{
80
109
  },
81
110
  items: [
82
111
  { ref: `${SRC}-gx-rec`, type: 'recurring', description: 'Cloud plan (monthly)', unitPrice: 49 },
112
+ {
113
+ ref: `${SRC}-gx-arr`,
114
+ type: 'recurring',
115
+ description: 'Managed backups (billed in arrears)',
116
+ unitPrice: 15,
117
+ // 0 % — shows a mixed-rate VAT summary on the document.
118
+ taxRate: '0',
119
+ chargeTiming: 'in_arrears',
120
+ start: 'prevMonth',
121
+ },
122
+ ],
123
+ usage: [],
124
+ },
125
+ {
126
+ // B2C: a private person on a flat monthly internet plan, invoiced in Polish.
127
+ account: {
128
+ name: `${ACCOUNT_NAME_PREFIX}Jan Kowalski`,
129
+ customerId: 'demo-cust-jan-kowalski',
130
+ customerKind: 'person',
131
+ currencyCode: 'PLN',
132
+ invoiceLanguage: 'pl',
133
+ },
134
+ items: [
135
+ // B2C price quoted gross (79.00 PLN incl. 23 % VAT) — Spec F, F-D2.
136
+ {
137
+ ref: `${SRC}-jk-rec`,
138
+ type: 'recurring',
139
+ description: 'Internet 300 Mb/s (miesięcznie)',
140
+ unitPrice: 79,
141
+ priceBasis: 'gross',
142
+ },
83
143
  ],
84
144
  usage: [],
85
145
  },
86
146
  ]
87
147
 
148
+
88
149
  /** Delete every row this seed owns within the scope (reset before re-seed). */
89
150
  export async function resetBillingDemo(em: EntityManager, scope: BillingDemoScope): Promise<void> {
90
151
  await em.nativeDelete(BillingAccountUsage, {
@@ -116,9 +177,16 @@ export async function seedBillingDemo(
116
177
  const nextBillDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1))
117
178
  const prevMonthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1))
118
179
  const prevMonthEnd = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 0))
180
+ const prevMonthMid = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 15))
181
+ const startFor = (item: DemoItemSpec): Date =>
182
+ item.start === 'prevMonth' ? prevMonthStart : item.start === 'prevMonthMid' ? prevMonthMid : nextBillDate
119
183
 
120
184
  const result: BillingDemoResult = { accountsCreated: 0, itemsCreated: 0, usageCreated: 0 }
121
185
 
186
+ // ── Seller settings (Spec B: posting needs them). Only empty fields are
187
+ // filled, so an operator's real details are never overwritten. ──
188
+ await applySellerDefaults(em, scope, DEFAULT_DEMO_SELLER)
189
+
122
190
  for (const spec of ACCOUNTS) {
123
191
  // ── Account (idempotent by name within scope) ──
124
192
  let account = await em.findOne(BillingAccount, {
@@ -130,6 +198,7 @@ export async function seedBillingDemo(
130
198
  account = em.create(BillingAccount, {
131
199
  ...scope,
132
200
  customerId: spec.account.customerId,
201
+ customerKind: spec.account.customerKind ?? 'company',
133
202
  name: spec.account.name,
134
203
  currencyCode: spec.account.currencyCode,
135
204
  billCycle: 'monthly',
@@ -156,8 +225,12 @@ export async function seedBillingDemo(
156
225
  ...scope,
157
226
  billAccountId: account.id,
158
227
  type: item.type,
159
- billStartDate: prevMonthStart,
228
+ billStartDate: startFor(item),
160
229
  billEndDate: null,
230
+ chargeTiming: item.chargeTiming ?? 'in_advance',
231
+ prorate: true,
232
+ taxRate: item.taxRate ?? '23',
233
+ priceBasis: item.priceBasis ?? 'net',
161
234
  description: item.description,
162
235
  rateJson: { unit_price: item.unitPrice },
163
236
  uomCode: item.uomCode ?? null,
@@ -0,0 +1,136 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { PAYMENT_CSV_HEADER } from '../paymentCsv'
3
+
4
+ /**
5
+ * Demo bank statement (Spec C): a `payments-sample.csv` built from the
6
+ * organization's real posted invoices, so numbers and amounts line up and
7
+ * every matcher outcome shows up after one upload:
8
+ *
9
+ * exact amount + number → allocated, invoice paid
10
+ * half the amount → invoice partially paid
11
+ * amount + 50 → paid, 50 credit applied to the next open invoice
12
+ * two numbers, sum of both → both paid
13
+ * no reference, known payer → unmatched → assign to account
14
+ * a top-up receipt number → unmatched (prepaid) → convert to top-up
15
+ * junk title → unmatched (no reference)
16
+ * other currency → unmatched (currency mismatch)
17
+ * the same line twice → both stored (occurrence index); re-upload = duplicates
18
+ *
19
+ * Pure builder + a loader for the CLI (`mercato billing payments-sample`).
20
+ */
21
+
22
+ export type SampleInvoice = {
23
+ number: string
24
+ totalGross: string
25
+ currencyCode: string
26
+ accountName: string
27
+ }
28
+
29
+ export type SampleReceipt = { number: string; currencyCode: string; accountName: string }
30
+
31
+ function csvField(value: string): string {
32
+ return /[",\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value
33
+ }
34
+
35
+ function money(value: number): string {
36
+ return value.toFixed(2)
37
+ }
38
+
39
+ export function buildPaymentsSampleCsv(params: {
40
+ invoices: SampleInvoice[]
41
+ receipt?: SampleReceipt | null
42
+ /** Spec D: a posted debit memo — paid by its own number like an invoice. */
43
+ debitMemo?: SampleInvoice | null
44
+ date: string
45
+ }): { csv: string; rows: number; missing: string[] } {
46
+ const { invoices, receipt, date, debitMemo } = params
47
+ const rows: string[][] = []
48
+ const missing: string[] = []
49
+ const gross = (inv: SampleInvoice) => Number.parseFloat(inv.totalGross)
50
+ const [a, b, c] = invoices
51
+ // Two invoices of the SAME account (references to two customers are ambiguous).
52
+ const rest = invoices.slice(3)
53
+ let d: SampleInvoice | undefined
54
+ let e: SampleInvoice | undefined
55
+ for (let i = 0; i < rest.length && !d; i += 1) {
56
+ const match = rest.slice(i + 1).find((other) => other.accountName === rest[i].accountName && other.currencyCode === rest[i].currencyCode)
57
+ if (match) {
58
+ d = rest[i]
59
+ e = match
60
+ }
61
+ }
62
+
63
+ if (a) rows.push([date, money(gross(a)), a.currencyCode, `Payment for ${a.number}`, a.accountName, ''])
64
+ else missing.push('exact payment (needs 1 posted invoice)')
65
+ if (b) rows.push([date, money(gross(b) / 2), b.currencyCode, `${b.number} part 1`, b.accountName, ''])
66
+ else missing.push('partial payment (needs 2 posted invoices)')
67
+ if (c) rows.push([date, money(gross(c) + 50), c.currencyCode, `Invoice ${c.number} + advance`, c.accountName, ''])
68
+ else missing.push('overpayment (needs 3 posted invoices)')
69
+ if (d && e) rows.push([date, money(gross(d) + gross(e)), d.currencyCode, `${d.number}, ${e.number}`, d.accountName, ''])
70
+ else missing.push('two invoices in one title (needs two more posted invoices of one account)')
71
+ if (a) rows.push([date, '25.00', a.currencyCode, 'Thank you', a.accountName, 'PL61109010140000071219812874'])
72
+ if (receipt) rows.push([date, '100.00', receipt.currencyCode, `Top-up ${receipt.number}`, receipt.accountName, ''])
73
+ else missing.push('prepaid top-up (needs a prepaid top-up receipt)')
74
+ if (debitMemo) {
75
+ rows.push([date, money(gross(debitMemo)), debitMemo.currencyCode, `Note ${debitMemo.number}`, debitMemo.accountName, ''])
76
+ }
77
+ rows.push([date, '12.34', 'PLN', 'misc transfer', 'Unknown payer', ''])
78
+ if (a) {
79
+ const other = a.currencyCode === 'EUR' ? 'PLN' : 'EUR'
80
+ rows.push([date, money(gross(a)), other, a.number, a.accountName, ''])
81
+ const dup = [date, '10.00', a.currencyCode, 'Duplicate line', a.accountName, '']
82
+ rows.push(dup, [...dup])
83
+ }
84
+ const csv = [PAYMENT_CSV_HEADER.join(','), ...rows.map((row) => row.map(csvField).join(','))].join('\n') + '\n'
85
+ return { csv, rows: rows.length, missing }
86
+ }
87
+
88
+ /** Posted, unpaid invoices of the org (oldest first) and one top-up receipt. */
89
+ export async function loadSampleSources(
90
+ em: EntityManager,
91
+ scope: { tenantId: string; organizationId: string },
92
+ ): Promise<{ invoices: SampleInvoice[]; receipt: SampleReceipt | null; debitMemo: SampleInvoice | null }> {
93
+ const invoices = (await em.execute(
94
+ `SELECT i.number, i.total_gross, i.currency_code, a.name AS account_name
95
+ FROM billing_invoices i
96
+ JOIN billing_accounts a ON a.id = i.bill_account_id
97
+ WHERE i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL AND i.test_run = false
98
+ AND i.document_type = 'invoice' AND i.status = 'posted' AND a.billing_mode = 'postpaid'
99
+ ORDER BY i.issue_date ASC, i.number ASC
100
+ LIMIT 20`,
101
+ [scope.tenantId, scope.organizationId],
102
+ )) as unknown as Array<{ number: string; total_gross: string; currency_code: string; account_name: string }>
103
+ const receipts = (await em.execute(
104
+ `SELECT i.number, i.currency_code, a.name AS account_name
105
+ FROM billing_invoices i
106
+ JOIN billing_accounts a ON a.id = i.bill_account_id
107
+ WHERE i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL AND i.document_type = 'receipt'
108
+ ORDER BY i.created_at DESC
109
+ LIMIT 1`,
110
+ [scope.tenantId, scope.organizationId],
111
+ )) as unknown as Array<{ number: string; currency_code: string; account_name: string }>
112
+ const debits = (await em.execute(
113
+ `SELECT i.number, i.total_gross, i.currency_code, a.name AS account_name
114
+ FROM billing_invoices i
115
+ JOIN billing_accounts a ON a.id = i.bill_account_id
116
+ WHERE i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL AND i.test_run = false
117
+ AND i.document_type = 'debit_memo' AND i.status = 'posted'
118
+ ORDER BY i.issue_date ASC
119
+ LIMIT 1`,
120
+ [scope.tenantId, scope.organizationId],
121
+ )) as unknown as Array<{ number: string; total_gross: string; currency_code: string; account_name: string }>
122
+ return {
123
+ debitMemo: debits?.[0]
124
+ ? { number: debits[0].number, totalGross: debits[0].total_gross, currencyCode: debits[0].currency_code, accountName: debits[0].account_name }
125
+ : null,
126
+ invoices: (invoices ?? []).map((row) => ({
127
+ number: row.number,
128
+ totalGross: row.total_gross,
129
+ currencyCode: row.currency_code,
130
+ accountName: row.account_name,
131
+ })),
132
+ receipt: receipts?.[0]
133
+ ? { number: receipts[0].number, currencyCode: receipts[0].currency_code, accountName: receipts[0].account_name }
134
+ : null,
135
+ }
136
+ }
@@ -0,0 +1,78 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import type { BillingSettings } from '../../data/entities'
3
+ import { getBillingSettings, type SettingsScope } from '../billingSettings'
4
+
5
+ /**
6
+ * Seller details for demo / seed tenants (Spec B: posting an invoice needs
7
+ * legal name, address, tax ID and IBAN). Public seed API — demo use cases
8
+ * (`demo_gix`, `demo_puffin`, …) call `applySellerDefaults` from their own
9
+ * `seedDefaults` with a profile of their own.
10
+ *
11
+ * Only EMPTY fields are filled, so an operator's real details are never
12
+ * overwritten and re-running a seed is a no-op. Values must pass the settings
13
+ * validators (checksum-valid IBAN, 8/11-char BIC) so the settings page can be
14
+ * saved without touching them.
15
+ */
16
+
17
+ export type SellerProfile = {
18
+ legalName: string
19
+ sellerAddress: { line1: string; line2?: string; city: string; postal_code: string; country: string }
20
+ taxId: string
21
+ bankName: string
22
+ iban: string
23
+ bic: string
24
+ invoiceFooter?: string
25
+ }
26
+
27
+ const SELLER_FIELDS = ['legalName', 'sellerAddress', 'taxId', 'bankName', 'iban', 'bic', 'invoiceFooter'] as const
28
+ type SellerField = (typeof SELLER_FIELDS)[number]
29
+
30
+ /** The generic seller of the billing demo seed (primary tenant). */
31
+ export const DEFAULT_DEMO_SELLER: SellerProfile = {
32
+ legalName: 'Dainamite Demo sp. z o.o.',
33
+ sellerAddress: { line1: 'ul. Przykładowa 10', city: 'Kraków', postal_code: '30-001', country: 'PL' },
34
+ taxId: 'PL1234563218',
35
+ bankName: 'Demo Bank S.A.',
36
+ iban: 'PL61109010140000071219812874',
37
+ bic: 'WBKPPLPP',
38
+ invoiceFooter: 'Sąd Rejonowy dla Krakowa-Śródmieścia, KRS 0000000000. Kapitał zakładowy 5 000 zł.',
39
+ }
40
+
41
+ function isEmpty(value: unknown): boolean {
42
+ if (value === null || value === undefined) return true
43
+ if (typeof value === 'string') return value.trim() === ''
44
+ if (typeof value === 'object') return Object.keys(value as object).length === 0
45
+ return false
46
+ }
47
+
48
+ /** Pure: which profile values would fill which empty settings fields. */
49
+ export function sellerFieldsToFill(
50
+ settings: Pick<BillingSettings, SellerField>,
51
+ profile: SellerProfile,
52
+ ): Partial<Record<SellerField, unknown>> {
53
+ const out: Partial<Record<SellerField, unknown>> = {}
54
+ for (const field of SELLER_FIELDS) {
55
+ const value = profile[field]
56
+ if (value === undefined) continue
57
+ if (isEmpty(settings[field])) out[field] = value
58
+ }
59
+ return out
60
+ }
61
+
62
+ /**
63
+ * Fill the organization's empty seller fields from `profile` and flush.
64
+ * Returns the names of the fields that were filled (empty when nothing changed).
65
+ */
66
+ export async function applySellerDefaults(
67
+ em: EntityManager,
68
+ scope: SettingsScope,
69
+ profile: SellerProfile,
70
+ ): Promise<string[]> {
71
+ const settings = await getBillingSettings(em, scope)
72
+ const fill = sellerFieldsToFill(settings, profile)
73
+ const fields = Object.keys(fill)
74
+ if (fields.length === 0) return []
75
+ Object.assign(settings, fill, { updatedAt: new Date() })
76
+ await em.flush()
77
+ return fields
78
+ }
@@ -0,0 +1,226 @@
1
+ import type { BillCycle, ChargeTiming } from '../data/validators'
2
+ import { addDays, cloneAtMidnight, shiftCycles } from './billPeriod'
3
+
4
+ /**
5
+ * Recurring-charge computation for one bill date (Spec A).
6
+ *
7
+ * Pure and storage-agnostic: given an item's state and the account's bill
8
+ * date, decide which service periods to charge (or credit) and where the
9
+ * item's `billed_to_date` watermark moves. The engine calls it once per item
10
+ * per simulated bill date and owns persistence.
11
+ *
12
+ * Model:
13
+ * - The account's cycle grid is anchored at the bill date `D`:
14
+ * …, D − cycle, D, D + cycle, … (each period `[g_k, g_{k+1} − 1]`).
15
+ * - Horizon — how far the item is billed on `D`:
16
+ * in_advance → D + cycle − 1 in_arrears → D − 1
17
+ * - Cursor — first unbilled day: `billed_to_date + 1`, else `bill_start_date`.
18
+ * - The unbilled window `[cursor, min(horizon, bill_end_date)]` is cut along
19
+ * the grid; each grid period becomes one line (full → unit price,
20
+ * partial → prorated by calendar days, or skipped when `prorate=false`).
21
+ * - Credit (A-D2): an item ended (`bill_end_date`) before what was already
22
+ * billed (`billed_to_date`) gets negative lines for the unused days when
23
+ * `prorate=true`, and the watermark is pulled back to `bill_end_date`.
24
+ *
25
+ * The watermark is the single source of truth against double billing: every
26
+ * computation starts at `billed_to_date + 1`, so switching timing, re-running
27
+ * a bill date, or catching up can never charge a day twice.
28
+ */
29
+
30
+ export type RecurringItemState = {
31
+ id: string
32
+ chargeTiming: ChargeTiming
33
+ prorate: boolean
34
+ billStartDate: Date | string
35
+ billEndDate?: Date | string | null
36
+ billedToDate?: Date | string | null
37
+ unitPrice: number
38
+ isActive: boolean
39
+ deletedAt?: Date | string | null
40
+ }
41
+
42
+ export type ProrationInfo = { activeDays: number; periodDays: number }
43
+
44
+ export type RecurringLine = {
45
+ billingItemId: string
46
+ kind: 'charge' | 'credit'
47
+ /** Inclusive service period the line covers. */
48
+ servicePeriodStart: Date
49
+ servicePeriodEnd: Date
50
+ /** 2dp half-up; negative for credits. */
51
+ amount: number
52
+ /** Null when the line covers a full grid period. */
53
+ proration: ProrationInfo | null
54
+ }
55
+
56
+ export type RecurringSkipReason =
57
+ | 'item-inactive'
58
+ | 'item-soft-deleted'
59
+ | 'partial-period-not-prorated'
60
+ | 'ended-after-billing-no-credit'
61
+ | 'period-cap-reached'
62
+
63
+ export type RecurringSkip = {
64
+ billingItemId: string
65
+ reason: RecurringSkipReason
66
+ servicePeriodStart: Date | null
67
+ servicePeriodEnd: Date | null
68
+ }
69
+
70
+ export type RecurringComputation = {
71
+ lines: RecurringLine[]
72
+ /** Watermark after these lines; unchanged when nothing was billed. */
73
+ newBilledToDate: Date | null
74
+ skipped: RecurringSkip[]
75
+ }
76
+
77
+ /** Upper bound of grid periods processed per item per call (runaway guard). */
78
+ export const MAX_PERIODS_PER_ITEM = 120
79
+
80
+ const DAY_MS = 86_400_000
81
+
82
+ function toDate(value: Date | string | number): Date {
83
+ return cloneAtMidnight(value)
84
+ }
85
+
86
+ function optDate(value: Date | string | null | undefined): Date | null {
87
+ return value === null || value === undefined || value === '' ? null : toDate(value)
88
+ }
89
+
90
+ function minDate(a: Date, b: Date): Date {
91
+ return a.getTime() <= b.getTime() ? a : b
92
+ }
93
+
94
+ function maxDate(a: Date, b: Date): Date {
95
+ return a.getTime() >= b.getTime() ? a : b
96
+ }
97
+
98
+ /** Inclusive day count of `[from, to]`. */
99
+ export function inclusiveDays(from: Date, to: Date): number {
100
+ return Math.round((to.getTime() - from.getTime()) / DAY_MS) + 1
101
+ }
102
+
103
+ /** Half-up 2dp rounding that survives binary floating point (1.005 → 1.01). */
104
+ export function roundHalfUp2dp(value: number): number {
105
+ const sign = value < 0 ? -1 : 1
106
+ return (sign * Math.round((Math.abs(value) + Number.EPSILON) * 100)) / 100
107
+ }
108
+
109
+ type GridPeriod = { index: number; start: Date; end: Date }
110
+
111
+ function gridPeriod(anchor: Date, cycle: BillCycle, index: number): GridPeriod {
112
+ const start = shiftCycles(anchor, cycle, index)
113
+ const end = addDays(shiftCycles(anchor, cycle, index + 1), -1)
114
+ return { index, start, end }
115
+ }
116
+
117
+ /** Grid period (anchored at `anchor`) containing `date`. */
118
+ export function gridPeriodContaining(anchor: Date, cycle: BillCycle, date: Date): GridPeriod {
119
+ const target = date.getTime()
120
+ // Coarse estimate first so long gaps don't walk one cycle at a time.
121
+ const approxDays: Record<BillCycle, number> = { weekly: 7, monthly: 30.44, quarterly: 91.31, annually: 365.25 }
122
+ let index = Math.floor((target - anchor.getTime()) / DAY_MS / approxDays[cycle])
123
+ let period = gridPeriod(anchor, cycle, index)
124
+ let guard = 0
125
+ while (period.start.getTime() > target && guard++ < 1000) period = gridPeriod(anchor, cycle, --index)
126
+ while (period.end.getTime() < target && guard++ < 2000) period = gridPeriod(anchor, cycle, ++index)
127
+ return period
128
+ }
129
+
130
+ /**
131
+ * Slices `[from, to]` along the grid and prices each slice. Shared by the
132
+ * charge and credit paths so both prorate identically.
133
+ */
134
+ function slice(
135
+ item: RecurringItemState,
136
+ anchor: Date,
137
+ cycle: BillCycle,
138
+ from: Date,
139
+ to: Date,
140
+ kind: RecurringLine['kind'],
141
+ ): { lines: RecurringLine[]; skipped: RecurringSkip[]; lastCovered: Date | null } {
142
+ const lines: RecurringLine[] = []
143
+ const skipped: RecurringSkip[] = []
144
+ let lastCovered: Date | null = null
145
+ let period = gridPeriodContaining(anchor, cycle, from)
146
+ let count = 0
147
+ while (period.start.getTime() <= to.getTime()) {
148
+ if (count++ >= MAX_PERIODS_PER_ITEM) {
149
+ skipped.push({ billingItemId: item.id, reason: 'period-cap-reached', servicePeriodStart: period.start, servicePeriodEnd: null })
150
+ break
151
+ }
152
+ const activeStart = maxDate(period.start, from)
153
+ const activeEnd = minDate(period.end, to)
154
+ const periodDays = inclusiveDays(period.start, period.end)
155
+ const activeDays = inclusiveDays(activeStart, activeEnd)
156
+ const partial = activeDays < periodDays
157
+ if (partial && !item.prorate) {
158
+ skipped.push({
159
+ billingItemId: item.id,
160
+ reason: 'partial-period-not-prorated',
161
+ servicePeriodStart: activeStart,
162
+ servicePeriodEnd: activeEnd,
163
+ })
164
+ } else {
165
+ const gross = partial ? (item.unitPrice * activeDays) / periodDays : item.unitPrice
166
+ const amount = roundHalfUp2dp(gross)
167
+ lines.push({
168
+ billingItemId: item.id,
169
+ kind,
170
+ servicePeriodStart: activeStart,
171
+ servicePeriodEnd: activeEnd,
172
+ amount: kind === 'credit' ? -amount : amount,
173
+ proration: partial ? { activeDays, periodDays } : null,
174
+ })
175
+ }
176
+ lastCovered = activeEnd
177
+ period = gridPeriod(anchor, cycle, period.index + 1)
178
+ }
179
+ return { lines, skipped, lastCovered }
180
+ }
181
+
182
+ export function computeRecurringLines(
183
+ item: RecurringItemState,
184
+ params: { billDate: Date | string; cycle: BillCycle },
185
+ ): RecurringComputation {
186
+ const billedTo = optDate(item.billedToDate)
187
+ if (item.deletedAt) {
188
+ return { lines: [], newBilledToDate: billedTo, skipped: [{ billingItemId: item.id, reason: 'item-soft-deleted', servicePeriodStart: null, servicePeriodEnd: null }] }
189
+ }
190
+ if (!item.isActive) {
191
+ return { lines: [], newBilledToDate: billedTo, skipped: [{ billingItemId: item.id, reason: 'item-inactive', servicePeriodStart: null, servicePeriodEnd: null }] }
192
+ }
193
+
194
+ const anchor = toDate(params.billDate)
195
+ const { cycle } = params
196
+ const start = toDate(item.billStartDate)
197
+ const end = optDate(item.billEndDate)
198
+
199
+ // ── Credit: the item ended inside what was already billed (A-D2). ──
200
+ if (end && billedTo && end.getTime() < billedTo.getTime()) {
201
+ if (!item.prorate) {
202
+ return {
203
+ lines: [],
204
+ newBilledToDate: billedTo,
205
+ skipped: [{ billingItemId: item.id, reason: 'ended-after-billing-no-credit', servicePeriodStart: addDays(end, 1), servicePeriodEnd: billedTo }],
206
+ }
207
+ }
208
+ const credit = slice(item, anchor, cycle, maxDate(addDays(end, 1), start), billedTo, 'credit')
209
+ return { lines: credit.lines, newBilledToDate: end, skipped: credit.skipped }
210
+ }
211
+
212
+ // ── Charge: bill the unbilled window up to the horizon. ──
213
+ const horizon =
214
+ item.chargeTiming === 'in_arrears' ? addDays(anchor, -1) : addDays(shiftCycles(anchor, cycle, 1), -1)
215
+ const cursor = billedTo ? maxDate(addDays(billedTo, 1), start) : start
216
+ const windowEnd = end ? minDate(horizon, end) : horizon
217
+ if (cursor.getTime() > windowEnd.getTime()) {
218
+ return { lines: [], newBilledToDate: billedTo, skipped: [] }
219
+ }
220
+ const charge = slice(item, anchor, cycle, cursor, windowEnd, 'charge')
221
+ return {
222
+ lines: charge.lines,
223
+ newBilledToDate: charge.lastCovered ?? billedTo,
224
+ skipped: charge.skipped,
225
+ }
226
+ }