@dainamite/billing 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (598) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/dist/modules/billing/acl.js +13 -1
  3. package/dist/modules/billing/acl.js.map +2 -2
  4. package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
  5. package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
  6. package/dist/modules/billing/api/accounts/route.js +46 -4
  7. package/dist/modules/billing/api/accounts/route.js.map +2 -2
  8. package/dist/modules/billing/api/export/account/[id]/route.js +11 -7
  9. package/dist/modules/billing/api/export/account/[id]/route.js.map +2 -2
  10. package/dist/modules/billing/api/invoices/[id]/memos/route.js +54 -0
  11. package/dist/modules/billing/api/invoices/[id]/memos/route.js.map +7 -0
  12. package/dist/modules/billing/api/invoices/[id]/pdf/route.js +69 -0
  13. package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
  14. package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
  15. package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
  16. package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
  17. package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
  18. package/dist/modules/billing/api/invoices/add-line/route.js +2 -2
  19. package/dist/modules/billing/api/invoices/add-line/route.js.map +2 -2
  20. package/dist/modules/billing/api/invoices/delete-draft/route.js +33 -0
  21. package/dist/modules/billing/api/invoices/delete-draft/route.js.map +7 -0
  22. package/dist/modules/billing/api/invoices/edit-line/route.js +2 -2
  23. package/dist/modules/billing/api/invoices/edit-line/route.js.map +2 -2
  24. package/dist/modules/billing/api/invoices/post/route.js +2 -2
  25. package/dist/modules/billing/api/invoices/post/route.js.map +2 -2
  26. package/dist/modules/billing/api/invoices/remove-line/route.js +2 -2
  27. package/dist/modules/billing/api/invoices/remove-line/route.js.map +2 -2
  28. package/dist/modules/billing/api/invoices/route.js +45 -46
  29. package/dist/modules/billing/api/invoices/route.js.map +2 -2
  30. package/dist/modules/billing/api/items/bulk/route.js +1 -1
  31. package/dist/modules/billing/api/items/bulk/route.js.map +2 -2
  32. package/dist/modules/billing/api/items/change-price/route.js +44 -0
  33. package/dist/modules/billing/api/items/change-price/route.js.map +7 -0
  34. package/dist/modules/billing/api/items/route.js +19 -7
  35. package/dist/modules/billing/api/items/route.js.map +2 -2
  36. package/dist/modules/billing/api/payment-imports/[id]/route.js +49 -0
  37. package/dist/modules/billing/api/payment-imports/[id]/route.js.map +7 -0
  38. package/dist/modules/billing/api/payment-imports/route.js +57 -0
  39. package/dist/modules/billing/api/payment-imports/route.js.map +7 -0
  40. package/dist/modules/billing/api/payments/[id]/route.js +60 -0
  41. package/dist/modules/billing/api/payments/[id]/route.js.map +7 -0
  42. package/dist/modules/billing/api/payments/allocate/route.js +35 -0
  43. package/dist/modules/billing/api/payments/allocate/route.js.map +7 -0
  44. package/dist/modules/billing/api/payments/assign/route.js +36 -0
  45. package/dist/modules/billing/api/payments/assign/route.js.map +7 -0
  46. package/dist/modules/billing/api/payments/convert-to-topup/route.js +35 -0
  47. package/dist/modules/billing/api/payments/convert-to-topup/route.js.map +7 -0
  48. package/dist/modules/billing/api/payments/ignore/route.js +35 -0
  49. package/dist/modules/billing/api/payments/ignore/route.js.map +7 -0
  50. package/dist/modules/billing/api/payments/import/route.js +63 -0
  51. package/dist/modules/billing/api/payments/import/route.js.map +7 -0
  52. package/dist/modules/billing/api/payments/restore/route.js +35 -0
  53. package/dist/modules/billing/api/payments/restore/route.js.map +7 -0
  54. package/dist/modules/billing/api/payments/route.js +114 -0
  55. package/dist/modules/billing/api/payments/route.js.map +7 -0
  56. package/dist/modules/billing/api/payments/unallocate/route.js +35 -0
  57. package/dist/modules/billing/api/payments/unallocate/route.js.map +7 -0
  58. package/dist/modules/billing/api/payments/unassign/route.js +35 -0
  59. package/dist/modules/billing/api/payments/unassign/route.js.map +7 -0
  60. package/dist/modules/billing/api/run-outcomes/route.js +43 -6
  61. package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
  62. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js +61 -0
  63. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js.map +7 -0
  64. package/dist/modules/billing/api/runs/[id]/route.js +53 -0
  65. package/dist/modules/billing/api/runs/[id]/route.js.map +7 -0
  66. package/dist/modules/billing/api/runs/retry-failed/route.js +1 -1
  67. package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
  68. package/dist/modules/billing/api/runs/route.js +9 -3
  69. package/dist/modules/billing/api/runs/route.js.map +2 -2
  70. package/dist/modules/billing/api/settings/number-preview/route.js +42 -0
  71. package/dist/modules/billing/api/settings/number-preview/route.js.map +7 -0
  72. package/dist/modules/billing/api/settings/route.js +92 -0
  73. package/dist/modules/billing/api/settings/route.js.map +7 -0
  74. package/dist/modules/billing/api/statements/route.js +3 -0
  75. package/dist/modules/billing/api/statements/route.js.map +2 -2
  76. package/dist/modules/billing/api/test-invoices/wipe/route.js +2 -2
  77. package/dist/modules/billing/api/test-invoices/wipe/route.js.map +2 -2
  78. package/dist/modules/billing/backend/billing/accounts/[id]/page.js +121 -232
  79. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +2 -2
  80. package/dist/modules/billing/backend/billing/accounts/create/page.js +6 -0
  81. package/dist/modules/billing/backend/billing/accounts/create/page.js.map +2 -2
  82. package/dist/modules/billing/backend/billing/accounts/page.js +63 -4
  83. package/dist/modules/billing/backend/billing/accounts/page.js.map +2 -2
  84. package/dist/modules/billing/backend/billing/invoices/[id]/page.js +297 -65
  85. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +3 -3
  86. package/dist/modules/billing/backend/billing/invoices/page.js +6 -172
  87. package/dist/modules/billing/backend/billing/invoices/page.js.map +2 -2
  88. package/dist/modules/billing/backend/billing/items/[id]/page.js +51 -7
  89. package/dist/modules/billing/backend/billing/items/[id]/page.js.map +2 -2
  90. package/dist/modules/billing/backend/billing/items/create/page.js +9 -6
  91. package/dist/modules/billing/backend/billing/items/create/page.js.map +2 -2
  92. package/dist/modules/billing/backend/billing/items/page.js +8 -2
  93. package/dist/modules/billing/backend/billing/items/page.js.map +2 -2
  94. package/dist/modules/billing/backend/billing/payments/[id]/page.js +297 -0
  95. package/dist/modules/billing/backend/billing/payments/[id]/page.js.map +7 -0
  96. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js +21 -0
  97. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js.map +7 -0
  98. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js +74 -0
  99. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js.map +7 -0
  100. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js +21 -0
  101. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js.map +7 -0
  102. package/dist/modules/billing/backend/billing/payments/page.js +58 -0
  103. package/dist/modules/billing/backend/billing/payments/page.js.map +7 -0
  104. package/dist/modules/billing/backend/billing/payments/page.meta.js +18 -0
  105. package/dist/modules/billing/backend/billing/payments/page.meta.js.map +7 -0
  106. package/dist/modules/billing/backend/billing/runs/[id]/page.js +49 -14
  107. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +2 -2
  108. package/dist/modules/billing/backend/billing/runs/page.js +58 -7
  109. package/dist/modules/billing/backend/billing/runs/page.js.map +2 -2
  110. package/dist/modules/billing/backend/billing/statements/[id]/page.js +61 -6
  111. package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +2 -2
  112. package/dist/modules/billing/backend/billing/statements/page.js +1 -1
  113. package/dist/modules/billing/backend/billing/statements/page.js.map +2 -2
  114. package/dist/modules/billing/backend/billing/topups/page.js +1 -1
  115. package/dist/modules/billing/backend/billing/topups/page.js.map +2 -2
  116. package/dist/modules/billing/backend/billing/transactions/page.js +1 -1
  117. package/dist/modules/billing/backend/billing/transactions/page.js.map +2 -2
  118. package/dist/modules/billing/backend/billing/usage/create/page.js +3 -2
  119. package/dist/modules/billing/backend/billing/usage/create/page.js.map +2 -2
  120. package/dist/modules/billing/backend/config/billing/page.js +472 -0
  121. package/dist/modules/billing/backend/config/billing/page.js.map +7 -0
  122. package/dist/modules/billing/backend/config/billing/page.meta.js +19 -0
  123. package/dist/modules/billing/backend/config/billing/page.meta.js.map +7 -0
  124. package/dist/modules/billing/cli.js +22 -1
  125. package/dist/modules/billing/cli.js.map +2 -2
  126. package/dist/modules/billing/commands/accounts.js +6 -0
  127. package/dist/modules/billing/commands/accounts.js.map +2 -2
  128. package/dist/modules/billing/commands/index.js +3 -0
  129. package/dist/modules/billing/commands/index.js.map +2 -2
  130. package/dist/modules/billing/commands/invoices.js +269 -167
  131. package/dist/modules/billing/commands/invoices.js.map +2 -2
  132. package/dist/modules/billing/commands/items.js +108 -0
  133. package/dist/modules/billing/commands/items.js.map +2 -2
  134. package/dist/modules/billing/commands/memos.js +179 -0
  135. package/dist/modules/billing/commands/memos.js.map +7 -0
  136. package/dist/modules/billing/commands/payments.js +379 -0
  137. package/dist/modules/billing/commands/payments.js.map +7 -0
  138. package/dist/modules/billing/commands/runs.js +151 -4
  139. package/dist/modules/billing/commands/runs.js.map +2 -2
  140. package/dist/modules/billing/commands/settings.js +59 -0
  141. package/dist/modules/billing/commands/settings.js.map +7 -0
  142. package/dist/modules/billing/commands/topups.js +9 -2
  143. package/dist/modules/billing/commands/topups.js.map +2 -2
  144. package/dist/modules/billing/commands/transactions.js +2 -2
  145. package/dist/modules/billing/commands/transactions.js.map +2 -2
  146. package/dist/modules/billing/components/AccountForm.js +149 -42
  147. package/dist/modules/billing/components/AccountForm.js.map +2 -2
  148. package/dist/modules/billing/components/AccountInvoicesSection.js +64 -0
  149. package/dist/modules/billing/components/AccountInvoicesSection.js.map +7 -0
  150. package/dist/modules/billing/components/AccountPaymentsSection.js +43 -0
  151. package/dist/modules/billing/components/AccountPaymentsSection.js.map +7 -0
  152. package/dist/modules/billing/components/AccountPicker.js +56 -0
  153. package/dist/modules/billing/components/AccountPicker.js.map +7 -0
  154. package/dist/modules/billing/components/AllocatePaymentDialog.js +134 -0
  155. package/dist/modules/billing/components/AllocatePaymentDialog.js.map +7 -0
  156. package/dist/modules/billing/components/BillingDataTable.js +21 -0
  157. package/dist/modules/billing/components/BillingDataTable.js.map +7 -0
  158. package/dist/modules/billing/components/BillingSettingsLink.js +19 -0
  159. package/dist/modules/billing/components/BillingSettingsLink.js.map +7 -0
  160. package/dist/modules/billing/components/ChangeItemPriceDialog.js +158 -0
  161. package/dist/modules/billing/components/ChangeItemPriceDialog.js.map +7 -0
  162. package/dist/modules/billing/components/CreateMemoDialog.js +200 -0
  163. package/dist/modules/billing/components/CreateMemoDialog.js.map +7 -0
  164. package/dist/modules/billing/components/CustomerLabel.js +33 -0
  165. package/dist/modules/billing/components/CustomerLabel.js.map +7 -0
  166. package/dist/modules/billing/components/InvoicesTable.js +270 -0
  167. package/dist/modules/billing/components/InvoicesTable.js.map +7 -0
  168. package/dist/modules/billing/components/ItemForm.js +176 -44
  169. package/dist/modules/billing/components/ItemForm.js.map +2 -2
  170. package/dist/modules/billing/components/ItemPriceField.js +46 -0
  171. package/dist/modules/billing/components/ItemPriceField.js.map +7 -0
  172. package/dist/modules/billing/components/LineFormDialog.js +25 -2
  173. package/dist/modules/billing/components/LineFormDialog.js.map +2 -2
  174. package/dist/modules/billing/components/ManualPaymentDialog.js +122 -0
  175. package/dist/modules/billing/components/ManualPaymentDialog.js.map +7 -0
  176. package/dist/modules/billing/components/MoneyInput.js +14 -0
  177. package/dist/modules/billing/components/MoneyInput.js.map +7 -0
  178. package/dist/modules/billing/components/PaymentUploadDialog.js +115 -0
  179. package/dist/modules/billing/components/PaymentUploadDialog.js.map +7 -0
  180. package/dist/modules/billing/components/PaymentsTable.js +171 -0
  181. package/dist/modules/billing/components/PaymentsTable.js.map +7 -0
  182. package/dist/modules/billing/components/PostRunDraftsButton.js +105 -0
  183. package/dist/modules/billing/components/PostRunDraftsButton.js.map +7 -0
  184. package/dist/modules/billing/components/PrepaidPanel.js +14 -4
  185. package/dist/modules/billing/components/PrepaidPanel.js.map +2 -2
  186. package/dist/modules/billing/components/RunCyclesSection.js +209 -0
  187. package/dist/modules/billing/components/RunCyclesSection.js.map +7 -0
  188. package/dist/modules/billing/components/TestRunActions.js +127 -0
  189. package/dist/modules/billing/components/TestRunActions.js.map +7 -0
  190. package/dist/modules/billing/components/account/AccountItemsTab.js +157 -0
  191. package/dist/modules/billing/components/account/AccountItemsTab.js.map +7 -0
  192. package/dist/modules/billing/components/account/AccountRunBillingDialog.js +122 -0
  193. package/dist/modules/billing/components/account/AccountRunBillingDialog.js.map +7 -0
  194. package/dist/modules/billing/components/account/AccountRunsTab.js +152 -0
  195. package/dist/modules/billing/components/account/AccountRunsTab.js.map +7 -0
  196. package/dist/modules/billing/components/account/AccountStatementsTab.js +98 -0
  197. package/dist/modules/billing/components/account/AccountStatementsTab.js.map +7 -0
  198. package/dist/modules/billing/components/account/AccountSummaryCards.js +125 -0
  199. package/dist/modules/billing/components/account/AccountSummaryCards.js.map +7 -0
  200. package/dist/modules/billing/components/account/AccountUsageTab.js +91 -0
  201. package/dist/modules/billing/components/account/AccountUsageTab.js.map +7 -0
  202. package/dist/modules/billing/components/account/AccountView.js +73 -0
  203. package/dist/modules/billing/components/account/AccountView.js.map +7 -0
  204. package/dist/modules/billing/components/account/accountRow.js +39 -0
  205. package/dist/modules/billing/components/account/accountRow.js.map +7 -0
  206. package/dist/modules/billing/components/customerNames.js +42 -0
  207. package/dist/modules/billing/components/customerNames.js.map +7 -0
  208. package/dist/modules/billing/data/entities.js +560 -4
  209. package/dist/modules/billing/data/entities.js.map +2 -2
  210. package/dist/modules/billing/data/entityIds.js +10 -1
  211. package/dist/modules/billing/data/entityIds.js.map +2 -2
  212. package/dist/modules/billing/data/validators.js +269 -15
  213. package/dist/modules/billing/data/validators.js.map +2 -2
  214. package/dist/modules/billing/events.js +50 -0
  215. package/dist/modules/billing/events.js.map +2 -2
  216. package/dist/modules/billing/i18n/en.json +457 -21
  217. package/dist/modules/billing/i18n/pl.json +457 -21
  218. package/dist/modules/billing/index.js +6 -2
  219. package/dist/modules/billing/index.js.map +2 -2
  220. package/dist/modules/billing/lib/accountFinancials.js +58 -0
  221. package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
  222. package/dist/modules/billing/lib/accountLock.js +14 -0
  223. package/dist/modules/billing/lib/accountLock.js.map +7 -0
  224. package/dist/modules/billing/lib/accountStatus.js +9 -7
  225. package/dist/modules/billing/lib/accountStatus.js.map +2 -2
  226. package/dist/modules/billing/lib/accountSummary.js +106 -0
  227. package/dist/modules/billing/lib/accountSummary.js.map +7 -0
  228. package/dist/modules/billing/lib/allocation.js +356 -0
  229. package/dist/modules/billing/lib/allocation.js.map +7 -0
  230. package/dist/modules/billing/lib/allocationEvents.js +22 -0
  231. package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
  232. package/dist/modules/billing/lib/billPeriod.js +15 -1
  233. package/dist/modules/billing/lib/billPeriod.js.map +2 -2
  234. package/dist/modules/billing/lib/billRunEngine.js +237 -102
  235. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  236. package/dist/modules/billing/lib/billingSettings.js +116 -0
  237. package/dist/modules/billing/lib/billingSettings.js.map +7 -0
  238. package/dist/modules/billing/lib/commandRoute.js +81 -0
  239. package/dist/modules/billing/lib/commandRoute.js.map +7 -0
  240. package/dist/modules/billing/lib/customerLookup.js +97 -0
  241. package/dist/modules/billing/lib/customerLookup.js.map +7 -0
  242. package/dist/modules/billing/lib/dateOnly.js +13 -0
  243. package/dist/modules/billing/lib/dateOnly.js.map +7 -0
  244. package/dist/modules/billing/lib/documentParties.js +16 -0
  245. package/dist/modules/billing/lib/documentParties.js.map +7 -0
  246. package/dist/modules/billing/lib/documentTypes.js +27 -0
  247. package/dist/modules/billing/lib/documentTypes.js.map +7 -0
  248. package/dist/modules/billing/lib/dueDate.js +18 -0
  249. package/dist/modules/billing/lib/dueDate.js.map +7 -0
  250. package/dist/modules/billing/lib/iban.js +29 -0
  251. package/dist/modules/billing/lib/iban.js.map +7 -0
  252. package/dist/modules/billing/lib/invoiceDelivery.js +131 -0
  253. package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
  254. package/dist/modules/billing/lib/invoiceLanguages.js +16 -0
  255. package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
  256. package/dist/modules/billing/lib/invoiceWriter.js +107 -99
  257. package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
  258. package/dist/modules/billing/lib/itemBillingState.js +27 -0
  259. package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
  260. package/dist/modules/billing/lib/itemPriceChange.js +18 -0
  261. package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
  262. package/dist/modules/billing/lib/itemSelector.js.map +2 -2
  263. package/dist/modules/billing/lib/lineAmounts.js +32 -0
  264. package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
  265. package/dist/modules/billing/lib/memoGuards.js +25 -0
  266. package/dist/modules/billing/lib/memoGuards.js.map +7 -0
  267. package/dist/modules/billing/lib/navigation.js +48 -0
  268. package/dist/modules/billing/lib/navigation.js.map +7 -0
  269. package/dist/modules/billing/lib/numbering.js +120 -0
  270. package/dist/modules/billing/lib/numbering.js.map +7 -0
  271. package/dist/modules/billing/lib/overdue.js +36 -0
  272. package/dist/modules/billing/lib/overdue.js.map +7 -0
  273. package/dist/modules/billing/lib/paymentCsv.js +125 -0
  274. package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
  275. package/dist/modules/billing/lib/paymentGateway.js +15 -0
  276. package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
  277. package/dist/modules/billing/lib/paymentImport.js +164 -0
  278. package/dist/modules/billing/lib/paymentImport.js.map +7 -0
  279. package/dist/modules/billing/lib/paymentMatcher.js +36 -0
  280. package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
  281. package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
  282. package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
  283. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
  284. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
  285. package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
  286. package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
  287. package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
  288. package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
  289. package/dist/modules/billing/lib/pdf/invoiceTemplate.js +274 -0
  290. package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
  291. package/dist/modules/billing/lib/prepaidConfig.js +17 -25
  292. package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
  293. package/dist/modules/billing/lib/prepaidConsume.js +26 -7
  294. package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
  295. package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
  296. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
  297. package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
  298. package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
  299. package/dist/modules/billing/lib/seeds/demo.js +51 -3
  300. package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
  301. package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
  302. package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
  303. package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
  304. package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
  305. package/dist/modules/billing/lib/servicePeriods.js +121 -0
  306. package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
  307. package/dist/modules/billing/lib/testRunCleanup.js +24 -0
  308. package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
  309. package/dist/modules/billing/lib/topupCapture.js +2 -2
  310. package/dist/modules/billing/lib/topupCapture.js.map +2 -2
  311. package/dist/modules/billing/lib/topupReceipt.js +58 -79
  312. package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
  313. package/dist/modules/billing/lib/usageRater.js.map +1 -1
  314. package/dist/modules/billing/lib/usageRunner.js +14 -6
  315. package/dist/modules/billing/lib/usageRunner.js.map +2 -2
  316. package/dist/modules/billing/lib/vat.js +39 -0
  317. package/dist/modules/billing/lib/vat.js.map +7 -0
  318. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
  319. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
  320. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
  321. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
  322. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
  323. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
  324. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
  325. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
  326. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
  327. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
  328. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
  329. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
  330. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
  331. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
  332. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
  333. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
  334. package/dist/modules/billing/setup.js +7 -184
  335. package/dist/modules/billing/setup.js.map +2 -2
  336. package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
  337. package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
  338. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
  339. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
  340. package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
  341. package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
  342. package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
  343. package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
  344. package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
  345. package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
  346. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
  347. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
  348. package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
  349. package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
  350. package/generated/entities/billing_account/index.ts +3 -0
  351. package/generated/entities/billing_allocation/index.ts +18 -0
  352. package/generated/entities/billing_document_sequence/index.ts +11 -0
  353. package/generated/entities/billing_invoice/index.ts +38 -0
  354. package/generated/entities/billing_invoice_line/index.ts +24 -0
  355. package/generated/entities/billing_item/index.ts +4 -0
  356. package/generated/entities/billing_payment/index.ts +25 -0
  357. package/generated/entities/billing_payment_import/index.ts +14 -0
  358. package/generated/entities/billing_run/index.ts +0 -1
  359. package/generated/entities/billing_run_outcome/index.ts +1 -0
  360. package/generated/entities/billing_settings/index.ts +26 -0
  361. package/generated/entities/billing_statement/index.ts +1 -0
  362. package/generated/entities.ids.generated.ts +7 -0
  363. package/package.json +5 -1
  364. package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +170 -0
  365. package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
  366. package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
  367. package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
  368. package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
  369. package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
  370. package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
  371. package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
  372. package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
  373. package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
  374. package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
  375. package/src/modules/billing/__tests__/commands/invoices.test.ts +436 -354
  376. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
  377. package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
  378. package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
  379. package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
  380. package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
  381. package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
  382. package/src/modules/billing/__tests__/entityRegistry.test.ts +8 -1
  383. package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
  384. package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
  385. package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
  386. package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
  387. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +228 -17
  388. package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
  389. package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
  390. package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
  391. package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
  392. package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +189 -0
  393. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
  394. package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
  395. package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
  396. package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
  397. package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
  398. package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
  399. package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
  400. package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
  401. package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
  402. package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
  403. package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +199 -0
  404. package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
  405. package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
  406. package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
  407. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
  408. package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
  409. package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
  410. package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
  411. package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
  412. package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
  413. package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
  414. package/src/modules/billing/__tests__/setup.test.ts +13 -4
  415. package/src/modules/billing/__tests__/validators.test.ts +193 -3
  416. package/src/modules/billing/acl.ts +12 -0
  417. package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
  418. package/src/modules/billing/api/accounts/route.ts +58 -4
  419. package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
  420. package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
  421. package/src/modules/billing/api/invoices/[id]/pdf/route.ts +79 -0
  422. package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
  423. package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
  424. package/src/modules/billing/api/invoices/add-line/route.ts +2 -2
  425. package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
  426. package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
  427. package/src/modules/billing/api/invoices/post/route.ts +7 -6
  428. package/src/modules/billing/api/invoices/remove-line/route.ts +2 -2
  429. package/src/modules/billing/api/invoices/route.ts +81 -86
  430. package/src/modules/billing/api/items/bulk/route.ts +2 -1
  431. package/src/modules/billing/api/items/change-price/route.ts +54 -0
  432. package/src/modules/billing/api/items/route.ts +23 -7
  433. package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
  434. package/src/modules/billing/api/payment-imports/route.ts +57 -0
  435. package/src/modules/billing/api/payments/[id]/route.ts +63 -0
  436. package/src/modules/billing/api/payments/allocate/route.ts +35 -0
  437. package/src/modules/billing/api/payments/assign/route.ts +36 -0
  438. package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
  439. package/src/modules/billing/api/payments/ignore/route.ts +35 -0
  440. package/src/modules/billing/api/payments/import/route.ts +72 -0
  441. package/src/modules/billing/api/payments/restore/route.ts +35 -0
  442. package/src/modules/billing/api/payments/route.ts +147 -0
  443. package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
  444. package/src/modules/billing/api/payments/unassign/route.ts +35 -0
  445. package/src/modules/billing/api/run-outcomes/route.ts +49 -6
  446. package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
  447. package/src/modules/billing/api/runs/[id]/route.ts +59 -0
  448. package/src/modules/billing/api/runs/retry-failed/route.ts +2 -1
  449. package/src/modules/billing/api/runs/route.ts +15 -7
  450. package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
  451. package/src/modules/billing/api/settings/route.ts +116 -0
  452. package/src/modules/billing/api/statements/route.ts +5 -0
  453. package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
  454. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
  455. package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
  456. package/src/modules/billing/backend/billing/accounts/page.tsx +70 -4
  457. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +444 -95
  458. package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
  459. package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
  460. package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
  461. package/src/modules/billing/backend/billing/items/page.tsx +18 -2
  462. package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
  463. package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
  464. package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
  465. package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
  466. package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
  467. package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
  468. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +49 -18
  469. package/src/modules/billing/backend/billing/runs/page.tsx +59 -8
  470. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +86 -5
  471. package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
  472. package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
  473. package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
  474. package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
  475. package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
  476. package/src/modules/billing/backend/config/billing/page.tsx +549 -0
  477. package/src/modules/billing/cli.ts +31 -1
  478. package/src/modules/billing/commands/accounts.ts +6 -0
  479. package/src/modules/billing/commands/index.ts +3 -0
  480. package/src/modules/billing/commands/invoices.ts +364 -231
  481. package/src/modules/billing/commands/items.ts +143 -0
  482. package/src/modules/billing/commands/memos.ts +218 -0
  483. package/src/modules/billing/commands/payments.ts +428 -0
  484. package/src/modules/billing/commands/runs.ts +205 -3
  485. package/src/modules/billing/commands/settings.ts +77 -0
  486. package/src/modules/billing/commands/topups.ts +12 -3
  487. package/src/modules/billing/commands/transactions.ts +2 -2
  488. package/src/modules/billing/components/AccountForm.tsx +199 -53
  489. package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
  490. package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
  491. package/src/modules/billing/components/AccountPicker.tsx +70 -0
  492. package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
  493. package/src/modules/billing/components/BillingDataTable.tsx +34 -0
  494. package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
  495. package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
  496. package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
  497. package/src/modules/billing/components/CustomerLabel.tsx +53 -0
  498. package/src/modules/billing/components/InvoicesTable.tsx +344 -0
  499. package/src/modules/billing/components/ItemForm.tsx +233 -44
  500. package/src/modules/billing/components/ItemPriceField.tsx +81 -0
  501. package/src/modules/billing/components/LineFormDialog.tsx +25 -1
  502. package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
  503. package/src/modules/billing/components/MoneyInput.tsx +26 -0
  504. package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
  505. package/src/modules/billing/components/PaymentsTable.tsx +213 -0
  506. package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
  507. package/src/modules/billing/components/PrepaidPanel.tsx +19 -6
  508. package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
  509. package/src/modules/billing/components/TestRunActions.tsx +152 -0
  510. package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
  511. package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
  512. package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
  513. package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
  514. package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
  515. package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
  516. package/src/modules/billing/components/account/AccountView.tsx +101 -0
  517. package/src/modules/billing/components/account/accountRow.ts +70 -0
  518. package/src/modules/billing/components/customerNames.ts +62 -0
  519. package/src/modules/billing/data/entities.ts +654 -5
  520. package/src/modules/billing/data/entityIds.ts +9 -0
  521. package/src/modules/billing/data/validators.ts +388 -8
  522. package/src/modules/billing/events.ts +56 -3
  523. package/src/modules/billing/i18n/en.json +457 -21
  524. package/src/modules/billing/i18n/pl.json +457 -21
  525. package/src/modules/billing/index.ts +6 -2
  526. package/src/modules/billing/lib/accountFinancials.ts +84 -0
  527. package/src/modules/billing/lib/accountLock.ts +32 -0
  528. package/src/modules/billing/lib/accountStatus.ts +11 -9
  529. package/src/modules/billing/lib/accountSummary.ts +172 -0
  530. package/src/modules/billing/lib/allocation.ts +515 -0
  531. package/src/modules/billing/lib/allocationEvents.ts +26 -0
  532. package/src/modules/billing/lib/billPeriod.ts +19 -0
  533. package/src/modules/billing/lib/billRunEngine.ts +350 -150
  534. package/src/modules/billing/lib/billingSettings.ts +145 -0
  535. package/src/modules/billing/lib/commandRoute.ts +112 -0
  536. package/src/modules/billing/lib/customerLookup.ts +157 -0
  537. package/src/modules/billing/lib/dateOnly.ts +22 -0
  538. package/src/modules/billing/lib/documentParties.ts +23 -0
  539. package/src/modules/billing/lib/documentTypes.ts +37 -0
  540. package/src/modules/billing/lib/dueDate.ts +26 -0
  541. package/src/modules/billing/lib/iban.ts +36 -0
  542. package/src/modules/billing/lib/invoiceDelivery.ts +197 -0
  543. package/src/modules/billing/lib/invoiceLanguages.ts +34 -0
  544. package/src/modules/billing/lib/invoiceWriter.ts +162 -148
  545. package/src/modules/billing/lib/itemBillingState.ts +67 -0
  546. package/src/modules/billing/lib/itemPriceChange.ts +56 -0
  547. package/src/modules/billing/lib/itemSelector.ts +4 -1
  548. package/src/modules/billing/lib/lineAmounts.ts +61 -0
  549. package/src/modules/billing/lib/memoGuards.ts +41 -0
  550. package/src/modules/billing/lib/navigation.ts +64 -0
  551. package/src/modules/billing/lib/numbering.ts +174 -0
  552. package/src/modules/billing/lib/overdue.ts +60 -0
  553. package/src/modules/billing/lib/paymentCsv.ts +171 -0
  554. package/src/modules/billing/lib/paymentGateway.ts +52 -0
  555. package/src/modules/billing/lib/paymentImport.ts +228 -0
  556. package/src/modules/billing/lib/paymentMatcher.ts +88 -0
  557. package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
  558. package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
  559. package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
  560. package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
  561. package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
  562. package/src/modules/billing/lib/pdf/invoiceModel.ts +328 -0
  563. package/src/modules/billing/lib/pdf/invoicePdf.ts +216 -0
  564. package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
  565. package/src/modules/billing/lib/prepaidConfig.ts +27 -36
  566. package/src/modules/billing/lib/prepaidConsume.ts +29 -6
  567. package/src/modules/billing/lib/prepaidPeriodClose.ts +407 -239
  568. package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
  569. package/src/modules/billing/lib/seeds/demo.ts +79 -6
  570. package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
  571. package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
  572. package/src/modules/billing/lib/servicePeriods.ts +226 -0
  573. package/src/modules/billing/lib/testRunCleanup.ts +42 -0
  574. package/src/modules/billing/lib/topupCapture.ts +3 -4
  575. package/src/modules/billing/lib/topupReceipt.ts +70 -103
  576. package/src/modules/billing/lib/usageRater.ts +1 -1
  577. package/src/modules/billing/lib/usageRunner.ts +17 -5
  578. package/src/modules/billing/lib/vat.ts +56 -0
  579. package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
  580. package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
  581. package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
  582. package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
  583. package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
  584. package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
  585. package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
  586. package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
  587. package/src/modules/billing/setup.ts +11 -237
  588. package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
  589. package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
  590. package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
  591. package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
  592. package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
  593. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
  594. package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
  595. package/dist/modules/billing/lib/invoiceStatus.js +0 -38
  596. package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
  597. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
  598. package/src/modules/billing/lib/invoiceStatus.ts +0 -61
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/billing/lib/pdf/invoicePdf.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { BillingAccount, BillingInvoice, BillingInvoiceLine } from '../../data/entities'\nimport { billingEntityIds } from '../../data/entityIds'\nimport { buildSellerSnapshot, getBillingSettings } from '../billingSettings'\nimport { buildBuyerSnapshot } from '../documentParties'\nimport { DEFAULT_INVOICE_LANGUAGE } from '../invoiceLanguages'\nimport { buildInvoiceDocumentModel, type DocumentTranslate, type PdfAddress } from './invoiceModel'\nimport { renderInvoicePdf, type PdfLogo } from './invoiceTemplate'\nimport { attachmentIdFromUrl, readAttachment, storeAttachment } from './attachmentStore'\n\n/**\n * Billing document PDFs (Spec B, P-D1 / B-D5):\n *\n * - posted documents are rendered once and frozen as an attachment\n * (`pdf_attachment_id`); later downloads stream the stored file, so the\n * document never changes after issue. If freezing failed at post, the\n * first download renders and freezes it (lazy fallback).\n * - drafts and test documents are rendered live with a DRAFT / TEST\n * watermark and never stored.\n *\n * Parties come from the frozen snapshots on posted documents and from the\n * live account / settings on drafts. Labels use the document language\n * (`invoice_language`), never the operator's UI locale.\n */\n\nexport type TranslatorLoader = (language: string) => Promise<DocumentTranslate>\n\n/** Billing i18n for a document language (en / pl, falling back to en). */\nexport const loadDocumentTranslator: TranslatorLoader = async (language) => {\n const { loadDictionary } = await import('@open-mercato/shared/lib/i18n/server')\n const { createFallbackTranslator } = await import('@open-mercato/shared/lib/i18n/translate')\n const locale = language === 'pl' ? 'pl' : 'en'\n const translate = createFallbackTranslator(await loadDictionary(locale))\n return (key, fallback) => translate(key, fallback)\n}\n\ntype Snapshot = Record<string, unknown>\n\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.trim() !== '' ? value : null\n}\n\nfunction address(value: unknown): PdfAddress | null {\n return value && typeof value === 'object' ? (value as PdfAddress) : null\n}\n\nexport function pdfFileName(invoice: Pick<BillingInvoice, 'number'>): string {\n return `${invoice.number.replace(/[^A-Za-z0-9._-]+/g, '-')}.pdf`\n}\n\nfunction isFrozenDocument(invoice: Pick<BillingInvoice, 'status' | 'testRun'>): boolean {\n return invoice.status !== 'draft' && !invoice.testRun\n}\n\nasync function resolveParties(em: EntityManager, invoice: BillingInvoice) {\n const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }\n let seller: Snapshot | null = isFrozenDocument(invoice) ? (invoice.sellerSnapshot ?? null) : null\n let buyer: Snapshot | null = isFrozenDocument(invoice) ? (invoice.buyerSnapshot ?? null) : null\n if (!seller || !('legal_name' in seller)) seller = buildSellerSnapshot(await getBillingSettings(em, scope))\n if (!buyer) {\n const account = await em.findOne(BillingAccount, { id: invoice.billAccountId, ...scope } as never)\n buyer = account ? buildBuyerSnapshot(account) : { name: '' }\n }\n return { seller, buyer }\n}\n\nasync function resolveLogo(em: EntityManager, invoice: BillingInvoice, seller: Snapshot): Promise<PdfLogo | null> {\n const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }\n let attachmentId = str(seller.logo_attachment_id)\n if (!attachmentId) {\n // Fallback: the organization logo, only when it is one of our own\n // attachments (never fetch arbitrary URLs server-side).\n const rows = (await em.execute(`SELECT logo_url FROM organizations WHERE id = ? LIMIT 1`, [\n invoice.organizationId,\n ])) as unknown as Array<{ logo_url: string | null }>\n attachmentId = attachmentIdFromUrl(rows?.[0]?.logo_url)\n }\n if (!attachmentId) return null\n const file = await readAttachment(em, scope, attachmentId)\n return file ? { bytes: file.bytes, mimeType: file.mimeType } : null\n}\n\n/** Renders a billing document as it should print now (no storage). */\nexport async function renderBillingDocument(\n em: EntityManager,\n invoice: BillingInvoice,\n deps: { loadTranslator?: TranslatorLoader } = {},\n): Promise<Uint8Array> {\n const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }\n const lines = await em.find(\n BillingInvoiceLine,\n { invoiceId: invoice.id, ...scope, deletedAt: null } as never,\n { orderBy: { lineNumber: 'asc' } as never },\n )\n const { seller, buyer } = await resolveParties(em, invoice)\n const language = str(buyer.invoice_language) ?? DEFAULT_INVOICE_LANGUAGE\n const translate = await (deps.loadTranslator ?? loadDocumentTranslator)(language)\n\n let correctsNumber: string | null = null\n if (invoice.correctsInvoiceId) {\n const corrected = await em.findOne(BillingInvoice, { id: invoice.correctsInvoiceId, ...scope } as never)\n correctsNumber = corrected?.number ?? null\n }\n\n const model = buildInvoiceDocumentModel({\n invoice: {\n number: invoice.number,\n documentType: invoice.documentType as 'invoice' | 'credit_memo' | 'debit_memo' | 'receipt',\n status: invoice.status,\n testRun: invoice.testRun,\n issueDate: invoice.issueDate ?? null,\n dueDate: invoice.dueDate ?? null,\n billDate: invoice.billDate ?? null,\n periodStart: invoice.periodStart ?? null,\n periodEnd: invoice.periodEnd ?? null,\n currencyCode: invoice.currencyCode,\n subtotalNet: invoice.subtotalNet,\n taxTotal: invoice.taxTotal,\n totalGross: invoice.totalGross,\n correctsNumber: invoice.correctsInvoiceNumber ?? correctsNumber,\n correctsIssueDate: invoice.correctsIssueDate ?? null,\n correctionReason: invoice.correctionReason ?? null,\n },\n lines: lines.map((line) => ({\n description: line.description,\n quantity: line.quantity,\n unitPriceNet: line.unitPriceNet,\n taxRate: line.taxRate,\n netAmount: line.netAmount,\n taxAmount: line.taxAmount,\n grossAmount: line.grossAmount,\n servicePeriodStart: line.servicePeriodStart ?? null,\n servicePeriodEnd: line.servicePeriodEnd ?? null,\n })),\n seller: {\n legalName: str(seller.legal_name),\n address: address(seller.address),\n taxId: str(seller.tax_id),\n bankName: str(seller.bank_name),\n iban: str(seller.iban),\n bic: str(seller.bic),\n footer: str(seller.invoice_footer),\n },\n buyer: {\n name: str(buyer.name) ?? '',\n address: address(buyer.invoice_address),\n taxId: str(buyer.tax_id),\n },\n language,\n translate,\n })\n return renderInvoicePdf(model, { logo: await resolveLogo(em, invoice, seller) })\n}\n\n/**\n * Renders and stores the PDF of a posted document once. Idempotent: returns\n * the existing attachment id when already frozen. The column is claimed with\n * a conditional update, so concurrent freezes never overwrite each other.\n */\nexport async function freezeInvoicePdf(\n em: EntityManager,\n invoice: BillingInvoice,\n deps: { loadTranslator?: TranslatorLoader } = {},\n): Promise<{ attachmentId: string; bytes: Uint8Array | null }> {\n if (invoice.pdfAttachmentId) return { attachmentId: invoice.pdfAttachmentId, bytes: null }\n if (!isFrozenDocument(invoice)) throw new Error('Only posted, non-test documents are frozen')\n const bytes = await renderBillingDocument(em, invoice, deps)\n const scope = { tenantId: invoice.tenantId, organizationId: invoice.organizationId }\n const attachmentId = await storeAttachment(em, scope, {\n entityId: billingEntityIds.invoice,\n recordId: invoice.id,\n fileName: pdfFileName(invoice),\n mimeType: 'application/pdf',\n bytes,\n })\n await em.nativeUpdate(\n BillingInvoice,\n { id: invoice.id, ...scope, pdfAttachmentId: null } as never,\n { pdfAttachmentId: attachmentId } as never,\n )\n invoice.pdfAttachmentId = attachmentId\n return { attachmentId, bytes }\n}\n\n/**\n * The PDF to serve / attach: the frozen file for posted documents (frozen\n * now if it never was), a live watermarked render for drafts and tests.\n */\nexport async function getInvoicePdf(\n em: EntityManager,\n invoice: BillingInvoice,\n deps: { loadTranslator?: TranslatorLoader } = {},\n): Promise<{ bytes: Uint8Array; fileName: string; frozen: boolean }> {\n const fileName = pdfFileName(invoice)\n if (!isFrozenDocument(invoice)) {\n return { bytes: await renderBillingDocument(em, invoice, deps), fileName, frozen: false }\n }\n if (invoice.pdfAttachmentId) {\n const stored = await readAttachment(\n em,\n { tenantId: invoice.tenantId, organizationId: invoice.organizationId },\n invoice.pdfAttachmentId,\n )\n if (stored) return { bytes: stored.bytes, fileName, frozen: true }\n // Stored file lost (storage moved / purged): serve a fresh render from\n // the frozen snapshots rather than failing; the attachment id is kept.\n console.warn('[billing.pdf] frozen PDF unreadable, rendering from snapshots', invoice.id)\n return { bytes: await renderBillingDocument(em, invoice, deps), fileName, frozen: true }\n }\n const frozen = await freezeInvoicePdf(em, invoice, deps)\n return {\n bytes: frozen.bytes ?? (await renderBillingDocument(em, invoice, deps)),\n fileName,\n frozen: true,\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,274 @@
1
+ import { PDFDocument, degrees, rgb } from "pdf-lib";
2
+ import fontkit from "@pdf-lib/fontkit";
3
+ import { NOTO_SANS_BOLD_BASE64, NOTO_SANS_REGULAR_BASE64 } from "./fonts/embeddedFonts.js";
4
+ const PAGE_WIDTH = 595.28;
5
+ const PAGE_HEIGHT = 841.89;
6
+ const MARGIN = 40;
7
+ const CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN;
8
+ const FOOTER_HEIGHT = 48;
9
+ const BODY_SIZE = 9;
10
+ const SMALL_SIZE = 8;
11
+ const LINE_GAP = 3;
12
+ const TABLE_COLUMNS = [24, 179, 30, 62, 36, 62, 56, 66.28];
13
+ const TEXT = rgb(0.12, 0.12, 0.14);
14
+ const MUTED = rgb(0.42, 0.42, 0.46);
15
+ const RULE = rgb(0.82, 0.82, 0.85);
16
+ const HEADER_FILL = rgb(0.95, 0.95, 0.96);
17
+ const WATERMARK = rgb(0.85, 0.85, 0.87);
18
+ function base64ToBytes(value) {
19
+ return Uint8Array.from(Buffer.from(value, "base64"));
20
+ }
21
+ function sanitizeForFont(text, charset) {
22
+ let out = "";
23
+ for (const char of text.replace(/\t/g, " ")) {
24
+ const code = char.codePointAt(0) ?? 63;
25
+ out += code === 10 || charset.has(code) ? char : "?";
26
+ }
27
+ return out;
28
+ }
29
+ function wrap(text, font, size, width) {
30
+ const lines = [];
31
+ for (const paragraph of text.split("\n")) {
32
+ let current = "";
33
+ for (const word of paragraph.split(/\s+/).filter(Boolean)) {
34
+ const candidate = current ? `${current} ${word}` : word;
35
+ if (font.widthOfTextAtSize(candidate, size) <= width) {
36
+ current = candidate;
37
+ continue;
38
+ }
39
+ if (current) lines.push(current);
40
+ let chunk = "";
41
+ for (const char of word) {
42
+ if (font.widthOfTextAtSize(chunk + char, size) > width && chunk) {
43
+ lines.push(chunk);
44
+ chunk = char;
45
+ } else {
46
+ chunk += char;
47
+ }
48
+ }
49
+ current = chunk;
50
+ }
51
+ lines.push(current);
52
+ }
53
+ return lines;
54
+ }
55
+ class Layout {
56
+ constructor(doc, fonts) {
57
+ this.doc = doc;
58
+ this.fonts = fonts;
59
+ this.pages = [];
60
+ this.y = 0;
61
+ this.newPage();
62
+ }
63
+ get boldFont() {
64
+ return this.fonts.bold;
65
+ }
66
+ newPage() {
67
+ this.page = this.doc.addPage([PAGE_WIDTH, PAGE_HEIGHT]);
68
+ this.pages.push(this.page);
69
+ this.y = PAGE_HEIGHT - MARGIN;
70
+ }
71
+ /** Starts a new page when fewer than `height` points remain above the footer. */
72
+ ensure(height) {
73
+ if (this.y - height < MARGIN + FOOTER_HEIGHT) {
74
+ this.newPage();
75
+ return true;
76
+ }
77
+ return false;
78
+ }
79
+ text(value, x, opts = {}) {
80
+ const size = opts.size ?? BODY_SIZE;
81
+ this.page.drawText(sanitizeForFont(value, this.fonts.charset), {
82
+ x,
83
+ y: opts.y ?? this.y,
84
+ size,
85
+ font: opts.bold ? this.fonts.bold : this.fonts.regular,
86
+ color: opts.color ?? TEXT
87
+ });
88
+ }
89
+ width(value, size, bold = false) {
90
+ return (bold ? this.fonts.bold : this.fonts.regular).widthOfTextAtSize(sanitizeForFont(value, this.fonts.charset), size);
91
+ }
92
+ rule(y = this.y) {
93
+ this.page.drawLine({ start: { x: MARGIN, y }, end: { x: PAGE_WIDTH - MARGIN, y }, thickness: 0.5, color: RULE });
94
+ }
95
+ }
96
+ function drawTableHeader(layout, headers, align, columns, x0 = MARGIN) {
97
+ const wrapped = headers.map((header, i) => wrap(header, layout.boldFont, SMALL_SIZE, columns[i] - 6));
98
+ const lineHeight = SMALL_SIZE + 2;
99
+ const height = Math.max(...wrapped.map((lines) => lines.length)) * lineHeight + 8;
100
+ const total = columns.reduce((sum, w) => sum + w, 0);
101
+ layout.page.drawRectangle({ x: x0, y: layout.y - height + 4, width: total, height, color: HEADER_FILL });
102
+ let x = x0;
103
+ wrapped.forEach((lines, i) => {
104
+ const w = columns[i];
105
+ lines.forEach((line, li) => {
106
+ const tx = align[i] === "right" ? x + w - 3 - layout.width(line, SMALL_SIZE, true) : x + 3;
107
+ layout.text(line, tx, { size: SMALL_SIZE, bold: true, y: layout.y - SMALL_SIZE - 1 - li * lineHeight });
108
+ });
109
+ x += w;
110
+ });
111
+ layout.y -= height + 2;
112
+ }
113
+ function drawRows(layout, rows, headers, align, columns, fonts, x0 = MARGIN) {
114
+ const lineHeight = BODY_SIZE + LINE_GAP;
115
+ for (const row of rows) {
116
+ const cells = row.map(
117
+ (cell, i) => wrap(sanitizeForFont(cell, fonts.charset), fonts.regular, BODY_SIZE, columns[i] - 6)
118
+ );
119
+ const rowHeight = Math.max(...cells.map((c) => c.length)) * lineHeight + 4;
120
+ if (layout.ensure(rowHeight)) drawTableHeader(layout, headers, align, columns, x0);
121
+ let x = x0;
122
+ cells.forEach((cellLines, i) => {
123
+ const w = columns[i];
124
+ cellLines.forEach((line, li) => {
125
+ const tx = align[i] === "right" ? x + w - 3 - layout.width(line, BODY_SIZE) : x + 3;
126
+ layout.text(line, tx, { y: layout.y - BODY_SIZE - li * lineHeight });
127
+ });
128
+ x += w;
129
+ });
130
+ layout.y -= rowHeight;
131
+ layout.page.drawLine({
132
+ start: { x: x0, y: layout.y + 1 },
133
+ end: { x: x0 + columns.reduce((s, w) => s + w, 0), y: layout.y + 1 },
134
+ thickness: 0.3,
135
+ color: RULE
136
+ });
137
+ }
138
+ }
139
+ function drawBlock(layout, heading, lines, x, width, top, fonts) {
140
+ let y = top;
141
+ layout.text(heading.toUpperCase(), x, { size: SMALL_SIZE, bold: true, color: MUTED, y });
142
+ y -= SMALL_SIZE + 6;
143
+ lines.forEach((line, index) => {
144
+ for (const part of wrap(sanitizeForFont(line, fonts.charset), index === 0 ? fonts.bold : fonts.regular, BODY_SIZE, width)) {
145
+ layout.text(part, x, { bold: index === 0, y });
146
+ y -= BODY_SIZE + LINE_GAP;
147
+ }
148
+ });
149
+ return y;
150
+ }
151
+ async function embedLogo(doc, logo) {
152
+ if (!logo) return null;
153
+ try {
154
+ if (logo.mimeType === "image/png") return await doc.embedPng(logo.bytes);
155
+ if (logo.mimeType === "image/jpeg" || logo.mimeType === "image/jpg") return await doc.embedJpg(logo.bytes);
156
+ } catch (err) {
157
+ console.warn("[billing.pdf] logo could not be embedded", err);
158
+ }
159
+ return null;
160
+ }
161
+ async function renderInvoicePdf(model, opts = {}) {
162
+ const doc = await PDFDocument.create();
163
+ doc.registerFontkit(fontkit);
164
+ const regular = await doc.embedFont(base64ToBytes(NOTO_SANS_REGULAR_BASE64), { subset: false });
165
+ const bold = await doc.embedFont(base64ToBytes(NOTO_SANS_BOLD_BASE64), { subset: false });
166
+ const fonts = { regular, bold, charset: new Set(regular.getCharacterSet()) };
167
+ doc.setTitle(sanitizeForFont(`${model.title} ${model.number}`, fonts.charset));
168
+ doc.setLanguage(model.language);
169
+ doc.setProducer("Dainamite Billing");
170
+ const layout = new Layout(doc, fonts);
171
+ const logo = await embedLogo(doc, opts.logo);
172
+ if (logo) {
173
+ const scale = Math.min(140 / logo.width, 48 / logo.height, 1);
174
+ layout.page.drawImage(logo, {
175
+ x: MARGIN,
176
+ y: layout.y - logo.height * scale,
177
+ width: logo.width * scale,
178
+ height: logo.height * scale
179
+ });
180
+ }
181
+ const titleSize = 18;
182
+ layout.text(model.title, PAGE_WIDTH - MARGIN - layout.width(model.title, titleSize, true), {
183
+ size: titleSize,
184
+ bold: true,
185
+ y: layout.y - titleSize
186
+ });
187
+ layout.text(model.number, PAGE_WIDTH - MARGIN - layout.width(model.number, 11), {
188
+ size: 11,
189
+ color: MUTED,
190
+ y: layout.y - titleSize - 16
191
+ });
192
+ layout.y -= 64;
193
+ for (const [label, value] of model.meta) {
194
+ const text = `${label}: ${value}`;
195
+ layout.text(text, PAGE_WIDTH - MARGIN - layout.width(text, BODY_SIZE), { y: layout.y });
196
+ layout.y -= BODY_SIZE + LINE_GAP;
197
+ }
198
+ layout.y -= 10;
199
+ const half = CONTENT_WIDTH / 2 - 10;
200
+ const top = layout.y;
201
+ const sellerBottom = drawBlock(layout, model.seller.heading, model.seller.lines, MARGIN, half, top, fonts);
202
+ const buyerBottom = drawBlock(layout, model.buyer.heading, model.buyer.lines, MARGIN + half + 20, half, top, fonts);
203
+ layout.y = Math.min(sellerBottom, buyerBottom) - 14;
204
+ layout.ensure(40);
205
+ drawTableHeader(layout, model.table.headers, model.table.align, TABLE_COLUMNS);
206
+ drawRows(layout, model.table.rows, model.table.headers, model.table.align, TABLE_COLUMNS, fonts);
207
+ layout.y -= 14;
208
+ const summaryColumns = [60, 75, 70, 85];
209
+ const summaryX = PAGE_WIDTH - MARGIN - summaryColumns.reduce((s, w) => s + w, 0);
210
+ layout.ensure(SMALL_SIZE + 30 + model.vatSummary.rows.length * (BODY_SIZE + 7));
211
+ layout.text(model.vatSummary.heading, summaryX, { size: SMALL_SIZE, bold: true, color: MUTED });
212
+ layout.y -= SMALL_SIZE + 4;
213
+ const summaryAlign = ["right", "right", "right", "right"];
214
+ drawTableHeader(layout, model.vatSummary.headers, summaryAlign, summaryColumns, summaryX);
215
+ drawRows(layout, model.vatSummary.rows, model.vatSummary.headers, summaryAlign, summaryColumns, fonts, summaryX);
216
+ layout.y -= 10;
217
+ layout.ensure(model.totals.length * 16 + 10);
218
+ model.totals.forEach(([label, value], index) => {
219
+ const last = index === model.totals.length - 1;
220
+ const size = last ? 12 : BODY_SIZE + 1;
221
+ layout.text(value, PAGE_WIDTH - MARGIN - layout.width(value, size, last), { size, bold: last });
222
+ layout.text(label, summaryX, { size, bold: last, color: last ? TEXT : MUTED });
223
+ layout.y -= size + 6;
224
+ });
225
+ layout.y -= 10;
226
+ if (model.payment) {
227
+ layout.ensure((model.payment.lines.length + 1) * (BODY_SIZE + LINE_GAP) + 16);
228
+ layout.rule(layout.y + 4);
229
+ layout.y -= 10;
230
+ layout.text(model.payment.heading.toUpperCase(), MARGIN, { size: SMALL_SIZE, bold: true, color: MUTED });
231
+ layout.y -= SMALL_SIZE + 6;
232
+ for (const line of model.payment.lines) {
233
+ layout.text(line, MARGIN);
234
+ layout.y -= BODY_SIZE + LINE_GAP;
235
+ }
236
+ }
237
+ const pages = layout.pages;
238
+ pages.forEach((page, index) => {
239
+ layout.page = page;
240
+ let fy = MARGIN + FOOTER_HEIGHT - 14;
241
+ for (const line of model.footer.slice(0, 3)) {
242
+ for (const part of wrap(sanitizeForFont(line, fonts.charset), regular, SMALL_SIZE - 1, CONTENT_WIDTH - 90).slice(0, 2)) {
243
+ layout.text(part, MARGIN, { size: SMALL_SIZE - 1, color: MUTED, y: fy });
244
+ fy -= SMALL_SIZE + 1;
245
+ }
246
+ }
247
+ const pageText = model.pageLabel(index + 1, pages.length);
248
+ layout.text(pageText, PAGE_WIDTH - MARGIN - layout.width(pageText, SMALL_SIZE), {
249
+ size: SMALL_SIZE,
250
+ color: MUTED,
251
+ y: MARGIN
252
+ });
253
+ if (model.watermark) {
254
+ const size = 96;
255
+ const text = sanitizeForFont(model.watermark, fonts.charset);
256
+ const w = bold.widthOfTextAtSize(text, size);
257
+ page.drawText(text, {
258
+ x: PAGE_WIDTH / 2 - w / 2 * Math.cos(Math.PI / 4),
259
+ y: PAGE_HEIGHT / 2 - w / 2 * Math.sin(Math.PI / 4),
260
+ size,
261
+ font: bold,
262
+ color: WATERMARK,
263
+ rotate: degrees(45),
264
+ opacity: 0.5
265
+ });
266
+ }
267
+ });
268
+ return doc.save();
269
+ }
270
+ export {
271
+ renderInvoicePdf,
272
+ sanitizeForFont
273
+ };
274
+ //# sourceMappingURL=invoiceTemplate.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/billing/lib/pdf/invoiceTemplate.ts"],
4
+ "sourcesContent": ["import { PDFDocument, degrees, rgb, type PDFFont, type PDFImage, type PDFPage } from 'pdf-lib'\nimport fontkit from '@pdf-lib/fontkit'\nimport { NOTO_SANS_BOLD_BASE64, NOTO_SANS_REGULAR_BASE64 } from './fonts/embeddedFonts'\nimport type { InvoiceDocumentModel } from './invoiceModel'\n\n/**\n * Built-in A4 invoice template (Spec B) \u2014 lays out an `InvoiceDocumentModel`\n * with pdf-lib and an embedded Unicode font (Noto Sans subset, so Polish\n * diacritics render \u2014 the standard Helvetica font cannot).\n *\n * Layout: logo + title | seller / buyer blocks | meta rows | lines table\n * (wrapped descriptions, header repeated on every page) | VAT summary |\n * totals | payment instructions | footer | \"Page n of m\" | optional\n * diagonal watermark (DRAFT / TEST). Characters the subset font lacks are\n * replaced with \"?\" so rendering never throws on exotic customer text.\n */\n\nexport type PdfLogo = { bytes: Uint8Array; mimeType: string }\n\nconst PAGE_WIDTH = 595.28\nconst PAGE_HEIGHT = 841.89\nconst MARGIN = 40\nconst CONTENT_WIDTH = PAGE_WIDTH - 2 * MARGIN\nconst FOOTER_HEIGHT = 48\nconst BODY_SIZE = 9\nconst SMALL_SIZE = 8\nconst LINE_GAP = 3\n\n// # | description | qty | unit net | VAT % | net | VAT | gross (sums to CONTENT_WIDTH)\nconst TABLE_COLUMNS = [24, 179, 30, 62, 36, 62, 56, 66.28]\n\nconst TEXT = rgb(0.12, 0.12, 0.14)\nconst MUTED = rgb(0.42, 0.42, 0.46)\nconst RULE = rgb(0.82, 0.82, 0.85)\nconst HEADER_FILL = rgb(0.95, 0.95, 0.96)\nconst WATERMARK = rgb(0.85, 0.85, 0.87)\n\ntype Fonts = { regular: PDFFont; bold: PDFFont; charset: Set<number> }\n\nfunction base64ToBytes(value: string): Uint8Array {\n return Uint8Array.from(Buffer.from(value, 'base64'))\n}\n\n/** Replaces characters the embedded subset cannot draw. */\nexport function sanitizeForFont(text: string, charset: Set<number>): string {\n let out = ''\n for (const char of text.replace(/\\t/g, ' ')) {\n const code = char.codePointAt(0) ?? 63\n out += code === 10 || charset.has(code) ? char : '?'\n }\n return out\n}\n\nfunction wrap(text: string, font: PDFFont, size: number, width: number): string[] {\n const lines: string[] = []\n for (const paragraph of text.split('\\n')) {\n let current = ''\n for (const word of paragraph.split(/\\s+/).filter(Boolean)) {\n const candidate = current ? `${current} ${word}` : word\n if (font.widthOfTextAtSize(candidate, size) <= width) {\n current = candidate\n continue\n }\n if (current) lines.push(current)\n // A single word wider than the column is split by characters.\n let chunk = ''\n for (const char of word) {\n if (font.widthOfTextAtSize(chunk + char, size) > width && chunk) {\n lines.push(chunk)\n chunk = char\n } else {\n chunk += char\n }\n }\n current = chunk\n }\n lines.push(current)\n }\n return lines\n}\n\nclass Layout {\n readonly pages: PDFPage[] = []\n page!: PDFPage\n y = 0\n\n constructor(\n private readonly doc: PDFDocument,\n private readonly fonts: Fonts,\n ) {\n this.newPage()\n }\n\n get boldFont(): PDFFont {\n return this.fonts.bold\n }\n\n newPage(): void {\n this.page = this.doc.addPage([PAGE_WIDTH, PAGE_HEIGHT])\n this.pages.push(this.page)\n this.y = PAGE_HEIGHT - MARGIN\n }\n\n /** Starts a new page when fewer than `height` points remain above the footer. */\n ensure(height: number): boolean {\n if (this.y - height < MARGIN + FOOTER_HEIGHT) {\n this.newPage()\n return true\n }\n return false\n }\n\n text(value: string, x: number, opts: { size?: number; bold?: boolean; color?: ReturnType<typeof rgb>; y?: number } = {}) {\n const size = opts.size ?? BODY_SIZE\n this.page.drawText(sanitizeForFont(value, this.fonts.charset), {\n x,\n y: opts.y ?? this.y,\n size,\n font: opts.bold ? this.fonts.bold : this.fonts.regular,\n color: opts.color ?? TEXT,\n })\n }\n\n width(value: string, size: number, bold = false): number {\n return (bold ? this.fonts.bold : this.fonts.regular).widthOfTextAtSize(sanitizeForFont(value, this.fonts.charset), size)\n }\n\n rule(y = this.y): void {\n this.page.drawLine({ start: { x: MARGIN, y }, end: { x: PAGE_WIDTH - MARGIN, y }, thickness: 0.5, color: RULE })\n }\n}\n\nfunction drawTableHeader(layout: Layout, headers: string[], align: Array<'left' | 'right'>, columns: number[], x0 = MARGIN) {\n // Headers wrap inside their column (Polish labels are long: \"Cena netto\").\n const wrapped = headers.map((header, i) => wrap(header, layout.boldFont, SMALL_SIZE, columns[i] - 6))\n const lineHeight = SMALL_SIZE + 2\n const height = Math.max(...wrapped.map((lines) => lines.length)) * lineHeight + 8\n const total = columns.reduce((sum, w) => sum + w, 0)\n layout.page.drawRectangle({ x: x0, y: layout.y - height + 4, width: total, height, color: HEADER_FILL })\n let x = x0\n wrapped.forEach((lines, i) => {\n const w = columns[i]\n lines.forEach((line, li) => {\n const tx = align[i] === 'right' ? x + w - 3 - layout.width(line, SMALL_SIZE, true) : x + 3\n layout.text(line, tx, { size: SMALL_SIZE, bold: true, y: layout.y - SMALL_SIZE - 1 - li * lineHeight })\n })\n x += w\n })\n layout.y -= height + 2\n}\n\nfunction drawRows(\n layout: Layout,\n rows: string[][],\n headers: string[],\n align: Array<'left' | 'right'>,\n columns: number[],\n fonts: Fonts,\n x0 = MARGIN,\n) {\n const lineHeight = BODY_SIZE + LINE_GAP\n for (const row of rows) {\n const cells = row.map((cell, i) =>\n wrap(sanitizeForFont(cell, fonts.charset), fonts.regular, BODY_SIZE, columns[i] - 6),\n )\n const rowHeight = Math.max(...cells.map((c) => c.length)) * lineHeight + 4\n if (layout.ensure(rowHeight)) drawTableHeader(layout, headers, align, columns, x0)\n let x = x0\n cells.forEach((cellLines, i) => {\n const w = columns[i]\n cellLines.forEach((line, li) => {\n const tx = align[i] === 'right' ? x + w - 3 - layout.width(line, BODY_SIZE) : x + 3\n layout.text(line, tx, { y: layout.y - BODY_SIZE - li * lineHeight })\n })\n x += w\n })\n layout.y -= rowHeight\n layout.page.drawLine({\n start: { x: x0, y: layout.y + 1 },\n end: { x: x0 + columns.reduce((s, w) => s + w, 0), y: layout.y + 1 },\n thickness: 0.3,\n color: RULE,\n })\n }\n}\n\nfunction drawBlock(layout: Layout, heading: string, lines: string[], x: number, width: number, top: number, fonts: Fonts): number {\n let y = top\n layout.text(heading.toUpperCase(), x, { size: SMALL_SIZE, bold: true, color: MUTED, y })\n y -= SMALL_SIZE + 6\n lines.forEach((line, index) => {\n for (const part of wrap(sanitizeForFont(line, fonts.charset), index === 0 ? fonts.bold : fonts.regular, BODY_SIZE, width)) {\n layout.text(part, x, { bold: index === 0, y })\n y -= BODY_SIZE + LINE_GAP\n }\n })\n return y\n}\n\nasync function embedLogo(doc: PDFDocument, logo: PdfLogo | null | undefined): Promise<PDFImage | null> {\n if (!logo) return null\n try {\n if (logo.mimeType === 'image/png') return await doc.embedPng(logo.bytes)\n if (logo.mimeType === 'image/jpeg' || logo.mimeType === 'image/jpg') return await doc.embedJpg(logo.bytes)\n } catch (err) {\n console.warn('[billing.pdf] logo could not be embedded', err)\n }\n return null\n}\n\nexport async function renderInvoicePdf(model: InvoiceDocumentModel, opts: { logo?: PdfLogo | null } = {}): Promise<Uint8Array> {\n const doc = await PDFDocument.create()\n doc.registerFontkit(fontkit)\n // subset: false \u2014 the fonts are already subset at build time, and pdf-lib's\n // own subsetting drops kerning on some glyph runs.\n const regular = await doc.embedFont(base64ToBytes(NOTO_SANS_REGULAR_BASE64), { subset: false })\n const bold = await doc.embedFont(base64ToBytes(NOTO_SANS_BOLD_BASE64), { subset: false })\n const fonts: Fonts = { regular, bold, charset: new Set(regular.getCharacterSet()) }\n doc.setTitle(sanitizeForFont(`${model.title} ${model.number}`, fonts.charset))\n doc.setLanguage(model.language)\n doc.setProducer('Dainamite Billing')\n\n const layout = new Layout(doc, fonts)\n\n // \u2500\u2500 Header: logo left, title + number right \u2500\u2500\n const logo = await embedLogo(doc, opts.logo)\n if (logo) {\n const scale = Math.min(140 / logo.width, 48 / logo.height, 1)\n layout.page.drawImage(logo, {\n x: MARGIN,\n y: layout.y - logo.height * scale,\n width: logo.width * scale,\n height: logo.height * scale,\n })\n }\n const titleSize = 18\n layout.text(model.title, PAGE_WIDTH - MARGIN - layout.width(model.title, titleSize, true), {\n size: titleSize,\n bold: true,\n y: layout.y - titleSize,\n })\n layout.text(model.number, PAGE_WIDTH - MARGIN - layout.width(model.number, 11), {\n size: 11,\n color: MUTED,\n y: layout.y - titleSize - 16,\n })\n layout.y -= 64\n\n // \u2500\u2500 Meta rows (right-aligned key: value list) \u2500\u2500\n for (const [label, value] of model.meta) {\n const text = `${label}: ${value}`\n layout.text(text, PAGE_WIDTH - MARGIN - layout.width(text, BODY_SIZE), { y: layout.y })\n layout.y -= BODY_SIZE + LINE_GAP\n }\n layout.y -= 10\n\n // \u2500\u2500 Seller | Buyer \u2500\u2500\n const half = CONTENT_WIDTH / 2 - 10\n const top = layout.y\n const sellerBottom = drawBlock(layout, model.seller.heading, model.seller.lines, MARGIN, half, top, fonts)\n const buyerBottom = drawBlock(layout, model.buyer.heading, model.buyer.lines, MARGIN + half + 20, half, top, fonts)\n layout.y = Math.min(sellerBottom, buyerBottom) - 14\n\n // \u2500\u2500 Lines \u2500\u2500\n layout.ensure(40)\n drawTableHeader(layout, model.table.headers, model.table.align, TABLE_COLUMNS)\n drawRows(layout, model.table.rows, model.table.headers, model.table.align, TABLE_COLUMNS, fonts)\n layout.y -= 14\n\n // \u2500\u2500 VAT summary (right half) + totals \u2500\u2500\n const summaryColumns = [60, 75, 70, 85]\n const summaryX = PAGE_WIDTH - MARGIN - summaryColumns.reduce((s, w) => s + w, 0)\n layout.ensure(SMALL_SIZE + 30 + model.vatSummary.rows.length * (BODY_SIZE + 7))\n layout.text(model.vatSummary.heading, summaryX, { size: SMALL_SIZE, bold: true, color: MUTED })\n layout.y -= SMALL_SIZE + 4\n const summaryAlign: Array<'left' | 'right'> = ['right', 'right', 'right', 'right']\n drawTableHeader(layout, model.vatSummary.headers, summaryAlign, summaryColumns, summaryX)\n drawRows(layout, model.vatSummary.rows, model.vatSummary.headers, summaryAlign, summaryColumns, fonts, summaryX)\n layout.y -= 10\n\n layout.ensure(model.totals.length * 16 + 10)\n model.totals.forEach(([label, value], index) => {\n const last = index === model.totals.length - 1\n const size = last ? 12 : BODY_SIZE + 1\n layout.text(value, PAGE_WIDTH - MARGIN - layout.width(value, size, last), { size, bold: last })\n layout.text(label, summaryX, { size, bold: last, color: last ? TEXT : MUTED })\n layout.y -= size + 6\n })\n layout.y -= 10\n\n // \u2500\u2500 Payment instructions \u2500\u2500\n if (model.payment) {\n layout.ensure((model.payment.lines.length + 1) * (BODY_SIZE + LINE_GAP) + 16)\n layout.rule(layout.y + 4)\n layout.y -= 10\n layout.text(model.payment.heading.toUpperCase(), MARGIN, { size: SMALL_SIZE, bold: true, color: MUTED })\n layout.y -= SMALL_SIZE + 6\n for (const line of model.payment.lines) {\n layout.text(line, MARGIN)\n layout.y -= BODY_SIZE + LINE_GAP\n }\n }\n\n // \u2500\u2500 Footer, page numbers, watermark on every page \u2500\u2500\n const pages = layout.pages\n pages.forEach((page, index) => {\n layout.page = page\n let fy = MARGIN + FOOTER_HEIGHT - 14\n for (const line of model.footer.slice(0, 3)) {\n for (const part of wrap(sanitizeForFont(line, fonts.charset), regular, SMALL_SIZE - 1, CONTENT_WIDTH - 90).slice(0, 2)) {\n layout.text(part, MARGIN, { size: SMALL_SIZE - 1, color: MUTED, y: fy })\n fy -= SMALL_SIZE + 1\n }\n }\n const pageText = model.pageLabel(index + 1, pages.length)\n layout.text(pageText, PAGE_WIDTH - MARGIN - layout.width(pageText, SMALL_SIZE), {\n size: SMALL_SIZE,\n color: MUTED,\n y: MARGIN,\n })\n if (model.watermark) {\n const size = 96\n const text = sanitizeForFont(model.watermark, fonts.charset)\n const w = bold.widthOfTextAtSize(text, size)\n page.drawText(text, {\n x: PAGE_WIDTH / 2 - (w / 2) * Math.cos(Math.PI / 4),\n y: PAGE_HEIGHT / 2 - (w / 2) * Math.sin(Math.PI / 4),\n size,\n font: bold,\n color: WATERMARK,\n rotate: degrees(45),\n opacity: 0.5,\n })\n }\n })\n\n return doc.save()\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,43 +1,35 @@
1
- import { ModuleConfig } from "@open-mercato/core/modules/configs/data/entities";
2
- async function readConfig(em, name) {
3
- const row = await em.findOne(ModuleConfig, { moduleId: "billing", name });
4
- if (!row) return void 0;
5
- return row.valueJson;
6
- }
1
+ import { getBillingSettings, receiptTaxRate } from "./billingSettings.js";
7
2
  function asNumberString(value, fallback) {
8
3
  if (value === null || value === void 0) return fallback;
9
4
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
10
5
  if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) {
11
- return value.trim();
6
+ return String(Number(value));
12
7
  }
13
8
  return fallback;
14
9
  }
15
- async function getLowBalanceThresholdDefault(em) {
16
- return asNumberString(await readConfig(em, "prepaid.low_balance_threshold_default"), "0");
10
+ async function getLowBalanceThresholdDefault(em, scope) {
11
+ return asNumberString((await getBillingSettings(em, scope)).lowBalanceThresholdDefault, "0");
12
+ }
13
+ async function getNearLimitBufferDefault(em, scope) {
14
+ return asNumberString((await getBillingSettings(em, scope)).nearLimitBufferDefault, "0");
17
15
  }
18
- async function getNearLimitBufferDefault(em) {
19
- return asNumberString(await readConfig(em, "credit.near_limit_buffer_default"), "0");
16
+ async function getTopupProvider(em, scope) {
17
+ const value = (await getBillingSettings(em, scope)).topupProvider;
18
+ return typeof value === "string" && value.trim() ? value.trim() : "stripe";
20
19
  }
21
- async function getTopupProvider(em) {
22
- const value = await readConfig(em, "prepaid.topup_provider");
23
- return typeof value === "string" && value.trim() !== "" ? value.trim() : "stripe";
20
+ async function getStatementEnabled(em, scope) {
21
+ return (await getBillingSettings(em, scope)).statementEnabled !== false;
24
22
  }
25
- async function getStatementEnabled(em) {
26
- const value = await readConfig(em, "prepaid.statement_enabled");
27
- if (value === void 0 || value === null) return true;
28
- if (typeof value === "boolean") return value;
29
- if (typeof value === "string") return value.trim().toLowerCase() !== "false";
30
- return Boolean(value);
23
+ async function getTopupTaxRate(em, scope) {
24
+ return receiptTaxRate(await getBillingSettings(em, scope));
31
25
  }
32
- async function getTopupTaxRate(em) {
33
- const value = await readConfig(em, "prepaid.topup_tax_rate");
34
- if (typeof value === "number" && Number.isFinite(value)) return value;
35
- if (typeof value === "string" && Number.isFinite(Number(value))) return Number(value);
36
- return 0;
26
+ async function getPrepaidTaxPoint(em, scope) {
27
+ return (await getBillingSettings(em, scope)).prepaidTaxPoint === "consumption" ? "consumption" : "topup";
37
28
  }
38
29
  export {
39
30
  getLowBalanceThresholdDefault,
40
31
  getNearLimitBufferDefault,
32
+ getPrepaidTaxPoint,
41
33
  getStatementEnabled,
42
34
  getTopupProvider,
43
35
  getTopupTaxRate
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/billing/lib/prepaidConfig.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { ModuleConfig } from '@open-mercato/core/modules/configs/data/entities'\n\n/**\n * Typed readers for the prepaid billing module configs (SPEC-002), seeded by\n * `setup.ts \u2192 seedBillingConfigs`. Stored as raw JSON scalars in\n * `ModuleConfig.valueJson`. Each reader falls back to the same default the\n * seed uses, so a missing row (older tenant) behaves identically.\n */\n\nasync function readConfig(em: EntityManager, name: string): Promise<unknown> {\n const row = await em.findOne(ModuleConfig, { moduleId: 'billing', name })\n if (!row) return undefined\n return row.valueJson as unknown\n}\n\nfunction asNumberString(value: unknown, fallback: string): string {\n if (value === null || value === undefined) return fallback\n if (typeof value === 'number' && Number.isFinite(value)) return String(value)\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {\n return value.trim()\n }\n return fallback\n}\n\n/** `prepaid.low_balance_threshold_default` \u2014 numeric string (default '0'). */\nexport async function getLowBalanceThresholdDefault(em: EntityManager): Promise<string> {\n return asNumberString(await readConfig(em, 'prepaid.low_balance_threshold_default'), '0')\n}\n\n/** `credit.near_limit_buffer_default` \u2014 numeric string (default '0'). */\nexport async function getNearLimitBufferDefault(em: EntityManager): Promise<string> {\n return asNumberString(await readConfig(em, 'credit.near_limit_buffer_default'), '0')\n}\n\n/** `prepaid.topup_provider` \u2014 gateway provider key (default 'stripe'). */\nexport async function getTopupProvider(em: EntityManager): Promise<string> {\n const value = await readConfig(em, 'prepaid.topup_provider')\n return typeof value === 'string' && value.trim() !== '' ? value.trim() : 'stripe'\n}\n\n/** `prepaid.statement_enabled` \u2014 master switch (default true). */\nexport async function getStatementEnabled(em: EntityManager): Promise<boolean> {\n const value = await readConfig(em, 'prepaid.statement_enabled')\n if (value === undefined || value === null) return true\n if (typeof value === 'boolean') return value\n // A stored string \"false\" must read as false (Boolean(\"false\") === true).\n if (typeof value === 'string') return value.trim().toLowerCase() !== 'false'\n return Boolean(value)\n}\n\n/** `prepaid.topup_tax_rate` \u2014 VAT percent on top-up receipts (default 0). */\nexport async function getTopupTaxRate(em: EntityManager): Promise<number> {\n const value = await readConfig(em, 'prepaid.topup_tax_rate')\n if (typeof value === 'number' && Number.isFinite(value)) return value\n if (typeof value === 'string' && Number.isFinite(Number(value))) return Number(value)\n return 0\n}\n"],
5
- "mappings": "AACA,SAAS,oBAAoB;AAS7B,eAAe,WAAW,IAAmB,MAAgC;AAC3E,QAAM,MAAM,MAAM,GAAG,QAAQ,cAAc,EAAE,UAAU,WAAW,KAAK,CAAC;AACxE,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI;AACb;AAEA,SAAS,eAAe,OAAgB,UAA0B;AAChE,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO,OAAO,KAAK;AAC5E,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,MAAM,OAAO,SAAS,OAAO,KAAK,CAAC,GAAG;AACtF,WAAO,MAAM,KAAK;AAAA,EACpB;AACA,SAAO;AACT;AAGA,eAAsB,8BAA8B,IAAoC;AACtF,SAAO,eAAe,MAAM,WAAW,IAAI,uCAAuC,GAAG,GAAG;AAC1F;AAGA,eAAsB,0BAA0B,IAAoC;AAClF,SAAO,eAAe,MAAM,WAAW,IAAI,kCAAkC,GAAG,GAAG;AACrF;AAGA,eAAsB,iBAAiB,IAAoC;AACzE,QAAM,QAAQ,MAAM,WAAW,IAAI,wBAAwB;AAC3D,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,IAAI;AAC3E;AAGA,eAAsB,oBAAoB,IAAqC;AAC7E,QAAM,QAAQ,MAAM,WAAW,IAAI,2BAA2B;AAC9D,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,UAAW,QAAO;AAEvC,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,KAAK,EAAE,YAAY,MAAM;AACrE,SAAO,QAAQ,KAAK;AACtB;AAGA,eAAsB,gBAAgB,IAAoC;AACxE,QAAM,QAAQ,MAAM,WAAW,IAAI,wBAAwB;AAC3D,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO;AAChE,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,OAAO,KAAK,CAAC,EAAG,QAAO,OAAO,KAAK;AACpF,SAAO;AACT;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { getBillingSettings, receiptTaxRate, type SettingsScope } from './billingSettings'\n\n/**\n * Typed readers for the prepaid billing settings (SPEC-002), stored per\n * organization in `billing_settings` (Spec 0, E-D2 \u2014 previously tenant-wide\n * core `configs` rows). Every reader takes the organization scope; the\n * defaults are identical to the old config seed, so behaviour is unchanged.\n */\n\nfunction asNumberString(value: unknown, fallback: string): string {\n if (value === null || value === undefined) return fallback\n if (typeof value === 'number' && Number.isFinite(value)) return String(value)\n if (typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))) {\n return String(Number(value))\n }\n return fallback\n}\n\n/** Low-balance threshold default \u2014 numeric string (default '0'). */\nexport async function getLowBalanceThresholdDefault(em: EntityManager, scope: SettingsScope): Promise<string> {\n return asNumberString((await getBillingSettings(em, scope)).lowBalanceThresholdDefault, '0')\n}\n\n/** Near-credit-limit buffer default \u2014 numeric string (default '0'). */\nexport async function getNearLimitBufferDefault(em: EntityManager, scope: SettingsScope): Promise<string> {\n return asNumberString((await getBillingSettings(em, scope)).nearLimitBufferDefault, '0')\n}\n\n/** Gateway provider key for top-ups (default 'stripe'). */\nexport async function getTopupProvider(em: EntityManager, scope: SettingsScope): Promise<string> {\n const value = (await getBillingSettings(em, scope)).topupProvider\n return typeof value === 'string' && value.trim() ? value.trim() : 'stripe'\n}\n\n/** Consumption statements master switch (default true). */\nexport async function getStatementEnabled(em: EntityManager, scope: SettingsScope): Promise<boolean> {\n return (await getBillingSettings(em, scope)).statementEnabled !== false\n}\n\n/** VAT percent on top-up receipts (default 0; always 0 when tax is charged at consumption). */\nexport async function getTopupTaxRate(em: EntityManager, scope: SettingsScope): Promise<number> {\n return receiptTaxRate(await getBillingSettings(em, scope))\n}\n\n/** Where prepaid VAT is charged (Spec F, F-D3). Default `topup`. */\nexport async function getPrepaidTaxPoint(em: EntityManager, scope: SettingsScope): Promise<'topup' | 'consumption'> {\n return (await getBillingSettings(em, scope)).prepaidTaxPoint === 'consumption' ? 'consumption' : 'topup'\n}\n"],
5
+ "mappings": "AACA,SAAS,oBAAoB,sBAA0C;AASvE,SAAS,eAAe,OAAgB,UAA0B;AAChE,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,EAAG,QAAO,OAAO,KAAK;AAC5E,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,MAAM,OAAO,SAAS,OAAO,KAAK,CAAC,GAAG;AACtF,WAAO,OAAO,OAAO,KAAK,CAAC;AAAA,EAC7B;AACA,SAAO;AACT;AAGA,eAAsB,8BAA8B,IAAmB,OAAuC;AAC5G,SAAO,gBAAgB,MAAM,mBAAmB,IAAI,KAAK,GAAG,4BAA4B,GAAG;AAC7F;AAGA,eAAsB,0BAA0B,IAAmB,OAAuC;AACxG,SAAO,gBAAgB,MAAM,mBAAmB,IAAI,KAAK,GAAG,wBAAwB,GAAG;AACzF;AAGA,eAAsB,iBAAiB,IAAmB,OAAuC;AAC/F,QAAM,SAAS,MAAM,mBAAmB,IAAI,KAAK,GAAG;AACpD,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,IAAI,MAAM,KAAK,IAAI;AACpE;AAGA,eAAsB,oBAAoB,IAAmB,OAAwC;AACnG,UAAQ,MAAM,mBAAmB,IAAI,KAAK,GAAG,qBAAqB;AACpE;AAGA,eAAsB,gBAAgB,IAAmB,OAAuC;AAC9F,SAAO,eAAe,MAAM,mBAAmB,IAAI,KAAK,CAAC;AAC3D;AAGA,eAAsB,mBAAmB,IAAmB,OAAwD;AAClH,UAAQ,MAAM,mBAAmB,IAAI,KAAK,GAAG,oBAAoB,gBAAgB,gBAAgB;AACnG;",
6
6
  "names": []
7
7
  }
@@ -18,12 +18,16 @@ import {
18
18
  prepaidCreditUsed
19
19
  } from "./creditStatus.js";
20
20
  import { buildBalanceCrossingEvents } from "./balanceEvents.js";
21
- import { absMoney, negateMoney } from "./money.js";
21
+ import { absMoney, fromUnits, negateMoney } from "./money.js";
22
22
  import {
23
23
  getLowBalanceThresholdDefault,
24
- getNearLimitBufferDefault
24
+ getNearLimitBufferDefault,
25
+ getPrepaidTaxPoint
25
26
  } from "./prepaidConfig.js";
26
27
  import { rateUsageForItem } from "./usageRater.js";
28
+ import { priceBasisOf } from "./lineAmounts.js";
29
+ import { resolvePrepaidDebit } from "./prepaidTaxPoint.js";
30
+ import { isTieredRate } from "../data/validators.js";
27
31
  function breakdownFromMetadata(meta) {
28
32
  const b = meta?.usage_tier_breakdown;
29
33
  return Array.isArray(b) ? b : [];
@@ -47,9 +51,9 @@ async function consumePrepaidUsage(em, account, input) {
47
51
  const currencyCode = account.currencyCode;
48
52
  const threshold = resolveLowBalanceThreshold(
49
53
  await readAccountThreshold(em, input),
50
- await getLowBalanceThresholdDefault(em)
54
+ await getLowBalanceThresholdDefault(em, { tenantId: account.tenantId, organizationId: account.organizationId })
51
55
  );
52
- const nearLimitBuffer = await getNearLimitBufferDefault(em);
56
+ const nearLimitBuffer = await getNearLimitBufferDefault(em, { tenantId: account.tenantId, organizationId: account.organizationId });
53
57
  const creditLimit = account.creditLimit ?? "0";
54
58
  if (input.sourceRef) {
55
59
  const existing = await em.findOne(BillingAccountUsage, {
@@ -122,13 +126,21 @@ async function consumePrepaidUsage(em, account, input) {
122
126
  breakdown = rated.breakdown;
123
127
  }
124
128
  }
129
+ const taxPoint = await getPrepaidTaxPoint(em, { tenantId: input.tenantId, organizationId: input.organizationId });
130
+ const debit = resolvePrepaidDebit({
131
+ lineAmount: ratedAmount,
132
+ priceBasis: item && !isTieredRate(item.rateJson) ? priceBasisOf(item.priceBasis) : "net",
133
+ taxRate: item?.taxRate ?? "0",
134
+ taxPoint
135
+ });
136
+ const debitAmount = fromUnits(Math.round(debit.debit * 1e4));
125
137
  const movement = await applyBalanceMovement(em, {
126
138
  organizationId: input.organizationId,
127
139
  tenantId: input.tenantId,
128
140
  billAccountId: input.billAccountId,
129
141
  currencyCode,
130
142
  type: "usage",
131
- amount: negateMoney(ratedAmount),
143
+ amount: negateMoney(debitAmount),
132
144
  description: buildDescription(item, input, warning),
133
145
  usageId: usage.id,
134
146
  billingItemId: item?.id ?? null,
@@ -136,6 +148,11 @@ async function consumePrepaidUsage(em, account, input) {
136
148
  metadata: {
137
149
  uom_code: input.uomCode,
138
150
  usage_tier_breakdown: breakdown,
151
+ rated_amount: ratedAmount,
152
+ net: fromUnits(Math.round(debit.net * 1e4)),
153
+ tax: fromUnits(Math.round(debit.tax * 1e4)),
154
+ tax_rate: String(debit.taxRate),
155
+ tax_point: taxPoint,
139
156
  ...warning ? { warning } : {}
140
157
  }
141
158
  });
@@ -162,7 +179,7 @@ async function consumePrepaidUsage(em, account, input) {
162
179
  balance: balanceAfter,
163
180
  usageId: usage.id,
164
181
  transactionId: movement.transaction.id,
165
- ratedAmount,
182
+ ratedAmount: debitAmount,
166
183
  balanceStatus
167
184
  }
168
185
  });
@@ -176,7 +193,9 @@ async function consumePrepaidUsage(em, account, input) {
176
193
  result: {
177
194
  id: usage.id,
178
195
  deduplicated: false,
179
- ratedAmount,
196
+ // What left the balance (equals the rating in `topup` mode) — the same
197
+ // value the dedup path reads back from the transaction.
198
+ ratedAmount: debitAmount,
180
199
  currencyCode,
181
200
  balance: balanceAfter,
182
201
  balanceStatus,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/billing/lib/prepaidConsume.ts"],
4
- "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n BillingAccount,\n BillingAccountBalance,\n BillingAccountTransaction,\n BillingAccountUsage,\n BillingItem,\n} from '../data/entities'\nimport type { BalanceStatus, CreditStatus } from '../data/validators'\nimport {\n applyBalanceMovement,\n PREPAID_USAGE_SENTINEL_RUN_ID,\n} from './balanceLedger'\nimport {\n computeBalanceStatus,\n resolveLowBalanceThreshold,\n} from './balanceStatus'\nimport {\n computeCreditStatus,\n computeAvailableCredit,\n prepaidCreditUsed,\n} from './creditStatus'\nimport { buildBalanceCrossingEvents, type DomainEvent } from './balanceEvents'\nimport { absMoney, negateMoney } from './money'\nimport {\n getLowBalanceThresholdDefault,\n getNearLimitBufferDefault,\n} from './prepaidConfig'\nimport { rateUsageForItem, type TierBreakdownEntry } from './usageRater'\n\n/**\n * Real-time prepaid consume (SPEC-002 P1).\n *\n * For a prepaid account, a usage upload is rated in real time, debited from\n * the balance, and the remaining balance is returned \u2014 *\"I uploaded usage and\n * got my remaining balance back.\"* Usage is NEVER rejected: a missing price or\n * an exhausted balance still records the usage; we report status and let the\n * client throttle itself.\n *\n * The caller MUST run this inside an `em.transactional(...)` so the usage\n * insert, the atomic balance debit, and the transaction insert commit (or roll\n * back) together. Domain events are returned (not emitted) so the caller can\n * fire them AFTER the DB commit.\n */\n\nexport type PrepaidConsumeInput = {\n organizationId: string\n tenantId: string\n billAccountId: string\n uomCode: string\n quantity: number\n periodStart: Date\n periodEnd: Date\n lineDescription?: string | null\n sourceRef?: string | null\n}\n\nexport type PrepaidConsumeResult = {\n id: string\n deduplicated: boolean\n ratedAmount: string\n currencyCode: string\n balance: string\n balanceStatus: BalanceStatus\n creditStatus: CreditStatus\n tierBreakdown: TierBreakdownEntry[]\n warning: string | null\n}\n\nexport type PrepaidConsumeOutcome = {\n result: PrepaidConsumeResult\n events: DomainEvent[]\n}\n\nfunction breakdownFromMetadata(meta: Record<string, unknown> | null | undefined): TierBreakdownEntry[] {\n const b = meta?.usage_tier_breakdown\n return Array.isArray(b) ? (b as TierBreakdownEntry[]) : []\n}\n\n/**\n * Resolve the active `type=usage` Billing Item priced for a uom (exact match,\n * no conversion \u2014 same rule as the postpaid runner). Latest active item wins.\n */\nasync function findUsageItem(\n em: EntityManager,\n input: PrepaidConsumeInput,\n): Promise<BillingItem | null> {\n return em.findOne(\n BillingItem,\n {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n billAccountId: input.billAccountId,\n type: 'usage',\n uomCode: input.uomCode,\n isActive: true,\n deletedAt: null,\n } as never,\n { orderBy: { createdAt: 'DESC' } },\n )\n}\n\nexport async function consumePrepaidUsage(\n em: EntityManager,\n account: BillingAccount,\n input: PrepaidConsumeInput,\n): Promise<PrepaidConsumeOutcome> {\n const currencyCode = account.currencyCode\n const threshold = resolveLowBalanceThreshold(\n await readAccountThreshold(em, input),\n await getLowBalanceThresholdDefault(em),\n )\n const nearLimitBuffer = await getNearLimitBufferDefault(em)\n const creditLimit = account.creditLimit ?? '0'\n\n // \u2500\u2500\u2500 Idempotency: a duplicate sourceRef never double-debits \u2500\u2500\u2500\u2500\n if (input.sourceRef) {\n const existing = await em.findOne(BillingAccountUsage, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n sourceRef: input.sourceRef,\n deletedAt: null,\n })\n if (existing) {\n const txn = await em.findOne(BillingAccountTransaction, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n usageId: existing.id,\n deletedAt: null,\n })\n const balance = txn?.balanceAfter ?? (await readCurrentBalance(em, input))\n const ratedAmount = txn ? absMoney(txn.amount) : '0.0000'\n const warning = (txn?.metadata?.warning as string | undefined) ?? null\n return {\n result: {\n id: existing.id,\n deduplicated: true,\n ratedAmount,\n currencyCode,\n balance,\n balanceStatus: computeBalanceStatus(balance, threshold),\n creditStatus: computeCreditStatus(\n computeAvailableCredit(creditLimit, prepaidCreditUsed(balance)),\n nearLimitBuffer,\n prepaidCreditUsed(balance),\n ),\n tierBreakdown: breakdownFromMetadata(txn?.metadata),\n warning,\n },\n events: [],\n }\n }\n }\n\n // \u2500\u2500\u2500 3. Persist the usage record (rated at ingest = sentinel) \u2500\u2500\n const now = new Date()\n const usage = em.create(BillingAccountUsage, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n uomCode: input.uomCode,\n quantity: input.quantity.toFixed(4),\n periodStart: input.periodStart,\n periodEnd: input.periodEnd,\n lineDescription: input.lineDescription ?? null,\n sourceRef: input.sourceRef ?? null,\n // Stamped immediately so a postpaid-style run never re-rates it.\n ratedInBillRunId: PREPAID_USAGE_SENTINEL_RUN_ID,\n isActive: true,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(usage)\n await em.flush()\n\n // \u2500\u2500\u2500 4. Rate against the matching usage item \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const item = await findUsageItem(em, input)\n let ratedAmount = '0.0000'\n let breakdown: TierBreakdownEntry[] = []\n let warning: string | null = null\n if (!item) {\n warning = 'no_matching_item'\n } else {\n const rated = rateUsageForItem(item, input.quantity)\n if (!rated) {\n warning = 'unratable'\n } else {\n ratedAmount = rated.amount\n breakdown = rated.breakdown\n }\n }\n\n // \u2500\u2500\u2500 5\u20137. Atomic debit + paired transaction (signed amount) \u2500\u2500\u2500\u2500\n const movement = await applyBalanceMovement(em, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n currencyCode,\n type: 'usage',\n amount: negateMoney(ratedAmount),\n description: buildDescription(item, input, warning),\n usageId: usage.id,\n billingItemId: item?.id ?? null,\n sourceRef: input.sourceRef ?? null,\n metadata: {\n uom_code: input.uomCode,\n usage_tier_breakdown: breakdown,\n ...(warning ? { warning } : {}),\n },\n })\n\n const { balanceBefore, balanceAfter } = movement\n\n // \u2500\u2500\u2500 8. Status + crossing events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const balanceStatus = computeBalanceStatus(balanceAfter, threshold)\n const scope = {\n billAccountId: input.billAccountId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n currencyCode,\n }\n const events: DomainEvent[] = buildBalanceCrossingEvents({\n scope,\n balanceBefore,\n balanceAfter,\n threshold,\n creditLimit,\n nearLimitBuffer,\n })\n\n // Optional per-debit signal (excluded from workflow triggers).\n events.push({\n id: 'billing.usage.rated',\n payload: {\n ...scope,\n balance: balanceAfter,\n usageId: usage.id,\n transactionId: movement.transaction.id,\n ratedAmount,\n balanceStatus,\n },\n })\n\n const usedAfter = prepaidCreditUsed(balanceAfter)\n const creditStatus = computeCreditStatus(\n computeAvailableCredit(creditLimit, usedAfter),\n nearLimitBuffer,\n usedAfter,\n )\n\n return {\n result: {\n id: usage.id,\n deduplicated: false,\n ratedAmount,\n currencyCode,\n balance: balanceAfter,\n balanceStatus,\n creditStatus,\n tierBreakdown: breakdown,\n warning,\n },\n events,\n }\n}\n\nfunction buildDescription(\n item: BillingItem | null,\n input: PrepaidConsumeInput,\n warning: string | null,\n): string {\n if (input.lineDescription) return input.lineDescription\n const qty = input.quantity.toLocaleString('en-US')\n if (!item) return `Usage \u2014 ${qty} ${input.uomCode} (no matching item)`\n if (warning === 'unratable') return `Usage \u2014 ${qty} ${input.uomCode} (unratable)`\n return `${item.description} \u2014 ${qty} ${input.uomCode}`\n}\n\nasync function readAccountThreshold(\n em: EntityManager,\n input: PrepaidConsumeInput,\n): Promise<string | null> {\n const row = await em.findOne(BillingAccountBalance, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n })\n return row?.lowBalanceThreshold ?? null\n}\n\nasync function readCurrentBalance(\n em: EntityManager,\n input: PrepaidConsumeInput,\n): Promise<string> {\n const row = await em.findOne(BillingAccountBalance, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n })\n return row?.balance ?? '0.0000'\n}\n"],
5
- "mappings": "AACA;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,kCAAoD;AAC7D,SAAS,UAAU,mBAAmB;AACtC;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAiD;AA8C1D,SAAS,sBAAsB,MAAwE;AACrG,QAAM,IAAI,MAAM;AAChB,SAAO,MAAM,QAAQ,CAAC,IAAK,IAA6B,CAAC;AAC3D;AAMA,eAAe,cACb,IACA,OAC6B;AAC7B,SAAO,GAAG;AAAA,IACR;AAAA,IACA;AAAA,MACE,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,eAAe,MAAM;AAAA,MACrB,MAAM;AAAA,MACN,SAAS,MAAM;AAAA,MACf,UAAU;AAAA,MACV,WAAW;AAAA,IACb;AAAA,IACA,EAAE,SAAS,EAAE,WAAW,OAAO,EAAE;AAAA,EACnC;AACF;AAEA,eAAsB,oBACpB,IACA,SACA,OACgC;AAChC,QAAM,eAAe,QAAQ;AAC7B,QAAM,YAAY;AAAA,IAChB,MAAM,qBAAqB,IAAI,KAAK;AAAA,IACpC,MAAM,8BAA8B,EAAE;AAAA,EACxC;AACA,QAAM,kBAAkB,MAAM,0BAA0B,EAAE;AAC1D,QAAM,cAAc,QAAQ,eAAe;AAG3C,MAAI,MAAM,WAAW;AACnB,UAAM,WAAW,MAAM,GAAG,QAAQ,qBAAqB;AAAA,MACrD,UAAU,MAAM;AAAA,MAChB,eAAe,MAAM;AAAA,MACrB,WAAW,MAAM;AAAA,MACjB,WAAW;AAAA,IACb,CAAC;AACD,QAAI,UAAU;AACZ,YAAM,MAAM,MAAM,GAAG,QAAQ,2BAA2B;AAAA,QACtD,UAAU,MAAM;AAAA,QAChB,eAAe,MAAM;AAAA,QACrB,SAAS,SAAS;AAAA,QAClB,WAAW;AAAA,MACb,CAAC;AACD,YAAM,UAAU,KAAK,gBAAiB,MAAM,mBAAmB,IAAI,KAAK;AACxE,YAAMA,eAAc,MAAM,SAAS,IAAI,MAAM,IAAI;AACjD,YAAMC,WAAW,KAAK,UAAU,WAAkC;AAClE,aAAO;AAAA,QACL,QAAQ;AAAA,UACN,IAAI,SAAS;AAAA,UACb,cAAc;AAAA,UACd,aAAAD;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,qBAAqB,SAAS,SAAS;AAAA,UACtD,cAAc;AAAA,YACZ,uBAAuB,aAAa,kBAAkB,OAAO,CAAC;AAAA,YAC9D;AAAA,YACA,kBAAkB,OAAO;AAAA,UAC3B;AAAA,UACA,eAAe,sBAAsB,KAAK,QAAQ;AAAA,UAClD,SAAAC;AAAA,QACF;AAAA,QACA,QAAQ,CAAC;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAGA,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,QAAQ,GAAG,OAAO,qBAAqB;AAAA,IAC3C,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,IAChB,eAAe,MAAM;AAAA,IACrB,SAAS,MAAM;AAAA,IACf,UAAU,MAAM,SAAS,QAAQ,CAAC;AAAA,IAClC,aAAa,MAAM;AAAA,IACnB,WAAW,MAAM;AAAA,IACjB,iBAAiB,MAAM,mBAAmB;AAAA,IAC1C,WAAW,MAAM,aAAa;AAAA;AAAA,IAE9B,kBAAkB;AAAA,IAClB,UAAU;AAAA,IACV,WAAW;AAAA,IACX,WAAW;AAAA,EACb,CAAC;AACD,KAAG,QAAQ,KAAK;AAChB,QAAM,GAAG,MAAM;AAGf,QAAM,OAAO,MAAM,cAAc,IAAI,KAAK;AAC1C,MAAI,cAAc;AAClB,MAAI,YAAkC,CAAC;AACvC,MAAI,UAAyB;AAC7B,MAAI,CAAC,MAAM;AACT,cAAU;AAAA,EACZ,OAAO;AACL,UAAM,QAAQ,iBAAiB,MAAM,MAAM,QAAQ;AACnD,QAAI,CAAC,OAAO;AACV,gBAAU;AAAA,IACZ,OAAO;AACL,oBAAc,MAAM;AACpB,kBAAY,MAAM;AAAA,IACpB;AAAA,EACF;AAGA,QAAM,WAAW,MAAM,qBAAqB,IAAI;AAAA,IAC9C,gBAAgB,MAAM;AAAA,IACtB,UAAU,MAAM;AAAA,IAChB,eAAe,MAAM;AAAA,IACrB;AAAA,IACA,MAAM;AAAA,IACN,QAAQ,YAAY,WAAW;AAAA,IAC/B,aAAa,iBAAiB,MAAM,OAAO,OAAO;AAAA,IAClD,SAAS,MAAM;AAAA,IACf,eAAe,MAAM,MAAM;AAAA,IAC3B,WAAW,MAAM,aAAa;AAAA,IAC9B,UAAU;AAAA,MACR,UAAU,MAAM;AAAA,MAChB,sBAAsB;AAAA,MACtB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,IAC/B;AAAA,EACF,CAAC;AAED,QAAM,EAAE,eAAe,aAAa,IAAI;AAGxC,QAAM,gBAAgB,qBAAqB,cAAc,SAAS;AAClE,QAAM,QAAQ;AAAA,IACZ,eAAe,MAAM;AAAA,IACrB,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB;AAAA,EACF;AACA,QAAM,SAAwB,2BAA2B;AAAA,IACvD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,SAAO,KAAK;AAAA,IACV,IAAI;AAAA,IACJ,SAAS;AAAA,MACP,GAAG;AAAA,MACH,SAAS;AAAA,MACT,SAAS,MAAM;AAAA,MACf,eAAe,SAAS,YAAY;AAAA,MACpC;AAAA,MACA;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,YAAY,kBAAkB,YAAY;AAChD,QAAM,eAAe;AAAA,IACnB,uBAAuB,aAAa,SAAS;AAAA,IAC7C;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,IAAI,MAAM;AAAA,MACV,cAAc;AAAA,MACd;AAAA,MACA;AAAA,MACA,SAAS;AAAA,MACT;AAAA,MACA;AAAA,MACA,eAAe;AAAA,MACf;AAAA,IACF;AAAA,IACA;AAAA,EACF;AACF;AAEA,SAAS,iBACP,MACA,OACA,SACQ;AACR,MAAI,MAAM,gBAAiB,QAAO,MAAM;AACxC,QAAM,MAAM,MAAM,SAAS,eAAe,OAAO;AACjD,MAAI,CAAC,KAAM,QAAO,gBAAW,GAAG,IAAI,MAAM,OAAO;AACjD,MAAI,YAAY,YAAa,QAAO,gBAAW,GAAG,IAAI,MAAM,OAAO;AACnE,SAAO,GAAG,KAAK,WAAW,WAAM,GAAG,IAAI,MAAM,OAAO;AACtD;AAEA,eAAe,qBACb,IACA,OACwB;AACxB,QAAM,MAAM,MAAM,GAAG,QAAQ,uBAAuB;AAAA,IAClD,UAAU,MAAM;AAAA,IAChB,eAAe,MAAM;AAAA,EACvB,CAAC;AACD,SAAO,KAAK,uBAAuB;AACrC;AAEA,eAAe,mBACb,IACA,OACiB;AACjB,QAAM,MAAM,MAAM,GAAG,QAAQ,uBAAuB;AAAA,IAClD,UAAU,MAAM;AAAA,IAChB,eAAe,MAAM;AAAA,EACvB,CAAC;AACD,SAAO,KAAK,WAAW;AACzB;",
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport {\n BillingAccount,\n BillingAccountBalance,\n BillingAccountTransaction,\n BillingAccountUsage,\n BillingItem,\n} from '../data/entities'\nimport type { BalanceStatus, CreditStatus } from '../data/validators'\nimport {\n applyBalanceMovement,\n PREPAID_USAGE_SENTINEL_RUN_ID,\n} from './balanceLedger'\nimport {\n computeBalanceStatus,\n resolveLowBalanceThreshold,\n} from './balanceStatus'\nimport {\n computeCreditStatus,\n computeAvailableCredit,\n prepaidCreditUsed,\n} from './creditStatus'\nimport { buildBalanceCrossingEvents, type DomainEvent } from './balanceEvents'\nimport { absMoney, fromUnits, negateMoney } from './money'\nimport {\n getLowBalanceThresholdDefault,\n getNearLimitBufferDefault,\n getPrepaidTaxPoint,\n} from './prepaidConfig'\nimport { rateUsageForItem, type TierBreakdownEntry } from './usageRater'\nimport { priceBasisOf } from './lineAmounts'\nimport { resolvePrepaidDebit } from './prepaidTaxPoint'\nimport { isTieredRate } from '../data/validators'\n\n/**\n * Real-time prepaid consume (SPEC-002 P1).\n *\n * For a prepaid account, a usage upload is rated in real time, debited from\n * the balance, and the remaining balance is returned \u2014 *\"I uploaded usage and\n * got my remaining balance back.\"* Usage is NEVER rejected: a missing price or\n * an exhausted balance still records the usage; we report status and let the\n * client throttle itself.\n *\n * The caller MUST run this inside an `em.transactional(...)` so the usage\n * insert, the atomic balance debit, and the transaction insert commit (or roll\n * back) together. Domain events are returned (not emitted) so the caller can\n * fire them AFTER the DB commit.\n */\n\nexport type PrepaidConsumeInput = {\n organizationId: string\n tenantId: string\n billAccountId: string\n uomCode: string\n quantity: number\n periodStart: Date\n periodEnd: Date\n lineDescription?: string | null\n sourceRef?: string | null\n}\n\nexport type PrepaidConsumeResult = {\n id: string\n deduplicated: boolean\n ratedAmount: string\n currencyCode: string\n balance: string\n balanceStatus: BalanceStatus\n creditStatus: CreditStatus\n tierBreakdown: TierBreakdownEntry[]\n warning: string | null\n}\n\nexport type PrepaidConsumeOutcome = {\n result: PrepaidConsumeResult\n events: DomainEvent[]\n}\n\nfunction breakdownFromMetadata(meta: Record<string, unknown> | null | undefined): TierBreakdownEntry[] {\n const b = meta?.usage_tier_breakdown\n return Array.isArray(b) ? (b as TierBreakdownEntry[]) : []\n}\n\n/**\n * Resolve the active `type=usage` Billing Item priced for a uom (exact match,\n * no conversion \u2014 same rule as the postpaid runner). Latest active item wins.\n */\nasync function findUsageItem(\n em: EntityManager,\n input: PrepaidConsumeInput,\n): Promise<BillingItem | null> {\n return em.findOne(\n BillingItem,\n {\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n billAccountId: input.billAccountId,\n type: 'usage',\n uomCode: input.uomCode,\n isActive: true,\n deletedAt: null,\n } as never,\n { orderBy: { createdAt: 'DESC' } },\n )\n}\n\nexport async function consumePrepaidUsage(\n em: EntityManager,\n account: BillingAccount,\n input: PrepaidConsumeInput,\n): Promise<PrepaidConsumeOutcome> {\n const currencyCode = account.currencyCode\n const threshold = resolveLowBalanceThreshold(\n await readAccountThreshold(em, input),\n await getLowBalanceThresholdDefault(em, { tenantId: account.tenantId, organizationId: account.organizationId }),\n )\n const nearLimitBuffer = await getNearLimitBufferDefault(em, { tenantId: account.tenantId, organizationId: account.organizationId })\n const creditLimit = account.creditLimit ?? '0'\n\n // \u2500\u2500\u2500 Idempotency: a duplicate sourceRef never double-debits \u2500\u2500\u2500\u2500\n if (input.sourceRef) {\n const existing = await em.findOne(BillingAccountUsage, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n sourceRef: input.sourceRef,\n deletedAt: null,\n })\n if (existing) {\n const txn = await em.findOne(BillingAccountTransaction, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n usageId: existing.id,\n deletedAt: null,\n })\n const balance = txn?.balanceAfter ?? (await readCurrentBalance(em, input))\n const ratedAmount = txn ? absMoney(txn.amount) : '0.0000'\n const warning = (txn?.metadata?.warning as string | undefined) ?? null\n return {\n result: {\n id: existing.id,\n deduplicated: true,\n ratedAmount,\n currencyCode,\n balance,\n balanceStatus: computeBalanceStatus(balance, threshold),\n creditStatus: computeCreditStatus(\n computeAvailableCredit(creditLimit, prepaidCreditUsed(balance)),\n nearLimitBuffer,\n prepaidCreditUsed(balance),\n ),\n tierBreakdown: breakdownFromMetadata(txn?.metadata),\n warning,\n },\n events: [],\n }\n }\n }\n\n // \u2500\u2500\u2500 3. Persist the usage record (rated at ingest = sentinel) \u2500\u2500\n const now = new Date()\n const usage = em.create(BillingAccountUsage, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n uomCode: input.uomCode,\n quantity: input.quantity.toFixed(4),\n periodStart: input.periodStart,\n periodEnd: input.periodEnd,\n lineDescription: input.lineDescription ?? null,\n sourceRef: input.sourceRef ?? null,\n // Stamped immediately so a postpaid-style run never re-rates it.\n ratedInBillRunId: PREPAID_USAGE_SENTINEL_RUN_ID,\n isActive: true,\n createdAt: now,\n updatedAt: now,\n })\n em.persist(usage)\n await em.flush()\n\n // \u2500\u2500\u2500 4. Rate against the matching usage item \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const item = await findUsageItem(em, input)\n let ratedAmount = '0.0000'\n let breakdown: TierBreakdownEntry[] = []\n let warning: string | null = null\n if (!item) {\n warning = 'no_matching_item'\n } else {\n const rated = rateUsageForItem(item, input.quantity)\n if (!rated) {\n warning = 'unratable'\n } else {\n ratedAmount = rated.amount\n breakdown = rated.breakdown\n }\n }\n\n // \u2500\u2500\u2500 4b. Spec F (F-D3): the debit through the org's tax point \u2500\u2500\n // `topup` \u2192 the rated amount as is; `consumption` \u2192 its gross at the item's\n // rate (tiered rates are always net, a simple rate keeps the item basis).\n const taxPoint = await getPrepaidTaxPoint(em, { tenantId: input.tenantId, organizationId: input.organizationId })\n const debit = resolvePrepaidDebit({\n lineAmount: ratedAmount,\n priceBasis: item && !isTieredRate(item.rateJson) ? priceBasisOf(item.priceBasis) : 'net',\n taxRate: item?.taxRate ?? '0',\n taxPoint,\n })\n const debitAmount = fromUnits(Math.round(debit.debit * 10000))\n\n // \u2500\u2500\u2500 5\u20137. Atomic debit + paired transaction (signed amount) \u2500\u2500\u2500\u2500\n const movement = await applyBalanceMovement(em, {\n organizationId: input.organizationId,\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n currencyCode,\n type: 'usage',\n amount: negateMoney(debitAmount),\n description: buildDescription(item, input, warning),\n usageId: usage.id,\n billingItemId: item?.id ?? null,\n sourceRef: input.sourceRef ?? null,\n metadata: {\n uom_code: input.uomCode,\n usage_tier_breakdown: breakdown,\n rated_amount: ratedAmount,\n net: fromUnits(Math.round(debit.net * 10000)),\n tax: fromUnits(Math.round(debit.tax * 10000)),\n tax_rate: String(debit.taxRate),\n tax_point: taxPoint,\n ...(warning ? { warning } : {}),\n },\n })\n\n const { balanceBefore, balanceAfter } = movement\n\n // \u2500\u2500\u2500 8. Status + crossing events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n const balanceStatus = computeBalanceStatus(balanceAfter, threshold)\n const scope = {\n billAccountId: input.billAccountId,\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n currencyCode,\n }\n const events: DomainEvent[] = buildBalanceCrossingEvents({\n scope,\n balanceBefore,\n balanceAfter,\n threshold,\n creditLimit,\n nearLimitBuffer,\n })\n\n // Optional per-debit signal (excluded from workflow triggers).\n events.push({\n id: 'billing.usage.rated',\n payload: {\n ...scope,\n balance: balanceAfter,\n usageId: usage.id,\n transactionId: movement.transaction.id,\n ratedAmount: debitAmount,\n balanceStatus,\n },\n })\n\n const usedAfter = prepaidCreditUsed(balanceAfter)\n const creditStatus = computeCreditStatus(\n computeAvailableCredit(creditLimit, usedAfter),\n nearLimitBuffer,\n usedAfter,\n )\n\n return {\n result: {\n id: usage.id,\n deduplicated: false,\n // What left the balance (equals the rating in `topup` mode) \u2014 the same\n // value the dedup path reads back from the transaction.\n ratedAmount: debitAmount,\n currencyCode,\n balance: balanceAfter,\n balanceStatus,\n creditStatus,\n tierBreakdown: breakdown,\n warning,\n },\n events,\n }\n}\n\nfunction buildDescription(\n item: BillingItem | null,\n input: PrepaidConsumeInput,\n warning: string | null,\n): string {\n if (input.lineDescription) return input.lineDescription\n const qty = input.quantity.toLocaleString('en-US')\n if (!item) return `Usage \u2014 ${qty} ${input.uomCode} (no matching item)`\n if (warning === 'unratable') return `Usage \u2014 ${qty} ${input.uomCode} (unratable)`\n return `${item.description} \u2014 ${qty} ${input.uomCode}`\n}\n\nasync function readAccountThreshold(\n em: EntityManager,\n input: PrepaidConsumeInput,\n): Promise<string | null> {\n const row = await em.findOne(BillingAccountBalance, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n })\n return row?.lowBalanceThreshold ?? null\n}\n\nasync function readCurrentBalance(\n em: EntityManager,\n input: PrepaidConsumeInput,\n): Promise<string> {\n const row = await em.findOne(BillingAccountBalance, {\n tenantId: input.tenantId,\n billAccountId: input.billAccountId,\n })\n return row?.balance ?? '0.0000'\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["ratedAmount", "warning"]
7
7
  }