@dainamite/billing 0.19.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (614) hide show
  1. package/CHANGELOG.md +172 -0
  2. package/README.md +14 -0
  3. package/dist/modules/billing/acl.js +13 -1
  4. package/dist/modules/billing/acl.js.map +2 -2
  5. package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
  6. package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
  7. package/dist/modules/billing/api/accounts/route.js +46 -4
  8. package/dist/modules/billing/api/accounts/route.js.map +2 -2
  9. package/dist/modules/billing/api/export/account/[id]/route.js +16 -12
  10. package/dist/modules/billing/api/export/account/[id]/route.js.map +2 -2
  11. package/dist/modules/billing/api/invoices/[id]/memos/route.js +54 -0
  12. package/dist/modules/billing/api/invoices/[id]/memos/route.js.map +7 -0
  13. package/dist/modules/billing/api/invoices/[id]/pdf/route.js +69 -0
  14. package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
  15. package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
  16. package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
  17. package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
  18. package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
  19. package/dist/modules/billing/api/invoices/add-line/route.js +3 -3
  20. package/dist/modules/billing/api/invoices/add-line/route.js.map +2 -2
  21. package/dist/modules/billing/api/invoices/delete-draft/route.js +33 -0
  22. package/dist/modules/billing/api/invoices/delete-draft/route.js.map +7 -0
  23. package/dist/modules/billing/api/invoices/edit-line/route.js +3 -3
  24. package/dist/modules/billing/api/invoices/edit-line/route.js.map +2 -2
  25. package/dist/modules/billing/api/invoices/post/route.js +2 -2
  26. package/dist/modules/billing/api/invoices/post/route.js.map +2 -2
  27. package/dist/modules/billing/api/invoices/remove-line/route.js +3 -3
  28. package/dist/modules/billing/api/invoices/remove-line/route.js.map +2 -2
  29. package/dist/modules/billing/api/invoices/route.js +45 -46
  30. package/dist/modules/billing/api/invoices/route.js.map +2 -2
  31. package/dist/modules/billing/api/items/bulk/route.js +1 -1
  32. package/dist/modules/billing/api/items/bulk/route.js.map +2 -2
  33. package/dist/modules/billing/api/items/change-price/route.js +44 -0
  34. package/dist/modules/billing/api/items/change-price/route.js.map +7 -0
  35. package/dist/modules/billing/api/items/route.js +19 -7
  36. package/dist/modules/billing/api/items/route.js.map +2 -2
  37. package/dist/modules/billing/api/payment-imports/[id]/route.js +49 -0
  38. package/dist/modules/billing/api/payment-imports/[id]/route.js.map +7 -0
  39. package/dist/modules/billing/api/payment-imports/route.js +57 -0
  40. package/dist/modules/billing/api/payment-imports/route.js.map +7 -0
  41. package/dist/modules/billing/api/payments/[id]/route.js +60 -0
  42. package/dist/modules/billing/api/payments/[id]/route.js.map +7 -0
  43. package/dist/modules/billing/api/payments/allocate/route.js +35 -0
  44. package/dist/modules/billing/api/payments/allocate/route.js.map +7 -0
  45. package/dist/modules/billing/api/payments/assign/route.js +36 -0
  46. package/dist/modules/billing/api/payments/assign/route.js.map +7 -0
  47. package/dist/modules/billing/api/payments/convert-to-topup/route.js +35 -0
  48. package/dist/modules/billing/api/payments/convert-to-topup/route.js.map +7 -0
  49. package/dist/modules/billing/api/payments/ignore/route.js +35 -0
  50. package/dist/modules/billing/api/payments/ignore/route.js.map +7 -0
  51. package/dist/modules/billing/api/payments/import/route.js +63 -0
  52. package/dist/modules/billing/api/payments/import/route.js.map +7 -0
  53. package/dist/modules/billing/api/payments/restore/route.js +35 -0
  54. package/dist/modules/billing/api/payments/restore/route.js.map +7 -0
  55. package/dist/modules/billing/api/payments/route.js +114 -0
  56. package/dist/modules/billing/api/payments/route.js.map +7 -0
  57. package/dist/modules/billing/api/payments/unallocate/route.js +35 -0
  58. package/dist/modules/billing/api/payments/unallocate/route.js.map +7 -0
  59. package/dist/modules/billing/api/payments/unassign/route.js +35 -0
  60. package/dist/modules/billing/api/payments/unassign/route.js.map +7 -0
  61. package/dist/modules/billing/api/run-outcomes/route.js +44 -7
  62. package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
  63. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js +61 -0
  64. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js.map +7 -0
  65. package/dist/modules/billing/api/runs/[id]/route.js +53 -0
  66. package/dist/modules/billing/api/runs/[id]/route.js.map +7 -0
  67. package/dist/modules/billing/api/runs/retry-failed/route.js +3 -3
  68. package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
  69. package/dist/modules/billing/api/runs/route.js +11 -5
  70. package/dist/modules/billing/api/runs/route.js.map +2 -2
  71. package/dist/modules/billing/api/settings/number-preview/route.js +42 -0
  72. package/dist/modules/billing/api/settings/number-preview/route.js.map +7 -0
  73. package/dist/modules/billing/api/settings/route.js +92 -0
  74. package/dist/modules/billing/api/settings/route.js.map +7 -0
  75. package/dist/modules/billing/api/statements/route.js +3 -0
  76. package/dist/modules/billing/api/statements/route.js.map +2 -2
  77. package/dist/modules/billing/api/test-invoices/wipe/route.js +2 -2
  78. package/dist/modules/billing/api/test-invoices/wipe/route.js.map +2 -2
  79. package/dist/modules/billing/backend/billing/accounts/[id]/page.js +121 -232
  80. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +2 -2
  81. package/dist/modules/billing/backend/billing/accounts/create/page.js +6 -0
  82. package/dist/modules/billing/backend/billing/accounts/create/page.js.map +2 -2
  83. package/dist/modules/billing/backend/billing/accounts/page.js +63 -4
  84. package/dist/modules/billing/backend/billing/accounts/page.js.map +2 -2
  85. package/dist/modules/billing/backend/billing/invoices/[id]/page.js +297 -65
  86. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +3 -3
  87. package/dist/modules/billing/backend/billing/invoices/page.js +6 -172
  88. package/dist/modules/billing/backend/billing/invoices/page.js.map +2 -2
  89. package/dist/modules/billing/backend/billing/items/[id]/page.js +51 -7
  90. package/dist/modules/billing/backend/billing/items/[id]/page.js.map +2 -2
  91. package/dist/modules/billing/backend/billing/items/create/page.js +9 -6
  92. package/dist/modules/billing/backend/billing/items/create/page.js.map +2 -2
  93. package/dist/modules/billing/backend/billing/items/page.js +8 -2
  94. package/dist/modules/billing/backend/billing/items/page.js.map +2 -2
  95. package/dist/modules/billing/backend/billing/payments/[id]/page.js +297 -0
  96. package/dist/modules/billing/backend/billing/payments/[id]/page.js.map +7 -0
  97. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js +21 -0
  98. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js.map +7 -0
  99. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js +74 -0
  100. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js.map +7 -0
  101. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js +21 -0
  102. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js.map +7 -0
  103. package/dist/modules/billing/backend/billing/payments/page.js +58 -0
  104. package/dist/modules/billing/backend/billing/payments/page.js.map +7 -0
  105. package/dist/modules/billing/backend/billing/payments/page.meta.js +18 -0
  106. package/dist/modules/billing/backend/billing/payments/page.meta.js.map +7 -0
  107. package/dist/modules/billing/backend/billing/runs/[id]/page.js +49 -14
  108. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +2 -2
  109. package/dist/modules/billing/backend/billing/runs/page.js +58 -7
  110. package/dist/modules/billing/backend/billing/runs/page.js.map +2 -2
  111. package/dist/modules/billing/backend/billing/statements/[id]/page.js +61 -6
  112. package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +2 -2
  113. package/dist/modules/billing/backend/billing/statements/page.js +1 -1
  114. package/dist/modules/billing/backend/billing/statements/page.js.map +2 -2
  115. package/dist/modules/billing/backend/billing/topups/page.js +1 -1
  116. package/dist/modules/billing/backend/billing/topups/page.js.map +2 -2
  117. package/dist/modules/billing/backend/billing/transactions/page.js +1 -1
  118. package/dist/modules/billing/backend/billing/transactions/page.js.map +2 -2
  119. package/dist/modules/billing/backend/billing/usage/create/page.js +3 -2
  120. package/dist/modules/billing/backend/billing/usage/create/page.js.map +2 -2
  121. package/dist/modules/billing/backend/config/billing/page.js +472 -0
  122. package/dist/modules/billing/backend/config/billing/page.js.map +7 -0
  123. package/dist/modules/billing/backend/config/billing/page.meta.js +19 -0
  124. package/dist/modules/billing/backend/config/billing/page.meta.js.map +7 -0
  125. package/dist/modules/billing/ce.js.map +2 -2
  126. package/dist/modules/billing/cli.js +22 -1
  127. package/dist/modules/billing/cli.js.map +2 -2
  128. package/dist/modules/billing/commands/accounts.js +6 -0
  129. package/dist/modules/billing/commands/accounts.js.map +2 -2
  130. package/dist/modules/billing/commands/index.js +3 -0
  131. package/dist/modules/billing/commands/index.js.map +2 -2
  132. package/dist/modules/billing/commands/invoices.js +272 -170
  133. package/dist/modules/billing/commands/invoices.js.map +2 -2
  134. package/dist/modules/billing/commands/items.js +108 -0
  135. package/dist/modules/billing/commands/items.js.map +2 -2
  136. package/dist/modules/billing/commands/memos.js +179 -0
  137. package/dist/modules/billing/commands/memos.js.map +7 -0
  138. package/dist/modules/billing/commands/payments.js +379 -0
  139. package/dist/modules/billing/commands/payments.js.map +7 -0
  140. package/dist/modules/billing/commands/runs.js +154 -7
  141. package/dist/modules/billing/commands/runs.js.map +2 -2
  142. package/dist/modules/billing/commands/settings.js +59 -0
  143. package/dist/modules/billing/commands/settings.js.map +7 -0
  144. package/dist/modules/billing/commands/topups.js +9 -2
  145. package/dist/modules/billing/commands/topups.js.map +2 -2
  146. package/dist/modules/billing/commands/transactions.js +2 -2
  147. package/dist/modules/billing/commands/transactions.js.map +2 -2
  148. package/dist/modules/billing/components/AccountForm.js +149 -42
  149. package/dist/modules/billing/components/AccountForm.js.map +2 -2
  150. package/dist/modules/billing/components/AccountInvoicesSection.js +64 -0
  151. package/dist/modules/billing/components/AccountInvoicesSection.js.map +7 -0
  152. package/dist/modules/billing/components/AccountPaymentsSection.js +43 -0
  153. package/dist/modules/billing/components/AccountPaymentsSection.js.map +7 -0
  154. package/dist/modules/billing/components/AccountPicker.js +56 -0
  155. package/dist/modules/billing/components/AccountPicker.js.map +7 -0
  156. package/dist/modules/billing/components/AllocatePaymentDialog.js +134 -0
  157. package/dist/modules/billing/components/AllocatePaymentDialog.js.map +7 -0
  158. package/dist/modules/billing/components/BillingDataTable.js +21 -0
  159. package/dist/modules/billing/components/BillingDataTable.js.map +7 -0
  160. package/dist/modules/billing/components/BillingSettingsLink.js +19 -0
  161. package/dist/modules/billing/components/BillingSettingsLink.js.map +7 -0
  162. package/dist/modules/billing/components/ChangeItemPriceDialog.js +158 -0
  163. package/dist/modules/billing/components/ChangeItemPriceDialog.js.map +7 -0
  164. package/dist/modules/billing/components/CreateMemoDialog.js +200 -0
  165. package/dist/modules/billing/components/CreateMemoDialog.js.map +7 -0
  166. package/dist/modules/billing/components/CustomerLabel.js +33 -0
  167. package/dist/modules/billing/components/CustomerLabel.js.map +7 -0
  168. package/dist/modules/billing/components/InvoicesTable.js +270 -0
  169. package/dist/modules/billing/components/InvoicesTable.js.map +7 -0
  170. package/dist/modules/billing/components/ItemForm.js +176 -44
  171. package/dist/modules/billing/components/ItemForm.js.map +2 -2
  172. package/dist/modules/billing/components/ItemPriceField.js +46 -0
  173. package/dist/modules/billing/components/ItemPriceField.js.map +7 -0
  174. package/dist/modules/billing/components/LineFormDialog.js +25 -2
  175. package/dist/modules/billing/components/LineFormDialog.js.map +2 -2
  176. package/dist/modules/billing/components/ManualPaymentDialog.js +122 -0
  177. package/dist/modules/billing/components/ManualPaymentDialog.js.map +7 -0
  178. package/dist/modules/billing/components/MoneyInput.js +14 -0
  179. package/dist/modules/billing/components/MoneyInput.js.map +7 -0
  180. package/dist/modules/billing/components/PaymentUploadDialog.js +115 -0
  181. package/dist/modules/billing/components/PaymentUploadDialog.js.map +7 -0
  182. package/dist/modules/billing/components/PaymentsTable.js +171 -0
  183. package/dist/modules/billing/components/PaymentsTable.js.map +7 -0
  184. package/dist/modules/billing/components/PostRunDraftsButton.js +105 -0
  185. package/dist/modules/billing/components/PostRunDraftsButton.js.map +7 -0
  186. package/dist/modules/billing/components/PrepaidPanel.js +14 -4
  187. package/dist/modules/billing/components/PrepaidPanel.js.map +2 -2
  188. package/dist/modules/billing/components/RunCyclesSection.js +209 -0
  189. package/dist/modules/billing/components/RunCyclesSection.js.map +7 -0
  190. package/dist/modules/billing/components/TestRunActions.js +127 -0
  191. package/dist/modules/billing/components/TestRunActions.js.map +7 -0
  192. package/dist/modules/billing/components/account/AccountItemsTab.js +157 -0
  193. package/dist/modules/billing/components/account/AccountItemsTab.js.map +7 -0
  194. package/dist/modules/billing/components/account/AccountRunBillingDialog.js +122 -0
  195. package/dist/modules/billing/components/account/AccountRunBillingDialog.js.map +7 -0
  196. package/dist/modules/billing/components/account/AccountRunsTab.js +152 -0
  197. package/dist/modules/billing/components/account/AccountRunsTab.js.map +7 -0
  198. package/dist/modules/billing/components/account/AccountStatementsTab.js +98 -0
  199. package/dist/modules/billing/components/account/AccountStatementsTab.js.map +7 -0
  200. package/dist/modules/billing/components/account/AccountSummaryCards.js +125 -0
  201. package/dist/modules/billing/components/account/AccountSummaryCards.js.map +7 -0
  202. package/dist/modules/billing/components/account/AccountUsageTab.js +91 -0
  203. package/dist/modules/billing/components/account/AccountUsageTab.js.map +7 -0
  204. package/dist/modules/billing/components/account/AccountView.js +73 -0
  205. package/dist/modules/billing/components/account/AccountView.js.map +7 -0
  206. package/dist/modules/billing/components/account/accountRow.js +39 -0
  207. package/dist/modules/billing/components/account/accountRow.js.map +7 -0
  208. package/dist/modules/billing/components/customerNames.js +42 -0
  209. package/dist/modules/billing/components/customerNames.js.map +7 -0
  210. package/dist/modules/billing/data/entities.js +615 -59
  211. package/dist/modules/billing/data/entities.js.map +2 -2
  212. package/dist/modules/billing/data/entityIds.js +10 -1
  213. package/dist/modules/billing/data/entityIds.js.map +2 -2
  214. package/dist/modules/billing/data/validators.js +269 -15
  215. package/dist/modules/billing/data/validators.js.map +2 -2
  216. package/dist/modules/billing/events.js +70 -0
  217. package/dist/modules/billing/events.js.map +2 -2
  218. package/dist/modules/billing/i18n/en.json +457 -21
  219. package/dist/modules/billing/i18n/pl.json +457 -21
  220. package/dist/modules/billing/index.js +6 -2
  221. package/dist/modules/billing/index.js.map +2 -2
  222. package/dist/modules/billing/lib/accountFinancials.js +58 -0
  223. package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
  224. package/dist/modules/billing/lib/accountLock.js +14 -0
  225. package/dist/modules/billing/lib/accountLock.js.map +7 -0
  226. package/dist/modules/billing/lib/accountStatus.js +9 -7
  227. package/dist/modules/billing/lib/accountStatus.js.map +2 -2
  228. package/dist/modules/billing/lib/accountSummary.js +106 -0
  229. package/dist/modules/billing/lib/accountSummary.js.map +7 -0
  230. package/dist/modules/billing/lib/allocation.js +356 -0
  231. package/dist/modules/billing/lib/allocation.js.map +7 -0
  232. package/dist/modules/billing/lib/allocationEvents.js +22 -0
  233. package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
  234. package/dist/modules/billing/lib/billPeriod.js +15 -1
  235. package/dist/modules/billing/lib/billPeriod.js.map +2 -2
  236. package/dist/modules/billing/lib/billRunEngine.js +241 -106
  237. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  238. package/dist/modules/billing/lib/billRunLabel.js.map +1 -1
  239. package/dist/modules/billing/lib/billingSettings.js +116 -0
  240. package/dist/modules/billing/lib/billingSettings.js.map +7 -0
  241. package/dist/modules/billing/lib/commandRoute.js +81 -0
  242. package/dist/modules/billing/lib/commandRoute.js.map +7 -0
  243. package/dist/modules/billing/lib/customerLookup.js +97 -0
  244. package/dist/modules/billing/lib/customerLookup.js.map +7 -0
  245. package/dist/modules/billing/lib/dateOnly.js +13 -0
  246. package/dist/modules/billing/lib/dateOnly.js.map +7 -0
  247. package/dist/modules/billing/lib/documentParties.js +16 -0
  248. package/dist/modules/billing/lib/documentParties.js.map +7 -0
  249. package/dist/modules/billing/lib/documentTypes.js +27 -0
  250. package/dist/modules/billing/lib/documentTypes.js.map +7 -0
  251. package/dist/modules/billing/lib/dueDate.js +18 -0
  252. package/dist/modules/billing/lib/dueDate.js.map +7 -0
  253. package/dist/modules/billing/lib/iban.js +29 -0
  254. package/dist/modules/billing/lib/iban.js.map +7 -0
  255. package/dist/modules/billing/lib/invoiceDelivery.js +131 -0
  256. package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
  257. package/dist/modules/billing/lib/invoiceLanguages.js +16 -0
  258. package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
  259. package/dist/modules/billing/lib/invoiceWriter.js +107 -99
  260. package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
  261. package/dist/modules/billing/lib/itemBillingState.js +27 -0
  262. package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
  263. package/dist/modules/billing/lib/itemPriceChange.js +18 -0
  264. package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
  265. package/dist/modules/billing/lib/itemSelector.js.map +2 -2
  266. package/dist/modules/billing/lib/lineAmounts.js +32 -0
  267. package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
  268. package/dist/modules/billing/lib/memoGuards.js +25 -0
  269. package/dist/modules/billing/lib/memoGuards.js.map +7 -0
  270. package/dist/modules/billing/lib/navigation.js +48 -0
  271. package/dist/modules/billing/lib/navigation.js.map +7 -0
  272. package/dist/modules/billing/lib/numbering.js +120 -0
  273. package/dist/modules/billing/lib/numbering.js.map +7 -0
  274. package/dist/modules/billing/lib/overdue.js +36 -0
  275. package/dist/modules/billing/lib/overdue.js.map +7 -0
  276. package/dist/modules/billing/lib/paymentCsv.js +125 -0
  277. package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
  278. package/dist/modules/billing/lib/paymentGateway.js +15 -0
  279. package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
  280. package/dist/modules/billing/lib/paymentImport.js +164 -0
  281. package/dist/modules/billing/lib/paymentImport.js.map +7 -0
  282. package/dist/modules/billing/lib/paymentMatcher.js +36 -0
  283. package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
  284. package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
  285. package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
  286. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
  287. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
  288. package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
  289. package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
  290. package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
  291. package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
  292. package/dist/modules/billing/lib/pdf/invoiceTemplate.js +274 -0
  293. package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
  294. package/dist/modules/billing/lib/prepaidConfig.js +17 -25
  295. package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
  296. package/dist/modules/billing/lib/prepaidConsume.js +26 -7
  297. package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
  298. package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
  299. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
  300. package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
  301. package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
  302. package/dist/modules/billing/lib/seeds/demo.js +51 -3
  303. package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
  304. package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
  305. package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
  306. package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
  307. package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
  308. package/dist/modules/billing/lib/servicePeriods.js +121 -0
  309. package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
  310. package/dist/modules/billing/lib/tenantLock.js.map +1 -1
  311. package/dist/modules/billing/lib/testRunCleanup.js +24 -0
  312. package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
  313. package/dist/modules/billing/lib/topupCapture.js +2 -2
  314. package/dist/modules/billing/lib/topupCapture.js.map +2 -2
  315. package/dist/modules/billing/lib/topupReceipt.js +58 -79
  316. package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
  317. package/dist/modules/billing/lib/usageRater.js.map +1 -1
  318. package/dist/modules/billing/lib/usageRunner.js +14 -6
  319. package/dist/modules/billing/lib/usageRunner.js.map +2 -2
  320. package/dist/modules/billing/lib/vat.js +39 -0
  321. package/dist/modules/billing/lib/vat.js.map +7 -0
  322. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
  323. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
  324. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
  325. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
  326. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
  327. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
  328. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
  329. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
  330. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
  331. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
  332. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
  333. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
  334. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
  335. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
  336. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
  337. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
  338. package/dist/modules/billing/setup.js +7 -184
  339. package/dist/modules/billing/setup.js.map +2 -2
  340. package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
  341. package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
  342. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
  343. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
  344. package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
  345. package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
  346. package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
  347. package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
  348. package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
  349. package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
  350. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
  351. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
  352. package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
  353. package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
  354. package/dist/modules/billing/workers/reap-stale-bill-runs.js +2 -2
  355. package/dist/modules/billing/workers/reap-stale-bill-runs.js.map +2 -2
  356. package/generated/entities/billing_account/index.ts +26 -0
  357. package/generated/entities/billing_account_balance/index.ts +13 -0
  358. package/generated/entities/billing_account_transaction/index.ts +21 -0
  359. package/generated/entities/billing_account_usage/index.ts +18 -0
  360. package/generated/entities/billing_allocation/index.ts +18 -0
  361. package/generated/entities/billing_document_sequence/index.ts +11 -0
  362. package/generated/entities/billing_draft_invoice_edit/index.ts +14 -0
  363. package/generated/entities/billing_invoice/index.ts +38 -0
  364. package/generated/entities/billing_invoice_line/index.ts +24 -0
  365. package/generated/entities/billing_item/index.ts +26 -0
  366. package/generated/entities/billing_payment/index.ts +25 -0
  367. package/generated/entities/billing_payment_import/index.ts +14 -0
  368. package/generated/entities/billing_run/index.ts +20 -0
  369. package/generated/entities/billing_run_outcome/index.ts +18 -0
  370. package/generated/entities/billing_settings/index.ts +26 -0
  371. package/generated/entities/billing_statement/index.ts +22 -0
  372. package/generated/entities/billing_topup/index.ts +20 -0
  373. package/generated/entities.ids.generated.ts +35 -0
  374. package/package.json +15 -10
  375. package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +170 -0
  376. package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
  377. package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
  378. package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
  379. package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
  380. package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
  381. package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
  382. package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
  383. package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
  384. package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
  385. package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
  386. package/src/modules/billing/__tests__/commands/invoices.test.ts +437 -355
  387. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
  388. package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
  389. package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
  390. package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
  391. package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
  392. package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
  393. package/src/modules/billing/__tests__/entityRegistry.test.ts +326 -0
  394. package/src/modules/billing/__tests__/events.test.ts +142 -0
  395. package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
  396. package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
  397. package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
  398. package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
  399. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +231 -20
  400. package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
  401. package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
  402. package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
  403. package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
  404. package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +189 -0
  405. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
  406. package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
  407. package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
  408. package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
  409. package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
  410. package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
  411. package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
  412. package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
  413. package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
  414. package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
  415. package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +199 -0
  416. package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
  417. package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
  418. package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
  419. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
  420. package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
  421. package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
  422. package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
  423. package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
  424. package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
  425. package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
  426. package/src/modules/billing/__tests__/setup.test.ts +13 -4
  427. package/src/modules/billing/__tests__/validators.test.ts +193 -3
  428. package/src/modules/billing/acl.ts +12 -0
  429. package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
  430. package/src/modules/billing/api/accounts/route.ts +58 -4
  431. package/src/modules/billing/api/export/account/[id]/route.ts +20 -18
  432. package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
  433. package/src/modules/billing/api/invoices/[id]/pdf/route.ts +79 -0
  434. package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
  435. package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
  436. package/src/modules/billing/api/invoices/add-line/route.ts +3 -3
  437. package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
  438. package/src/modules/billing/api/invoices/edit-line/route.ts +4 -4
  439. package/src/modules/billing/api/invoices/post/route.ts +7 -6
  440. package/src/modules/billing/api/invoices/remove-line/route.ts +3 -3
  441. package/src/modules/billing/api/invoices/route.ts +81 -86
  442. package/src/modules/billing/api/items/bulk/route.ts +2 -1
  443. package/src/modules/billing/api/items/change-price/route.ts +54 -0
  444. package/src/modules/billing/api/items/route.ts +23 -7
  445. package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
  446. package/src/modules/billing/api/payment-imports/route.ts +57 -0
  447. package/src/modules/billing/api/payments/[id]/route.ts +63 -0
  448. package/src/modules/billing/api/payments/allocate/route.ts +35 -0
  449. package/src/modules/billing/api/payments/assign/route.ts +36 -0
  450. package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
  451. package/src/modules/billing/api/payments/ignore/route.ts +35 -0
  452. package/src/modules/billing/api/payments/import/route.ts +72 -0
  453. package/src/modules/billing/api/payments/restore/route.ts +35 -0
  454. package/src/modules/billing/api/payments/route.ts +147 -0
  455. package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
  456. package/src/modules/billing/api/payments/unassign/route.ts +35 -0
  457. package/src/modules/billing/api/run-outcomes/route.ts +50 -7
  458. package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
  459. package/src/modules/billing/api/runs/[id]/route.ts +59 -0
  460. package/src/modules/billing/api/runs/retry-failed/route.ts +4 -3
  461. package/src/modules/billing/api/runs/route.ts +17 -9
  462. package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
  463. package/src/modules/billing/api/settings/route.ts +116 -0
  464. package/src/modules/billing/api/statements/route.ts +5 -0
  465. package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
  466. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
  467. package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
  468. package/src/modules/billing/backend/billing/accounts/page.tsx +71 -5
  469. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +445 -96
  470. package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
  471. package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
  472. package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
  473. package/src/modules/billing/backend/billing/items/page.tsx +19 -3
  474. package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
  475. package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
  476. package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
  477. package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
  478. package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
  479. package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
  480. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +50 -19
  481. package/src/modules/billing/backend/billing/runs/page.tsx +60 -9
  482. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +87 -6
  483. package/src/modules/billing/backend/billing/statements/page.tsx +2 -2
  484. package/src/modules/billing/backend/billing/topups/page.tsx +2 -2
  485. package/src/modules/billing/backend/billing/transactions/page.tsx +2 -2
  486. package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
  487. package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
  488. package/src/modules/billing/backend/config/billing/page.tsx +549 -0
  489. package/src/modules/billing/ce.ts +8 -7
  490. package/src/modules/billing/cli.ts +31 -1
  491. package/src/modules/billing/commands/accounts.ts +6 -0
  492. package/src/modules/billing/commands/index.ts +3 -0
  493. package/src/modules/billing/commands/invoices.ts +371 -238
  494. package/src/modules/billing/commands/items.ts +143 -0
  495. package/src/modules/billing/commands/memos.ts +218 -0
  496. package/src/modules/billing/commands/payments.ts +428 -0
  497. package/src/modules/billing/commands/runs.ts +208 -6
  498. package/src/modules/billing/commands/settings.ts +77 -0
  499. package/src/modules/billing/commands/topups.ts +12 -3
  500. package/src/modules/billing/commands/transactions.ts +2 -2
  501. package/src/modules/billing/components/AccountForm.tsx +199 -53
  502. package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
  503. package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
  504. package/src/modules/billing/components/AccountPicker.tsx +70 -0
  505. package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
  506. package/src/modules/billing/components/BillingDataTable.tsx +34 -0
  507. package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
  508. package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
  509. package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
  510. package/src/modules/billing/components/CustomerLabel.tsx +53 -0
  511. package/src/modules/billing/components/InvoicesTable.tsx +344 -0
  512. package/src/modules/billing/components/ItemForm.tsx +233 -44
  513. package/src/modules/billing/components/ItemPriceField.tsx +81 -0
  514. package/src/modules/billing/components/LineFormDialog.tsx +25 -1
  515. package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
  516. package/src/modules/billing/components/MoneyInput.tsx +26 -0
  517. package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
  518. package/src/modules/billing/components/PaymentsTable.tsx +213 -0
  519. package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
  520. package/src/modules/billing/components/PrepaidPanel.tsx +20 -7
  521. package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
  522. package/src/modules/billing/components/TestRunActions.tsx +152 -0
  523. package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
  524. package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
  525. package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
  526. package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
  527. package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
  528. package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
  529. package/src/modules/billing/components/account/AccountView.tsx +101 -0
  530. package/src/modules/billing/components/account/accountRow.ts +70 -0
  531. package/src/modules/billing/components/customerNames.ts +62 -0
  532. package/src/modules/billing/data/entities.ts +685 -15
  533. package/src/modules/billing/data/entityIds.ts +25 -11
  534. package/src/modules/billing/data/validators.ts +388 -8
  535. package/src/modules/billing/events.ts +82 -3
  536. package/src/modules/billing/i18n/en.json +457 -21
  537. package/src/modules/billing/i18n/pl.json +457 -21
  538. package/src/modules/billing/index.ts +6 -2
  539. package/src/modules/billing/lib/accountFinancials.ts +84 -0
  540. package/src/modules/billing/lib/accountLock.ts +32 -0
  541. package/src/modules/billing/lib/accountStatus.ts +11 -9
  542. package/src/modules/billing/lib/accountSummary.ts +172 -0
  543. package/src/modules/billing/lib/allocation.ts +515 -0
  544. package/src/modules/billing/lib/allocationEvents.ts +26 -0
  545. package/src/modules/billing/lib/billPeriod.ts +19 -0
  546. package/src/modules/billing/lib/billRunEngine.ts +362 -162
  547. package/src/modules/billing/lib/billRunLabel.ts +1 -1
  548. package/src/modules/billing/lib/billingSettings.ts +145 -0
  549. package/src/modules/billing/lib/commandRoute.ts +112 -0
  550. package/src/modules/billing/lib/customerLookup.ts +157 -0
  551. package/src/modules/billing/lib/dateOnly.ts +22 -0
  552. package/src/modules/billing/lib/documentParties.ts +23 -0
  553. package/src/modules/billing/lib/documentTypes.ts +37 -0
  554. package/src/modules/billing/lib/dueDate.ts +26 -0
  555. package/src/modules/billing/lib/iban.ts +36 -0
  556. package/src/modules/billing/lib/invoiceDelivery.ts +197 -0
  557. package/src/modules/billing/lib/invoiceLanguages.ts +34 -0
  558. package/src/modules/billing/lib/invoiceWriter.ts +162 -148
  559. package/src/modules/billing/lib/itemBillingState.ts +67 -0
  560. package/src/modules/billing/lib/itemPriceChange.ts +56 -0
  561. package/src/modules/billing/lib/itemSelector.ts +4 -1
  562. package/src/modules/billing/lib/lineAmounts.ts +61 -0
  563. package/src/modules/billing/lib/memoGuards.ts +41 -0
  564. package/src/modules/billing/lib/navigation.ts +64 -0
  565. package/src/modules/billing/lib/numbering.ts +174 -0
  566. package/src/modules/billing/lib/overdue.ts +60 -0
  567. package/src/modules/billing/lib/paymentCsv.ts +171 -0
  568. package/src/modules/billing/lib/paymentGateway.ts +52 -0
  569. package/src/modules/billing/lib/paymentImport.ts +228 -0
  570. package/src/modules/billing/lib/paymentMatcher.ts +88 -0
  571. package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
  572. package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
  573. package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
  574. package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
  575. package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
  576. package/src/modules/billing/lib/pdf/invoiceModel.ts +328 -0
  577. package/src/modules/billing/lib/pdf/invoicePdf.ts +216 -0
  578. package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
  579. package/src/modules/billing/lib/prepaidConfig.ts +27 -36
  580. package/src/modules/billing/lib/prepaidConsume.ts +29 -6
  581. package/src/modules/billing/lib/prepaidPeriodClose.ts +410 -242
  582. package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
  583. package/src/modules/billing/lib/seeds/demo.ts +79 -6
  584. package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
  585. package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
  586. package/src/modules/billing/lib/servicePeriods.ts +226 -0
  587. package/src/modules/billing/lib/tenantLock.ts +1 -1
  588. package/src/modules/billing/lib/testRunCleanup.ts +42 -0
  589. package/src/modules/billing/lib/topupCapture.ts +3 -4
  590. package/src/modules/billing/lib/topupReceipt.ts +70 -103
  591. package/src/modules/billing/lib/usageRater.ts +1 -1
  592. package/src/modules/billing/lib/usageRunner.ts +17 -5
  593. package/src/modules/billing/lib/vat.ts +56 -0
  594. package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
  595. package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
  596. package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
  597. package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
  598. package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
  599. package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
  600. package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
  601. package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
  602. package/src/modules/billing/setup.ts +11 -237
  603. package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
  604. package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
  605. package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
  606. package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
  607. package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
  608. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
  609. package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
  610. package/src/modules/billing/workers/reap-stale-bill-runs.ts +4 -4
  611. package/dist/modules/billing/lib/invoiceStatus.js +0 -38
  612. package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
  613. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
  614. package/src/modules/billing/lib/invoiceStatus.ts +0 -61
