@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,171 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { z } from 'zod'
3
+ import { PAYMENT_IMPORT_MAX_ROWS } from '../data/validators'
4
+
5
+ /**
6
+ * Bank payment CSV parser (Spec C, demo-grade fixed format). Pure.
7
+ *
8
+ * date,amount,currency,reference,payer_name,payer_account
9
+ * 2026-11-20,123.00,PLN,"Payment for INV/2026/11/0003",ACME Sp. z o.o.,PL61…
10
+ *
11
+ * UTF-8 (BOM tolerated), comma separated, RFC-4180 quoting (`""` escapes,
12
+ * commas and newlines inside quotes), header required, `.` decimal
13
+ * separator, amount > 0 (refunds are out of scope), ≤ 5,000 data rows.
14
+ * Invalid rows are reported with their line number and skipped; they never
15
+ * abort the file.
16
+ */
17
+
18
+ export const PAYMENT_CSV_HEADER = ['date', 'amount', 'currency', 'reference', 'payer_name', 'payer_account'] as const
19
+
20
+ export type PaymentCsvRow = {
21
+ line: number
22
+ receivedAt: string
23
+ amount: string
24
+ currency: string
25
+ reference: string | null
26
+ payerName: string | null
27
+ payerAccount: string | null
28
+ }
29
+
30
+ export type PaymentCsvResult = {
31
+ rows: PaymentCsvRow[]
32
+ errors: Array<{ row: number; message: string }>
33
+ fatal: string | null
34
+ }
35
+
36
+ const rowSchema = z.object({
37
+ date: z
38
+ .string()
39
+ .regex(/^\d{4}-\d{2}-\d{2}$/, 'date must be YYYY-MM-DD')
40
+ .refine((value) => !Number.isNaN(new Date(`${value}T00:00:00Z`).getTime()), 'date is not a valid day'),
41
+ amount: z
42
+ .string()
43
+ .regex(/^\d+(\.\d{1,4})?$/, 'amount must be a positive number with "." as decimal separator')
44
+ .refine((value) => Number.parseFloat(value) > 0, 'amount must be greater than 0'),
45
+ currency: z.string().regex(/^[A-Za-z]{3}$/, 'currency must be a 3-letter code'),
46
+ reference: z.string().max(500, 'reference is longer than 500 characters'),
47
+ payer_name: z.string().max(255),
48
+ payer_account: z.string().max(64),
49
+ })
50
+
51
+ /** Splits CSV text into records of fields (RFC 4180). Returns the 1-based start line of each record. */
52
+ export function splitCsv(text: string): Array<{ line: number; fields: string[] }> {
53
+ const records: Array<{ line: number; fields: string[] }> = []
54
+ let fields: string[] = []
55
+ let field = ''
56
+ let inQuotes = false
57
+ let line = 1
58
+ let recordLine = 1
59
+ const pushRecord = () => {
60
+ fields.push(field)
61
+ if (!(fields.length === 1 && fields[0].trim() === '')) records.push({ line: recordLine, fields })
62
+ fields = []
63
+ field = ''
64
+ }
65
+ for (let i = 0; i < text.length; i += 1) {
66
+ const char = text[i]
67
+ if (inQuotes) {
68
+ if (char === '"') {
69
+ if (text[i + 1] === '"') {
70
+ field += '"'
71
+ i += 1
72
+ } else {
73
+ inQuotes = false
74
+ }
75
+ } else {
76
+ if (char === '\n') line += 1
77
+ field += char
78
+ }
79
+ continue
80
+ }
81
+ if (char === '"' && field.trim() === '') {
82
+ field = ''
83
+ inQuotes = true
84
+ } else if (char === ',') {
85
+ fields.push(field)
86
+ field = ''
87
+ } else if (char === '\n' || char === '\r') {
88
+ if (char === '\r' && text[i + 1] === '\n') i += 1
89
+ pushRecord()
90
+ line += 1
91
+ recordLine = line
92
+ } else {
93
+ field += char
94
+ }
95
+ }
96
+ if (field !== '' || fields.length > 0) pushRecord()
97
+ return records
98
+ }
99
+
100
+ export function parsePaymentCsv(text: string): PaymentCsvResult {
101
+ const records = splitCsv(text.replace(/^/, ''))
102
+ if (records.length === 0) return { rows: [], errors: [], fatal: 'The file is empty' }
103
+ const header = records[0].fields.map((f) => f.trim().toLowerCase())
104
+ if (header.join(',') !== PAYMENT_CSV_HEADER.join(',')) {
105
+ return { rows: [], errors: [], fatal: `The header must be: ${PAYMENT_CSV_HEADER.join(',')}` }
106
+ }
107
+ const data = records.slice(1)
108
+ if (data.length > PAYMENT_IMPORT_MAX_ROWS) {
109
+ return { rows: [], errors: [], fatal: `The file has more than ${PAYMENT_IMPORT_MAX_ROWS} rows` }
110
+ }
111
+ const rows: PaymentCsvRow[] = []
112
+ const errors: PaymentCsvResult['errors'] = []
113
+ for (const record of data) {
114
+ if (record.fields.length !== PAYMENT_CSV_HEADER.length) {
115
+ errors.push({ row: record.line, message: `expected ${PAYMENT_CSV_HEADER.length} columns, got ${record.fields.length}` })
116
+ continue
117
+ }
118
+ const raw = Object.fromEntries(PAYMENT_CSV_HEADER.map((key, i) => [key, record.fields[i].trim()]))
119
+ const parsed = rowSchema.safeParse(raw)
120
+ if (!parsed.success) {
121
+ errors.push({ row: record.line, message: parsed.error.issues.map((issue) => issue.message).join('; ') })
122
+ continue
123
+ }
124
+ rows.push({
125
+ line: record.line,
126
+ receivedAt: parsed.data.date,
127
+ amount: Number.parseFloat(parsed.data.amount).toFixed(4),
128
+ currency: parsed.data.currency.toUpperCase(),
129
+ reference: parsed.data.reference || null,
130
+ payerName: parsed.data.payer_name || null,
131
+ payerAccount: parsed.data.payer_account ? parsed.data.payer_account.replace(/\s+/g, '').toUpperCase() : null,
132
+ })
133
+ }
134
+ return { rows, errors, fatal: null }
135
+ }
136
+
137
+ /**
138
+ * Duplicate guard (P-D7): the same bank line uploaded again yields the same
139
+ * key; identical lines inside one file differ by their occurrence index.
140
+ */
141
+ export function paymentSourceRef(
142
+ scope: { tenantId: string; organizationId: string },
143
+ row: Pick<PaymentCsvRow, 'receivedAt' | 'amount' | 'currency' | 'reference' | 'payerAccount'>,
144
+ occurrence: number,
145
+ ): string {
146
+ const key = [
147
+ scope.tenantId,
148
+ scope.organizationId,
149
+ row.receivedAt,
150
+ row.amount,
151
+ row.currency,
152
+ row.reference ?? '',
153
+ row.payerAccount ?? '',
154
+ String(occurrence),
155
+ ].join('|')
156
+ return `csv:${createHash('sha256').update(key).digest('hex')}`
157
+ }
158
+
159
+ /** Source refs for all rows of a file, counting identical lines (occurrence index). */
160
+ export function sourceRefsForRows(
161
+ scope: { tenantId: string; organizationId: string },
162
+ rows: PaymentCsvRow[],
163
+ ): string[] {
164
+ const seen = new Map<string, number>()
165
+ return rows.map((row) => {
166
+ const identity = [row.receivedAt, row.amount, row.currency, row.reference ?? '', row.payerAccount ?? ''].join('|')
167
+ const occurrence = (seen.get(identity) ?? 0) + 1
168
+ seen.set(identity, occurrence)
169
+ return paymentSourceRef(scope, row, occurrence)
170
+ })
171
+ }
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Optional payment-gateway integration (Spec 0, E-D1).
3
+ *
4
+ * Billing no longer requires the `payment_gateways` module. When it is
5
+ * installed, online top-ups open a checkout session through its
6
+ * `paymentGatewayService`; when it isn't, top-ups via the gateway are
7
+ * unavailable (501) and the UI hides the "Top up" button. Bank-transfer
8
+ * top-ups are recorded through payments (billing Spec C).
9
+ *
10
+ * The structural type below covers only what billing calls, so billing never
11
+ * deep-imports the gateway module's types.
12
+ */
13
+
14
+ export type GatewaySessionRequest = {
15
+ providerKey: string
16
+ paymentId: string
17
+ amount: number
18
+ currencyCode: string
19
+ captureMethod: 'automatic' | 'manual'
20
+ successUrl?: string
21
+ cancelUrl?: string
22
+ metadata?: Record<string, unknown>
23
+ organizationId: string
24
+ tenantId: string
25
+ }
26
+
27
+ export type GatewaySessionResult = {
28
+ transaction: { id: string }
29
+ session: { redirectUrl?: string | null; clientSecret?: string | null; sessionId?: string | null }
30
+ }
31
+
32
+ export type TopupPaymentGateway = {
33
+ createPaymentSession(request: GatewaySessionRequest): Promise<GatewaySessionResult>
34
+ }
35
+
36
+ type ContainerLike = {
37
+ hasRegistration?: (name: string) => boolean
38
+ resolve: (name: string) => unknown
39
+ }
40
+
41
+ export const PAYMENT_GATEWAY_SERVICE = 'paymentGatewayService'
42
+
43
+ export function isPaymentGatewayAvailable(container: ContainerLike): boolean {
44
+ return typeof container.hasRegistration === 'function' && container.hasRegistration(PAYMENT_GATEWAY_SERVICE)
45
+ }
46
+
47
+ /** The gateway service, or null when the `payment_gateways` module isn't installed. */
48
+ export function resolvePaymentGateway(container: ContainerLike): TopupPaymentGateway | null {
49
+ if (!isPaymentGatewayAvailable(container)) return null
50
+ const service = container.resolve(PAYMENT_GATEWAY_SERVICE) as Partial<TopupPaymentGateway> | null
51
+ return service && typeof service.createPaymentSession === 'function' ? (service as TopupPaymentGateway) : null
52
+ }
@@ -0,0 +1,228 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
3
+ import { BillingPayment, BillingPaymentImport } from '../data/entities'
4
+ import { withAccountLock } from './accountLock'
5
+ import {
6
+ allocate,
7
+ applyAccountCredit,
8
+ emptyEffects,
9
+ loadOpenReceivables,
10
+ mergeEffects,
11
+ toCents,
12
+ type AllocationEffects,
13
+ type ArScope,
14
+ } from './allocation'
15
+ import { parsePaymentCsv, sourceRefsForRows, type PaymentCsvRow } from './paymentCsv'
16
+ import { extractInvoiceTokens, matchPayment, type MatchDecision, type MatchableDocument } from './paymentMatcher'
17
+
18
+ /**
19
+ * CSV payment import (Spec C, C-D4):
20
+ *
21
+ * 1. parse (row errors are reported, never fatal);
22
+ * 2. duplicate guard — one query for the file's source refs (P-D7);
23
+ * 3. one batched invoice-number lookup for every token of the file (P-D6);
24
+ * 4. create the import batch + one `BillingPayment` per new row;
25
+ * 5. per account, inside `withAccountLock`: allocate to the referenced
26
+ * invoices (oldest due first), then spend any remainder as credit on the
27
+ * account's other open receivables (D12);
28
+ * 6. finalise the batch counts from the payments' final statuses.
29
+ *
30
+ * Returns the summary plus the allocation effects; the caller emits events
31
+ * after everything committed.
32
+ */
33
+
34
+ export type ImportCounts = {
35
+ allocated: number
36
+ partially_allocated: number
37
+ matched: number
38
+ unmatched: number
39
+ duplicate: number
40
+ rejected: number
41
+ }
42
+
43
+ export type ImportSummary = {
44
+ importId: string
45
+ fileName: string
46
+ rowCount: number
47
+ counts: ImportCounts
48
+ errors: Array<{ row: number; message: string }>
49
+ }
50
+
51
+ const MAX_STORED_ERRORS = 200
52
+
53
+ async function existingSourceRefs(em: EntityManager, scope: ArScope, refs: string[]): Promise<Set<string>> {
54
+ if (refs.length === 0) return new Set()
55
+ const rows = (await em.execute(
56
+ `SELECT source_ref FROM billing_payments
57
+ WHERE tenant_id = ? AND organization_id = ? AND deleted_at IS NULL AND source_ref = ANY(?::text[])`,
58
+ [scope.tenantId, scope.organizationId, `{${refs.map((r) => `"${r}"`).join(',')}}`],
59
+ )) as unknown as Array<{ source_ref: string }>
60
+ return new Set((rows ?? []).map((row) => row.source_ref))
61
+ }
62
+
63
+ /** Batched lookup of every number-like token of the file (one query). */
64
+ export async function lookupDocumentsByNumber(
65
+ em: EntityManager,
66
+ scope: ArScope,
67
+ references: Array<string | null>,
68
+ ): Promise<Map<string, MatchableDocument>> {
69
+ const tokens = [...new Set(references.flatMap((ref) => extractInvoiceTokens(ref)))]
70
+ if (tokens.length === 0) return new Map()
71
+ const rows = (await em.execute(
72
+ `SELECT i.id, i.number, i.bill_account_id, a.billing_mode, i.currency_code, i.document_type, i.status, i.outstanding
73
+ FROM billing_invoices i
74
+ JOIN billing_accounts a ON a.id = i.bill_account_id AND a.tenant_id = i.tenant_id
75
+ WHERE i.tenant_id = ? AND i.organization_id = ? AND i.deleted_at IS NULL AND i.test_run = false
76
+ AND i.series IS NOT NULL
77
+ AND upper(i.number) = ANY(?::text[])`,
78
+ [scope.tenantId, scope.organizationId, `{${tokens.map((t) => `"${t.replace(/"/g, '')}"`).join(',')}}`],
79
+ )) as unknown as Array<{
80
+ id: string
81
+ number: string
82
+ bill_account_id: string
83
+ billing_mode: string
84
+ currency_code: string
85
+ document_type: string
86
+ status: string
87
+ outstanding: string
88
+ }>
89
+ const map = new Map<string, MatchableDocument>()
90
+ for (const row of rows ?? []) {
91
+ map.set(row.number.toUpperCase(), {
92
+ invoiceId: row.id,
93
+ number: row.number,
94
+ billAccountId: row.bill_account_id,
95
+ billingMode: row.billing_mode,
96
+ currencyCode: row.currency_code,
97
+ documentType: row.document_type,
98
+ status: row.status,
99
+ outstanding: row.outstanding,
100
+ })
101
+ }
102
+ return map
103
+ }
104
+
105
+ type Planned = { payment: BillingPayment; decision: MatchDecision }
106
+
107
+ /** Allocates one matched payment inside the account lock (referenced invoices first, then credit). */
108
+ async function settlePayment(tem: EntityManager, scope: ArScope, planned: Planned): Promise<AllocationEffects> {
109
+ const effects = emptyEffects()
110
+ const { payment, decision } = planned
111
+ if (decision.outcome === 'unmatched') return effects
112
+ const account = { id: decision.billAccountId, currencyCode: payment.currencyCode }
113
+ const source = { type: 'payment' as const, id: payment.id }
114
+ if (decision.outcome === 'allocate') {
115
+ let available = toCents(payment.amount)
116
+ const receivables = await loadOpenReceivables(tem, scope, account, decision.invoiceIds)
117
+ for (const receivable of receivables) {
118
+ if (available <= 0) break
119
+ const take = Math.min(available, receivable.outstandingCents)
120
+ if (take <= 0) continue
121
+ mergeEffects(effects, await allocate(tem, scope, { source, invoiceId: receivable.id, amountCents: take, by: 'auto' }))
122
+ available -= take
123
+ }
124
+ }
125
+ mergeEffects(effects, await applyAccountCredit(tem, scope, account, { onlySources: [source] }))
126
+ return effects
127
+ }
128
+
129
+ export async function importPayments(
130
+ em: EntityManager,
131
+ params: { scope: ArScope; fileName: string; content: string; userId?: string | null },
132
+ ): Promise<{ summary: ImportSummary; effects: AllocationEffects; paymentIds: string[] }> {
133
+ const { scope } = params
134
+ const parsed = parsePaymentCsv(params.content)
135
+ if (parsed.fatal) {
136
+ throw new CrudHttpError(400, { error: parsed.fatal, code: 'billing.payment_import.invalid_file' })
137
+ }
138
+ const refs = sourceRefsForRows(scope, parsed.rows)
139
+ const existing = await existingSourceRefs(em, scope, refs)
140
+ const fresh: Array<{ row: PaymentCsvRow; sourceRef: string }> = []
141
+ let duplicates = 0
142
+ parsed.rows.forEach((row, i) => {
143
+ if (existing.has(refs[i])) duplicates += 1
144
+ else fresh.push({ row, sourceRef: refs[i] })
145
+ })
146
+
147
+ const documents = await lookupDocumentsByNumber(em, scope, fresh.map((f) => f.row.reference))
148
+ const now = new Date()
149
+ const batch = em.create(BillingPaymentImport, {
150
+ organizationId: scope.organizationId,
151
+ tenantId: scope.tenantId,
152
+ fileName: params.fileName,
153
+ rowCount: parsed.rows.length + parsed.errors.length,
154
+ userId: params.userId ?? null,
155
+ createdAt: now,
156
+ updatedAt: now,
157
+ })
158
+ em.persist(batch)
159
+ await em.flush()
160
+
161
+ const planned: Planned[] = fresh.map(({ row, sourceRef }) => {
162
+ const decision = matchPayment({ reference: row.reference, currency: row.currency }, documents)
163
+ const payment = em.create(BillingPayment, {
164
+ organizationId: scope.organizationId,
165
+ tenantId: scope.tenantId,
166
+ receivedAt: new Date(`${row.receivedAt}T00:00:00Z`),
167
+ amount: row.amount,
168
+ currencyCode: row.currency,
169
+ reference: row.reference,
170
+ payerName: row.payerName,
171
+ payerAccount: row.payerAccount,
172
+ source: 'csv',
173
+ importId: batch.id,
174
+ billAccountId: decision.billAccountId,
175
+ status: decision.outcome === 'unmatched' ? 'unmatched' : 'matched',
176
+ unallocatedAmount: row.amount,
177
+ matchReason: decision.outcome === 'allocate' ? null : decision.reason,
178
+ sourceRef,
179
+ metadata: { csv_line: row.line },
180
+ createdAt: now,
181
+ updatedAt: now,
182
+ })
183
+ em.persist(payment)
184
+ return { payment, decision }
185
+ })
186
+ await em.flush()
187
+
188
+ // Settle per account, one lock per account (operations queue per account).
189
+ const effects = emptyEffects()
190
+ const byAccount = new Map<string, Planned[]>()
191
+ for (const item of planned) {
192
+ if (item.decision.outcome === 'unmatched') continue
193
+ const list = byAccount.get(item.decision.billAccountId) ?? []
194
+ list.push(item)
195
+ byAccount.set(item.decision.billAccountId, list)
196
+ }
197
+ for (const [billAccountId, items] of byAccount) {
198
+ await withAccountLock(em, { tenantId: scope.tenantId, billAccountId }, async (tem) => {
199
+ for (const item of items) mergeEffects(effects, await settlePayment(tem, scope, item))
200
+ })
201
+ }
202
+
203
+ const paymentIds = planned.map((p) => p.payment.id)
204
+ const statuses = paymentIds.length
205
+ ? ((await em.execute(
206
+ `SELECT status, COUNT(*)::int AS n FROM billing_payments
207
+ WHERE tenant_id = ? AND organization_id = ? AND import_id = ? GROUP BY status`,
208
+ [scope.tenantId, scope.organizationId, batch.id],
209
+ )) as unknown as Array<{ status: string; n: number }>)
210
+ : []
211
+ const count = (status: string) => Number(statuses.find((s) => s.status === status)?.n ?? 0)
212
+ const counts: ImportCounts = {
213
+ allocated: count('allocated'),
214
+ partially_allocated: count('partially_allocated'),
215
+ matched: count('matched'),
216
+ unmatched: count('unmatched'),
217
+ duplicate: duplicates,
218
+ rejected: parsed.errors.length,
219
+ }
220
+ const errors = parsed.errors.slice(0, MAX_STORED_ERRORS)
221
+ await em.nativeUpdate(BillingPaymentImport, { id: batch.id } as never, { counts, errors, updatedAt: new Date() } as never)
222
+
223
+ return {
224
+ summary: { importId: batch.id, fileName: params.fileName, rowCount: batch.rowCount, counts, errors },
225
+ effects,
226
+ paymentIds,
227
+ }
228
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Payment auto-matcher (Spec C, C-D1) — pure.
3
+ *
4
+ * Matching uses ONLY invoice numbers found in the payment reference:
5
+ * 1. `extractInvoiceTokens` pulls number-like tokens out of the title;
6
+ * 2. the import looks up all tokens of the whole file in one query and
7
+ * passes the hits as a map (upper-cased number → document);
8
+ * 3. `matchPayment` decides per row.
9
+ *
10
+ * Exact, full-number, case-insensitive matches only (false positives are
11
+ * worse than misses; every auto allocation is un-allocatable anyway).
12
+ */
13
+
14
+ export type MatchableDocument = {
15
+ invoiceId: string
16
+ number: string
17
+ billAccountId: string
18
+ billingMode: string
19
+ currencyCode: string
20
+ documentType: string
21
+ status: string
22
+ outstanding: string
23
+ }
24
+
25
+ export type MatchDecision =
26
+ | {
27
+ outcome: 'allocate'
28
+ billAccountId: string
29
+ /** Referenced open receivables, in the order they appear in the title. */
30
+ invoiceIds: string[]
31
+ }
32
+ | {
33
+ /** Account known but nothing referenced is open → the amount becomes credit. */
34
+ outcome: 'credit'
35
+ billAccountId: string
36
+ reason: 'invoice_already_paid'
37
+ }
38
+ | {
39
+ outcome: 'unmatched'
40
+ billAccountId: string | null
41
+ reason: 'no_reference' | 'currency_mismatch' | 'prepaid_account'
42
+ }
43
+
44
+ const TOKEN_RE = /[A-Za-z0-9][A-Za-z0-9/_.\-]{1,62}[A-Za-z0-9]/g
45
+
46
+ /** Number-like tokens of a payment title, upper-cased, de-duplicated, in order. */
47
+ export function extractInvoiceTokens(reference: string | null | undefined): string[] {
48
+ if (!reference) return []
49
+ const tokens: string[] = []
50
+ for (const match of reference.matchAll(TOKEN_RE)) {
51
+ const token = match[0].toUpperCase()
52
+ // A number needs at least one digit; plain words are never invoice numbers.
53
+ if (!/\d/.test(token)) continue
54
+ if (!tokens.includes(token)) tokens.push(token)
55
+ }
56
+ return tokens
57
+ }
58
+
59
+ const OPEN = ['posted', 'partially_paid']
60
+ const RECEIVABLE = ['invoice', 'debit_memo']
61
+
62
+ export function matchPayment(
63
+ payment: { reference: string | null; currency: string },
64
+ documentsByNumber: ReadonlyMap<string, MatchableDocument>,
65
+ ): MatchDecision {
66
+ const hits = extractInvoiceTokens(payment.reference)
67
+ .map((token) => documentsByNumber.get(token))
68
+ .filter((doc): doc is MatchableDocument => Boolean(doc))
69
+ if (hits.length === 0) return { outcome: 'unmatched', billAccountId: null, reason: 'no_reference' }
70
+
71
+ const accounts = new Set(hits.map((doc) => doc.billAccountId))
72
+ // References to several customers are ambiguous — the operator decides.
73
+ if (accounts.size > 1) return { outcome: 'unmatched', billAccountId: null, reason: 'no_reference' }
74
+ const billAccountId = hits[0].billAccountId
75
+
76
+ // Prepaid accounts (or their top-up receipts): never allocated (C-D3).
77
+ if (hits.some((doc) => doc.billingMode === 'prepaid' || doc.documentType === 'receipt')) {
78
+ return { outcome: 'unmatched', billAccountId, reason: 'prepaid_account' }
79
+ }
80
+ if (hits.some((doc) => doc.currencyCode !== payment.currency.toUpperCase())) {
81
+ return { outcome: 'unmatched', billAccountId, reason: 'currency_mismatch' }
82
+ }
83
+ const open = hits.filter(
84
+ (doc) => RECEIVABLE.includes(doc.documentType) && OPEN.includes(doc.status) && Number.parseFloat(doc.outstanding) > 0,
85
+ )
86
+ if (open.length === 0) return { outcome: 'credit', billAccountId, reason: 'invoice_already_paid' }
87
+ return { outcome: 'allocate', billAccountId, invoiceIds: open.map((doc) => doc.invoiceId) }
88
+ }
@@ -0,0 +1,60 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { Attachment } from '@open-mercato/core/modules/attachments/data/entities'
3
+ import { StorageDriverFactory } from '@open-mercato/core/modules/attachments/lib/drivers'
4
+ import { createAttachmentFromBuffer } from '@open-mercato/core/modules/attachments/lib/createFromBuffer'
5
+
6
+ /**
7
+ * Thin adapter over core `attachments` (a platform module billing requires,
8
+ * P-D1): stores frozen invoice PDFs and reads stored files (PDFs, the
9
+ * settings logo). The only place billing touches the attachments module, so
10
+ * tests mock this file instead of storage drivers.
11
+ */
12
+
13
+ export type StoredFile = { bytes: Uint8Array; mimeType: string; fileName: string }
14
+ export type FileScope = { tenantId: string; organizationId: string }
15
+
16
+ export async function storeAttachment(
17
+ em: EntityManager,
18
+ scope: FileScope,
19
+ params: { entityId: string; recordId: string; fileName: string; mimeType: string; bytes: Uint8Array },
20
+ ): Promise<string> {
21
+ const created = await createAttachmentFromBuffer({
22
+ em,
23
+ tenantId: scope.tenantId,
24
+ organizationId: scope.organizationId,
25
+ entityId: params.entityId,
26
+ recordId: params.recordId,
27
+ fileName: params.fileName,
28
+ mimeType: params.mimeType,
29
+ buffer: Buffer.from(params.bytes),
30
+ })
31
+ return created.id
32
+ }
33
+
34
+ /** Reads a stored attachment of this scope; null when missing or unreadable. */
35
+ export async function readAttachment(em: EntityManager, scope: FileScope, attachmentId: string): Promise<StoredFile | null> {
36
+ const attachment = await em.findOne(Attachment, {
37
+ id: attachmentId,
38
+ tenantId: scope.tenantId,
39
+ organizationId: scope.organizationId,
40
+ } as never)
41
+ if (!attachment) return null
42
+ try {
43
+ const driver = await new StorageDriverFactory(em).resolveForPartition(attachment.partitionCode, scope)
44
+ const { buffer } = await driver.read(attachment.partitionCode, attachment.storagePath)
45
+ return {
46
+ bytes: new Uint8Array(buffer),
47
+ mimeType: attachment.mimeType ?? 'application/octet-stream',
48
+ fileName: attachment.fileName,
49
+ }
50
+ } catch (err) {
51
+ console.warn('[billing.pdf] attachment could not be read', attachmentId, err)
52
+ return null
53
+ }
54
+ }
55
+
56
+ /** Attachment id from an `/api/attachments/file/<id>` (or image) URL — e.g. `Organization.logo_url`. */
57
+ export function attachmentIdFromUrl(url: string | null | undefined): string | null {
58
+ const match = /\/api\/attachments\/(?:file|image)\/([0-9a-f-]{36})/i.exec(url ?? '')
59
+ return match ? match[1] : null
60
+ }