@@ -0,0 +1,216 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { BillingAccount, BillingInvoice, BillingInvoiceLine } from '../../data/entities'
3
+ import { billingEntityIds } from '../../data/entityIds'
4
+ import { buildSellerSnapshot, getBillingSettings } from '../billingSettings'
5
+ import { buildBuyerSnapshot } from '../documentParties'
6
+ import { DEFAULT_INVOICE_LANGUAGE } from '../invoiceLanguages'
7
+ import { buildInvoiceDocumentModel, type DocumentTranslate, type PdfAddress } from './invoiceModel'
8
+ import { renderInvoicePdf, type PdfLogo } from './invoiceTemplate'
9
+ import { attachmentIdFromUrl, readAttachment, storeAttachment } from './attachmentStore'
10
+
11
+ /**
12
+ * Billing document PDFs (Spec B, P-D1 / B-D5):
13
+ *
14
+ * - posted documents are rendered once and frozen as an attachment
15
+ * (`pdf_attachment_id`); later downloads stream the stored file, so the
16
+ * document never changes after issue. If freezing failed at post, the
17
+ * first download renders and freezes it (lazy fallback).
18
+ * - drafts and test documents are rendered live with a DRAFT / TEST
19
+ * watermark and never stored.
20
+ *
21
+ * Parties come from the frozen snapshots on posted documents and from the
22
+ * live account / settings on drafts. Labels use the document language
23
+ * (`invoice_language`), never the operator's UI locale.
24
+ */
25
+
26
+ export type TranslatorLoader = (language: string) => Promise<DocumentTranslate>
27
+
28
+ /** Billing i18n for a document language (en / pl, falling back to en). */
29
+ export const loadDocumentTranslator: TranslatorLoader = async (language) => {
30
+ const { loadDictionary } = await import('@open-mercato/shared/lib/i18n/server')
31
+ const { createFallbackTranslator } = await import('@open-mercato/shared/lib/i18n/translate')
32
+ const locale = language === 'pl' ? 'pl' : 'en'
33
+ const translate = createFallbackTranslator(await loadDictionary(locale))
34
+ return (key, fallback) => translate(key, fallback)
35
+ }
36
+
37
+ type Snapshot = Record<string, unknown>
38
+
39
+ function str(value: unknown): string | null {
40
+ return typeof value === 'string' && value.trim() !== '' ? value : null
41
+ }
42
+
43
+ function address(value: unknown): PdfAddress | null {
44
+ return value && typeof value === 'object' ? (value as PdfAddress) : null
45
+ }
46
+
47
+ export function pdfFileName(invoice: Pick<BillingInvoice, 'number'>): string {
48
+ return `${invoice.number.replace(/[^A-Za-z0-9._-]+/g, '-')}.pdf`
49
+ }
50
+
51
+ function isFrozenDocument(invoice: Pick<BillingInvoice, 'status' | 'testRun'>): boolean {
52
+ return invoice.status !== 'draft' && !invoice.testRun
53
+ }
54
+
55
+ async function resolveParties(em: EntityManager, invoice: BillingInvoice) {
56
+ const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }
57
+ let seller: Snapshot | null = isFrozenDocument(invoice) ? (invoice.sellerSnapshot ?? null) : null
58
+ let buyer: Snapshot | null = isFrozenDocument(invoice) ? (invoice.buyerSnapshot ?? null) : null
59
+ if (!seller || !('legal_name' in seller)) seller = buildSellerSnapshot(await getBillingSettings(em, scope))
60
+ if (!buyer) {
61
+ const account = await em.findOne(BillingAccount, { id: invoice.billAccountId, ...scope } as never)
62
+ buyer = account ? buildBuyerSnapshot(account) : { name: '' }
63
+ }
64
+ return { seller, buyer }
65
+ }
66
+
67
+ async function resolveLogo(em: EntityManager, invoice: BillingInvoice, seller: Snapshot): Promise<PdfLogo | null> {
68
+ const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }
69
+ let attachmentId = str(seller.logo_attachment_id)
70
+ if (!attachmentId) {
71
+ // Fallback: the organization logo, only when it is one of our own
72
+ // attachments (never fetch arbitrary URLs server-side).
73
+ const rows = (await em.execute(`SELECT logo_url FROM organizations WHERE id = ? LIMIT 1`, [
74
+ invoice.organizationId,
75
+ ])) as unknown as Array<{ logo_url: string | null }>
76
+ attachmentId = attachmentIdFromUrl(rows?.[0]?.logo_url)
77
+ }
78
+ if (!attachmentId) return null
79
+ const file = await readAttachment(em, scope, attachmentId)
80
+ return file ? { bytes: file.bytes, mimeType: file.mimeType } : null
81
+ }
82
+
83
+ /** Renders a billing document as it should print now (no storage). */
84
+ export async function renderBillingDocument(
85
+ em: EntityManager,
86
+ invoice: BillingInvoice,
87
+ deps: { loadTranslator?: TranslatorLoader } = {},
88
+ ): Promise<Uint8Array> {
89
+ const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }
90
+ const lines = await em.find(
91
+ BillingInvoiceLine,
92
+ { invoiceId: invoice.id, ...scope, deletedAt: null } as never,
93
+ { orderBy: { lineNumber: 'asc' } as never },
94
+ )
95
+ const { seller, buyer } = await resolveParties(em, invoice)
96
+ const language = str(buyer.invoice_language) ?? DEFAULT_INVOICE_LANGUAGE
97
+ const translate = await (deps.loadTranslator ?? loadDocumentTranslator)(language)
98
+
99
+ let correctsNumber: string | null = null
100
+ if (invoice.correctsInvoiceId) {
101
+ const corrected = await em.findOne(BillingInvoice, { id: invoice.correctsInvoiceId, ...scope } as never)
102
+ correctsNumber = corrected?.number ?? null
103
+ }
104
+
105
+ const model = buildInvoiceDocumentModel({
106
+ invoice: {
107
+ number: invoice.number,
108
+ documentType: invoice.documentType as 'invoice' | 'credit_memo' | 'debit_memo' | 'receipt',
109
+ status: invoice.status,
110
+ testRun: invoice.testRun,
111
+ issueDate: invoice.issueDate ?? null,
112
+ dueDate: invoice.dueDate ?? null,
113
+ billDate: invoice.billDate ?? null,
114
+ periodStart: invoice.periodStart ?? null,
115
+ periodEnd: invoice.periodEnd ?? null,
116
+ currencyCode: invoice.currencyCode,
117
+ subtotalNet: invoice.subtotalNet,
118
+ taxTotal: invoice.taxTotal,
119
+ totalGross: invoice.totalGross,
120
+ correctsNumber: invoice.correctsInvoiceNumber ?? correctsNumber,
121
+ correctsIssueDate: invoice.correctsIssueDate ?? null,
122
+ correctionReason: invoice.correctionReason ?? null,
123
+ },
124
+ lines: lines.map((line) => ({
125
+ description: line.description,
126
+ quantity: line.quantity,
127
+ unitPriceNet: line.unitPriceNet,
128
+ taxRate: line.taxRate,
129
+ netAmount: line.netAmount,
130
+ taxAmount: line.taxAmount,
131
+ grossAmount: line.grossAmount,
132
+ servicePeriodStart: line.servicePeriodStart ?? null,
133
+ servicePeriodEnd: line.servicePeriodEnd ?? null,
134
+ })),
135
+ seller: {
136
+ legalName: str(seller.legal_name),
137
+ address: address(seller.address),
138
+ taxId: str(seller.tax_id),
139
+ bankName: str(seller.bank_name),
140
+ iban: str(seller.iban),
141
+ bic: str(seller.bic),
142
+ footer: str(seller.invoice_footer),
143
+ },
144
+ buyer: {
145
+ name: str(buyer.name) ?? '',
146
+ address: address(buyer.invoice_address),
147
+ taxId: str(buyer.tax_id),
148
+ },
149
+ language,
150
+ translate,
151
+ })
152
+ return renderInvoicePdf(model, { logo: await resolveLogo(em, invoice, seller) })
153
+ }
154
+
155
+ /**
156
+ * Renders and stores the PDF of a posted document once. Idempotent: returns
157
+ * the existing attachment id when already frozen. The column is claimed with
158
+ * a conditional update, so concurrent freezes never overwrite each other.
159
+ */
160
+ export async function freezeInvoicePdf(
161
+ em: EntityManager,
162
+ invoice: BillingInvoice,
163
+ deps: { loadTranslator?: TranslatorLoader } = {},
164
+ ): Promise<{ attachmentId: string; bytes: Uint8Array | null }> {
165
+ if (invoice.pdfAttachmentId) return { attachmentId: invoice.pdfAttachmentId, bytes: null }
166
+ if (!isFrozenDocument(invoice)) throw new Error('Only posted, non-test documents are frozen')
167
+ const bytes = await renderBillingDocument(em, invoice, deps)
168
+ const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }
169
+ const attachmentId = await storeAttachment(em, scope, {
170
+ entityId: billingEntityIds.invoice,
171
+ recordId: invoice.id,
172
+ fileName: pdfFileName(invoice),
173
+ mimeType: 'application/pdf',
174
+ bytes,
175
+ })
176
+ await em.nativeUpdate(
177
+ BillingInvoice,
178
+ { id: invoice.id, ...scope, pdfAttachmentId: null } as never,
179
+ { pdfAttachmentId: attachmentId } as never,
180
+ )
181
+ invoice.pdfAttachmentId = attachmentId
182
+ return { attachmentId, bytes }
183
+ }
184
+
185
+ /**
186
+ * The PDF to serve / attach: the frozen file for posted documents (frozen
187
+ * now if it never was), a live watermarked render for drafts and tests.
188
+ */
189
+ export async function getInvoicePdf(
190
+ em: EntityManager,
191
+ invoice: BillingInvoice,
192
+ deps: { loadTranslator?: TranslatorLoader } = {},
193
+ ): Promise<{ bytes: Uint8Array; fileName: string; frozen: boolean }> {
194
+ const fileName = pdfFileName(invoice)
195
+ if (!isFrozenDocument(invoice)) {
196
+ return { bytes: await renderBillingDocument(em, invoice, deps), fileName, frozen: false }
197
+ }
198
+ if (invoice.pdfAttachmentId) {
199
+ const stored = await readAttachment(
200
+ em,
201
+ { tenantId: invoice.tenantId, organizationId: invoice.organizationId },
202
+ invoice.pdfAttachmentId,
203
+ )
204
+ if (stored) return { bytes: stored.bytes, fileName, frozen: true }
205
+ // Stored file lost (storage moved / purged): serve a fresh render from
206
+ // the frozen snapshots rather than failing; the attachment id is kept.
207
+ console.warn('[billing.pdf] frozen PDF unreadable, rendering from snapshots', invoice.id)
208
+ return { bytes: await renderBillingDocument(em, invoice, deps), fileName, frozen: true }
209
+ }
210
+ const frozen = await freezeInvoicePdf(em, invoice, deps)
211
+ return {
212
+ bytes: frozen.bytes ?? (await renderBillingDocument(em, invoice, deps)),
213
+ fileName,
214
+ frozen: true,
215
+ }
216
+ }
@@ -0,0 +1,338 @@
1
+ import { PDFDocument, degrees, rgb, type PDFFont, type PDFImage, type PDFPage } from 'pdf-lib'
2
+ import fontkit from '@pdf-lib/fontkit'
3
+ import { NOTO_SANS_BOLD_BASE64, NOTO_SANS_REGULAR_BASE64 } from './fonts/embeddedFonts'
4
+ import type { InvoiceDocumentModel } from './invoiceModel'
5
+
6
+ /**
7
+ * Built-in A4 invoice template (Spec B) — lays out an `InvoiceDocumentModel`
8
+ * with pdf-lib and an embedded Unicode font (Noto Sans subset, so Polish
9
+ * diacritics render — the standard Helvetica font cannot).
10
+ *
11
+ * Layout: logo + title | seller / buyer blocks | meta rows | lines table
12
+ * (wrapped descriptions, header repeated on every page) | VAT summary |
13
+ * totals | payment instructions | footer | "Page n of m" | optional
14
+ * diagonal watermark (DRAFT / TEST). Characters the subset font lacks are
15
+ * replaced with "?" so rendering never throws on exotic customer text.
16
+ */
17
+
18
+ export type PdfLogo = { bytes: Uint8Array; mimeType: string }
19
+
20
+ const PAGE_WIDTH = 595.28
21
+ const PAGE_HEIGHT = 841.89
22
+ const MARGIN = 40
23
+ const CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN
24
+ const FOOTER_HEIGHT = 48
25
+ const BODY_SIZE = 9
26
+ const SMALL_SIZE = 8
27
+ const LINE_GAP = 3
28
+
29
+ // # | description | qty | unit net | VAT % | net | VAT | gross (sums to CONTENT_WIDTH)
30
+ const TABLE_COLUMNS = [24, 179, 30, 62, 36, 62, 56, 66.28]
31
+
32
+ const TEXT = rgb(0.12, 0.12, 0.14)
33
+ const MUTED = rgb(0.42, 0.42, 0.46)
34
+ const RULE = rgb(0.82, 0.82, 0.85)
35
+ const HEADER_FILL = rgb(0.95, 0.95, 0.96)
36
+ const WATERMARK = rgb(0.85, 0.85, 0.87)
37
+
38
+ type Fonts = { regular: PDFFont; bold: PDFFont; charset: Set<number> }
39
+
40
+ function base64ToBytes(value: string): Uint8Array {
41
+ return Uint8Array.from(Buffer.from(value, 'base64'))
42
+ }
43
+
44
+ /** Replaces characters the embedded subset cannot draw. */
45
+ export function sanitizeForFont(text: string, charset: Set<number>): string {
46
+ let out = ''
47
+ for (const char of text.replace(/\t/g, ' ')) {
48
+ const code = char.codePointAt(0) ?? 63
49
+ out += code === 10 || charset.has(code) ? char : '?'
50
+ }
51
+ return out
52
+ }
53
+
54
+ function wrap(text: string, font: PDFFont, size: number, width: number): string[] {
55
+ const lines: string[] = []
56
+ for (const paragraph of text.split('\n')) {
57
+ let current = ''
58
+ for (const word of paragraph.split(/\s+/).filter(Boolean)) {
59
+ const candidate = current ? `${current} ${word}` : word
60
+ if (font.widthOfTextAtSize(candidate, size) <= width) {
61
+ current = candidate
62
+ continue
63
+ }
64
+ if (current) lines.push(current)
65
+ // A single word wider than the column is split by characters.
66
+ let chunk = ''
67
+ for (const char of word) {
68
+ if (font.widthOfTextAtSize(chunk + char, size) > width && chunk) {
69
+ lines.push(chunk)
70
+ chunk = char
71
+ } else {
72
+ chunk += char
73
+ }
74
+ }
75
+ current = chunk
76
+ }
77
+ lines.push(current)
78
+ }
79
+ return lines
80
+ }
81
+
82
+ class Layout {
83
+ readonly pages: PDFPage[] = []
84
+ page!: PDFPage
85
+ y = 0
86
+
87
+ constructor(
88
+ private readonly doc: PDFDocument,
89
+ private readonly fonts: Fonts,
90
+ ) {
91
+ this.newPage()
92
+ }
93
+
94
+ get boldFont(): PDFFont {
95
+ return this.fonts.bold
96
+ }
97
+
98
+ newPage(): void {
99
+ this.page = this.doc.addPage([PAGE_WIDTH, PAGE_HEIGHT])
100
+ this.pages.push(this.page)
101
+ this.y = PAGE_HEIGHT - MARGIN
102
+ }
103
+
104
+ /** Starts a new page when fewer than `height` points remain above the footer. */
105
+ ensure(height: number): boolean {
106
+ if (this.y - height < MARGIN + FOOTER_HEIGHT) {
107
+ this.newPage()
108
+ return true
109
+ }
110
+ return false
111
+ }
112
+
113
+ text(value: string, x: number, opts: { size?: number; bold?: boolean; color?: ReturnType<typeof rgb>; y?: number } = {}) {
114
+ const size = opts.size ?? BODY_SIZE
115
+ this.page.drawText(sanitizeForFont(value, this.fonts.charset), {
116
+ x,
117
+ y: opts.y ?? this.y,
118
+ size,
119
+ font: opts.bold ? this.fonts.bold : this.fonts.regular,
120
+ color: opts.color ?? TEXT,
121
+ })
122
+ }
123
+
124
+ width(value: string, size: number, bold = false): number {
125
+ return (bold ? this.fonts.bold : this.fonts.regular).widthOfTextAtSize(sanitizeForFont(value, this.fonts.charset), size)
126
+ }
127
+
128
+ rule(y = this.y): void {
129
+ this.page.drawLine({ start: { x: MARGIN, y }, end: { x: PAGE_WIDTH - MARGIN, y }, thickness: 0.5, color: RULE })
130
+ }
131
+ }
132
+
133
+ function drawTableHeader(layout: Layout, headers: string[], align: Array<'left' | 'right'>, columns: number[], x0 = MARGIN) {
134
+ // Headers wrap inside their column (Polish labels are long: "Cena netto").
135
+ const wrapped = headers.map((header, i) => wrap(header, layout.boldFont, SMALL_SIZE, columns[i] - 6))
136
+ const lineHeight = SMALL_SIZE + 2
137
+ const height = Math.max(...wrapped.map((lines) => lines.length)) * lineHeight + 8
138
+ const total = columns.reduce((sum, w) => sum + w, 0)
139
+ layout.page.drawRectangle({ x: x0, y: layout.y - height + 4, width: total, height, color: HEADER_FILL })
140
+ let x = x0
141
+ wrapped.forEach((lines, i) => {
142
+ const w = columns[i]
143
+ lines.forEach((line, li) => {
144
+ const tx = align[i] === 'right' ? x + w - 3 - layout.width(line, SMALL_SIZE, true) : x + 3
145
+ layout.text(line, tx, { size: SMALL_SIZE, bold: true, y: layout.y - SMALL_SIZE - 1 - li * lineHeight })
146
+ })
147
+ x += w
148
+ })
149
+ layout.y -= height + 2
150
+ }
151
+
152
+ function drawRows(
153
+ layout: Layout,
154
+ rows: string[][],
155
+ headers: string[],
156
+ align: Array<'left' | 'right'>,
157
+ columns: number[],
158
+ fonts: Fonts,
159
+ x0 = MARGIN,
160
+ ) {
161
+ const lineHeight = BODY_SIZE + LINE_GAP
162
+ for (const row of rows) {
163
+ const cells = row.map((cell, i) =>
164
+ wrap(sanitizeForFont(cell, fonts.charset), fonts.regular, BODY_SIZE, columns[i] - 6),
165
+ )
166
+ const rowHeight = Math.max(...cells.map((c) => c.length)) * lineHeight + 4
167
+ if (layout.ensure(rowHeight)) drawTableHeader(layout, headers, align, columns, x0)
168
+ let x = x0
169
+ cells.forEach((cellLines, i) => {
170
+ const w = columns[i]
171
+ cellLines.forEach((line, li) => {
172
+ const tx = align[i] === 'right' ? x + w - 3 - layout.width(line, BODY_SIZE) : x + 3
173
+ layout.text(line, tx, { y: layout.y - BODY_SIZE - li * lineHeight })
174
+ })
175
+ x += w
176
+ })
177
+ layout.y -= rowHeight
178
+ layout.page.drawLine({
179
+ start: { x: x0, y: layout.y + 1 },
180
+ end: { x: x0 + columns.reduce((s, w) => s + w, 0), y: layout.y + 1 },
181
+ thickness: 0.3,
182
+ color: RULE,
183
+ })
184
+ }
185
+ }
186
+
187
+ function drawBlock(layout: Layout, heading: string, lines: string[], x: number, width: number, top: number, fonts: Fonts): number {
188
+ let y = top
189
+ layout.text(heading.toUpperCase(), x, { size: SMALL_SIZE, bold: true, color: MUTED, y })
190
+ y -= SMALL_SIZE + 6
191
+ lines.forEach((line, index) => {
192
+ for (const part of wrap(sanitizeForFont(line, fonts.charset), index === 0 ? fonts.bold : fonts.regular, BODY_SIZE, width)) {
193
+ layout.text(part, x, { bold: index === 0, y })
194
+ y -= BODY_SIZE + LINE_GAP
195
+ }
196
+ })
197
+ return y
198
+ }
199
+
200
+ async function embedLogo(doc: PDFDocument, logo: PdfLogo | null | undefined): Promise<PDFImage | null> {
201
+ if (!logo) return null
202
+ try {
203
+ if (logo.mimeType === 'image/png') return await doc.embedPng(logo.bytes)
204
+ if (logo.mimeType === 'image/jpeg' || logo.mimeType === 'image/jpg') return await doc.embedJpg(logo.bytes)
205
+ } catch (err) {
206
+ console.warn('[billing.pdf] logo could not be embedded', err)
207
+ }
208
+ return null
209
+ }
210
+
211
+ export async function renderInvoicePdf(model: InvoiceDocumentModel, opts: { logo?: PdfLogo | null } = {}): Promise<Uint8Array> {
212
+ const doc = await PDFDocument.create()
213
+ doc.registerFontkit(fontkit)
214
+ // subset: false — the fonts are already subset at build time, and pdf-lib's
215
+ // own subsetting drops kerning on some glyph runs.
216
+ const regular = await doc.embedFont(base64ToBytes(NOTO_SANS_REGULAR_BASE64), { subset: false })
217
+ const bold = await doc.embedFont(base64ToBytes(NOTO_SANS_BOLD_BASE64), { subset: false })
218
+ const fonts: Fonts = { regular, bold, charset: new Set(regular.getCharacterSet()) }
219
+ doc.setTitle(sanitizeForFont(`${model.title} ${model.number}`, fonts.charset))
220
+ doc.setLanguage(model.language)
221
+ doc.setProducer('Dainamite Billing')
222
+
223
+ const layout = new Layout(doc, fonts)
224
+
225
+ // ── Header: logo left, title + number right ──
226
+ const logo = await embedLogo(doc, opts.logo)
227
+ if (logo) {
228
+ const scale = Math.min(140 / logo.width, 48 / logo.height, 1)
229
+ layout.page.drawImage(logo, {
230
+ x: MARGIN,
231
+ y: layout.y - logo.height * scale,
232
+ width: logo.width * scale,
233
+ height: logo.height * scale,
234
+ })
235
+ }
236
+ const titleSize = 18
237
+ layout.text(model.title, PAGE_WIDTH - MARGIN - layout.width(model.title, titleSize, true), {
238
+ size: titleSize,
239
+ bold: true,
240
+ y: layout.y - titleSize,
241
+ })
242
+ layout.text(model.number, PAGE_WIDTH - MARGIN - layout.width(model.number, 11), {
243
+ size: 11,
244
+ color: MUTED,
245
+ y: layout.y - titleSize - 16,
246
+ })
247
+ layout.y -= 64
248
+
249
+ // ── Meta rows (right-aligned key: value list) ──
250
+ for (const [label, value] of model.meta) {
251
+ const text = `${label}: ${value}`
252
+ layout.text(text, PAGE_WIDTH - MARGIN - layout.width(text, BODY_SIZE), { y: layout.y })
253
+ layout.y -= BODY_SIZE + LINE_GAP
254
+ }
255
+ layout.y -= 10
256
+
257
+ // ── Seller | Buyer ──
258
+ const half = CONTENT_WIDTH / 2 - 10
259
+ const top = layout.y
260
+ const sellerBottom = drawBlock(layout, model.seller.heading, model.seller.lines, MARGIN, half, top, fonts)
261
+ const buyerBottom = drawBlock(layout, model.buyer.heading, model.buyer.lines, MARGIN + half + 20, half, top, fonts)
262
+ layout.y = Math.min(sellerBottom, buyerBottom) - 14
263
+
264
+ // ── Lines ──
265
+ layout.ensure(40)
266
+ drawTableHeader(layout, model.table.headers, model.table.align, TABLE_COLUMNS)
267
+ drawRows(layout, model.table.rows, model.table.headers, model.table.align, TABLE_COLUMNS, fonts)
268
+ layout.y -= 14
269
+
270
+ // ── VAT summary (right half) + totals ──
271
+ const summaryColumns = [60, 75, 70, 85]
272
+ const summaryX = PAGE_WIDTH - MARGIN - summaryColumns.reduce((s, w) => s + w, 0)
273
+ layout.ensure(SMALL_SIZE + 30 + model.vatSummary.rows.length * (BODY_SIZE + 7))
274
+ layout.text(model.vatSummary.heading, summaryX, { size: SMALL_SIZE, bold: true, color: MUTED })
275
+ layout.y -= SMALL_SIZE + 4
276
+ const summaryAlign: Array<'left' | 'right'> = ['right', 'right', 'right', 'right']
277
+ drawTableHeader(layout, model.vatSummary.headers, summaryAlign, summaryColumns, summaryX)
278
+ drawRows(layout, model.vatSummary.rows, model.vatSummary.headers, summaryAlign, summaryColumns, fonts, summaryX)
279
+ layout.y -= 10
280
+
281
+ layout.ensure(model.totals.length * 16 + 10)
282
+ model.totals.forEach(([label, value], index) => {
283
+ const last = index === model.totals.length - 1
284
+ const size = last ? 12 : BODY_SIZE + 1
285
+ layout.text(value, PAGE_WIDTH - MARGIN - layout.width(value, size, last), { size, bold: last })
286
+ layout.text(label, summaryX, { size, bold: last, color: last ? TEXT : MUTED })
287
+ layout.y -= size + 6
288
+ })
289
+ layout.y -= 10
290
+
291
+ // ── Payment instructions ──
292
+ if (model.payment) {
293
+ layout.ensure((model.payment.lines.length + 1) * (BODY_SIZE + LINE_GAP) + 16)
294
+ layout.rule(layout.y + 4)
295
+ layout.y -= 10
296
+ layout.text(model.payment.heading.toUpperCase(), MARGIN, { size: SMALL_SIZE, bold: true, color: MUTED })
297
+ layout.y -= SMALL_SIZE + 6
298
+ for (const line of model.payment.lines) {
299
+ layout.text(line, MARGIN)
300
+ layout.y -= BODY_SIZE + LINE_GAP
301
+ }
302
+ }
303
+
304
+ // ── Footer, page numbers, watermark on every page ──
305
+ const pages = layout.pages
306
+ pages.forEach((page, index) => {
307
+ layout.page = page
308
+ let fy = MARGIN + FOOTER_HEIGHT - 14
309
+ for (const line of model.footer.slice(0, 3)) {
310
+ for (const part of wrap(sanitizeForFont(line, fonts.charset), regular, SMALL_SIZE - 1, CONTENT_WIDTH - 90).slice(0, 2)) {
311
+ layout.text(part, MARGIN, { size: SMALL_SIZE - 1, color: MUTED, y: fy })
312
+ fy -= SMALL_SIZE + 1
313
+ }
314
+ }
315
+ const pageText = model.pageLabel(index + 1, pages.length)
316
+ layout.text(pageText, PAGE_WIDTH - MARGIN - layout.width(pageText, SMALL_SIZE), {
317
+ size: SMALL_SIZE,
318
+ color: MUTED,
319
+ y: MARGIN,
320
+ })
321
+ if (model.watermark) {
322
+ const size = 96
323
+ const text = sanitizeForFont(model.watermark, fonts.charset)
324
+ const w = bold.widthOfTextAtSize(text, size)
325
+ page.drawText(text, {
326
+ x: PAGE_WIDTH / 2 - (w / 2) * Math.cos(Math.PI / 4),
327
+ y: PAGE_HEIGHT / 2 - (w / 2) * Math.sin(Math.PI / 4),
328
+ size,
329
+ font: bold,
330
+ color: WATERMARK,
331
+ rotate: degrees(45),
332
+ opacity: 0.5,
333
+ })
334
+ }
335
+ })
336
+
337
+ return doc.save()
338
+ }
@@ -1,58 +1,49 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
- import { ModuleConfig } from '@open-mercato/core/modules/configs/data/entities'
2
+ import { getBillingSettings, receiptTaxRate, type SettingsScope } from './billingSettings'
3
3
 
4
4
  /**
5
- * Typed readers for the prepaid billing module configs (SPEC-002), seeded by
6
- * `setup.ts → seedBillingConfigs`. Stored as raw JSON scalars in
7
- * `ModuleConfig.valueJson`. Each reader falls back to the same default the
8
- * seed uses, so a missing row (older tenant) behaves identically.
5
+ * Typed readers for the prepaid billing settings (SPEC-002), stored per
6
+ * organization in `billing_settings` (Spec 0, E-D2 — previously tenant-wide
7
+ * core `configs` rows). Every reader takes the organization scope; the
8
+ * defaults are identical to the old config seed, so behaviour is unchanged.
9
9
  */
10
10
 
11
- async function readConfig(em: EntityManager, name: string): Promise<unknown> {
12
- const row = await em.findOne(ModuleConfig, { moduleId: 'billing', name })
13
- if (!row) return undefined
14
- return row.valueJson as unknown
15
- }
16
-
17
11
  function asNumberString(value: unknown, fallback: string): string {
18
12
  if (value === null || value === undefined) return fallback
19
13
  if (typeof value === 'number' && Number.isFinite(value)) return String(value)
20
14
  if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {
21
- return value.trim()
15
+ return String(Number(value))
22
16
  }
23
17
  return fallback
24
18
  }
25
19
 
26
- /** `prepaid.low_balance_threshold_default` — numeric string (default '0'). */
27
- export async function getLowBalanceThresholdDefault(em: EntityManager): Promise<string> {
28
- return asNumberString(await readConfig(em, 'prepaid.low_balance_threshold_default'), '0')
20
+ /** Low-balance threshold default — numeric string (default '0'). */
21
+ export async function getLowBalanceThresholdDefault(em: EntityManager, scope: SettingsScope): Promise<string> {
22
+ return asNumberString((await getBillingSettings(em, scope)).lowBalanceThresholdDefault, '0')
23
+ }
24
+
25
+ /** Near-credit-limit buffer default — numeric string (default '0'). */
26
+ export async function getNearLimitBufferDefault(em: EntityManager, scope: SettingsScope): Promise<string> {
27
+ return asNumberString((await getBillingSettings(em, scope)).nearLimitBufferDefault, '0')
29
28
  }
30
29
 
31
- /** `credit.near_limit_buffer_default` — numeric string (default '0'). */
32
- export async function getNearLimitBufferDefault(em: EntityManager): Promise<string> {
33
- return asNumberString(await readConfig(em, 'credit.near_limit_buffer_default'), '0')
30
+ /** Gateway provider key for top-ups (default 'stripe'). */
31
+ export async function getTopupProvider(em: EntityManager, scope: SettingsScope): Promise<string> {
32
+ const value = (await getBillingSettings(em, scope)).topupProvider
33
+ return typeof value === 'string' && value.trim() ? value.trim() : 'stripe'
34
34
  }
35
35
 
36
- /** `prepaid.topup_provider` — gateway provider key (default 'stripe'). */
37
- export async function getTopupProvider(em: EntityManager): Promise<string> {
38
- const value = await readConfig(em, 'prepaid.topup_provider')
39
- return typeof value === 'string' && value.trim() !== '' ? value.trim() : 'stripe'
36
+ /** Consumption statements master switch (default true). */
37
+ export async function getStatementEnabled(em: EntityManager, scope: SettingsScope): Promise<boolean> {
38
+ return (await getBillingSettings(em, scope)).statementEnabled !== false
40
39
  }
41
40
 
42
- /** `prepaid.statement_enabled` — master switch (default true). */
43
- export async function getStatementEnabled(em: EntityManager): Promise<boolean> {
44
- const value = await readConfig(em, 'prepaid.statement_enabled')
45
- if (value === undefined || value === null) return true
46
- if (typeof value === 'boolean') return value
47
- // A stored string "false" must read as false (Boolean("false") === true).
48
- if (typeof value === 'string') return value.trim().toLowerCase() !== 'false'
49
- return Boolean(value)
41
+ /** VAT percent on top-up receipts (default 0; always 0 when tax is charged at consumption). */
42
+ export async function getTopupTaxRate(em: EntityManager, scope: SettingsScope): Promise<number> {
43
+ return receiptTaxRate(await getBillingSettings(em, scope))
50
44
  }
51
45
 
52
- /** `prepaid.topup_tax_rate` — VAT percent on top-up receipts (default 0). */
53
- export async function getTopupTaxRate(em: EntityManager): Promise<number> {
54
- const value = await readConfig(em, 'prepaid.topup_tax_rate')
55
- if (typeof value === 'number' && Number.isFinite(value)) return value
56
- if (typeof value === 'string' && Number.isFinite(Number(value))) return Number(value)
57
- return 0
46
+ /** Where prepaid VAT is charged (Spec F, F-D3). Default `topup`. */
47
+ export async function getPrepaidTaxPoint(em: EntityManager, scope: SettingsScope): Promise<'topup' | 'consumption'> {
48
+ return (await getBillingSettings(em, scope)).prepaidTaxPoint === 'consumption' ? 'consumption' : 'topup'
58
49
  }