@dainamite/billing 0.20.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (604) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/modules/billing/acl.js +13 -1
  3. package/dist/modules/billing/acl.js.map +2 -2
  4. package/dist/modules/billing/api/accounts/[id]/summary/route.js +52 -0
  5. package/dist/modules/billing/api/accounts/[id]/summary/route.js.map +7 -0
  6. package/dist/modules/billing/api/accounts/route.js +47 -4
  7. package/dist/modules/billing/api/accounts/route.js.map +2 -2
  8. package/dist/modules/billing/api/export/account/[id]/route.js +11 -7
  9. package/dist/modules/billing/api/export/account/[id]/route.js.map +2 -2
  10. package/dist/modules/billing/api/invoices/[id]/memos/route.js +54 -0
  11. package/dist/modules/billing/api/invoices/[id]/memos/route.js.map +7 -0
  12. package/dist/modules/billing/api/invoices/[id]/pdf/route.js +70 -0
  13. package/dist/modules/billing/api/invoices/[id]/pdf/route.js.map +7 -0
  14. package/dist/modules/billing/api/invoices/[id]/route.js +44 -24
  15. package/dist/modules/billing/api/invoices/[id]/route.js.map +2 -2
  16. package/dist/modules/billing/api/invoices/[id]/send/route.js +107 -0
  17. package/dist/modules/billing/api/invoices/[id]/send/route.js.map +7 -0
  18. package/dist/modules/billing/api/invoices/add-line/route.js +2 -2
  19. package/dist/modules/billing/api/invoices/add-line/route.js.map +2 -2
  20. package/dist/modules/billing/api/invoices/delete-draft/route.js +33 -0
  21. package/dist/modules/billing/api/invoices/delete-draft/route.js.map +7 -0
  22. package/dist/modules/billing/api/invoices/edit-line/route.js +2 -2
  23. package/dist/modules/billing/api/invoices/edit-line/route.js.map +2 -2
  24. package/dist/modules/billing/api/invoices/post/route.js +2 -2
  25. package/dist/modules/billing/api/invoices/post/route.js.map +2 -2
  26. package/dist/modules/billing/api/invoices/remove-line/route.js +2 -2
  27. package/dist/modules/billing/api/invoices/remove-line/route.js.map +2 -2
  28. package/dist/modules/billing/api/invoices/route.js +45 -46
  29. package/dist/modules/billing/api/invoices/route.js.map +2 -2
  30. package/dist/modules/billing/api/items/bulk/route.js +1 -1
  31. package/dist/modules/billing/api/items/bulk/route.js.map +2 -2
  32. package/dist/modules/billing/api/items/change-price/route.js +44 -0
  33. package/dist/modules/billing/api/items/change-price/route.js.map +7 -0
  34. package/dist/modules/billing/api/items/route.js +19 -7
  35. package/dist/modules/billing/api/items/route.js.map +2 -2
  36. package/dist/modules/billing/api/payment-imports/[id]/route.js +49 -0
  37. package/dist/modules/billing/api/payment-imports/[id]/route.js.map +7 -0
  38. package/dist/modules/billing/api/payment-imports/route.js +57 -0
  39. package/dist/modules/billing/api/payment-imports/route.js.map +7 -0
  40. package/dist/modules/billing/api/payments/[id]/route.js +60 -0
  41. package/dist/modules/billing/api/payments/[id]/route.js.map +7 -0
  42. package/dist/modules/billing/api/payments/allocate/route.js +35 -0
  43. package/dist/modules/billing/api/payments/allocate/route.js.map +7 -0
  44. package/dist/modules/billing/api/payments/assign/route.js +36 -0
  45. package/dist/modules/billing/api/payments/assign/route.js.map +7 -0
  46. package/dist/modules/billing/api/payments/convert-to-topup/route.js +35 -0
  47. package/dist/modules/billing/api/payments/convert-to-topup/route.js.map +7 -0
  48. package/dist/modules/billing/api/payments/ignore/route.js +35 -0
  49. package/dist/modules/billing/api/payments/ignore/route.js.map +7 -0
  50. package/dist/modules/billing/api/payments/import/route.js +63 -0
  51. package/dist/modules/billing/api/payments/import/route.js.map +7 -0
  52. package/dist/modules/billing/api/payments/restore/route.js +35 -0
  53. package/dist/modules/billing/api/payments/restore/route.js.map +7 -0
  54. package/dist/modules/billing/api/payments/route.js +114 -0
  55. package/dist/modules/billing/api/payments/route.js.map +7 -0
  56. package/dist/modules/billing/api/payments/unallocate/route.js +35 -0
  57. package/dist/modules/billing/api/payments/unallocate/route.js.map +7 -0
  58. package/dist/modules/billing/api/payments/unassign/route.js +35 -0
  59. package/dist/modules/billing/api/payments/unassign/route.js.map +7 -0
  60. package/dist/modules/billing/api/run-outcomes/route.js +43 -6
  61. package/dist/modules/billing/api/run-outcomes/route.js.map +2 -2
  62. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js +61 -0
  63. package/dist/modules/billing/api/runs/[id]/post-drafts/route.js.map +7 -0
  64. package/dist/modules/billing/api/runs/[id]/route.js +53 -0
  65. package/dist/modules/billing/api/runs/[id]/route.js.map +7 -0
  66. package/dist/modules/billing/api/runs/retry-failed/route.js +1 -1
  67. package/dist/modules/billing/api/runs/retry-failed/route.js.map +2 -2
  68. package/dist/modules/billing/api/runs/route.js +9 -3
  69. package/dist/modules/billing/api/runs/route.js.map +2 -2
  70. package/dist/modules/billing/api/settings/number-preview/route.js +42 -0
  71. package/dist/modules/billing/api/settings/number-preview/route.js.map +7 -0
  72. package/dist/modules/billing/api/settings/route.js +92 -0
  73. package/dist/modules/billing/api/settings/route.js.map +7 -0
  74. package/dist/modules/billing/api/statements/route.js +3 -0
  75. package/dist/modules/billing/api/statements/route.js.map +2 -2
  76. package/dist/modules/billing/api/test-invoices/wipe/route.js +2 -2
  77. package/dist/modules/billing/api/test-invoices/wipe/route.js.map +2 -2
  78. package/dist/modules/billing/backend/billing/accounts/[id]/page.js +121 -232
  79. package/dist/modules/billing/backend/billing/accounts/[id]/page.js.map +2 -2
  80. package/dist/modules/billing/backend/billing/accounts/create/page.js +6 -0
  81. package/dist/modules/billing/backend/billing/accounts/create/page.js.map +2 -2
  82. package/dist/modules/billing/backend/billing/accounts/page.js +63 -4
  83. package/dist/modules/billing/backend/billing/accounts/page.js.map +2 -2
  84. package/dist/modules/billing/backend/billing/invoices/[id]/page.js +297 -65
  85. package/dist/modules/billing/backend/billing/invoices/[id]/page.js.map +3 -3
  86. package/dist/modules/billing/backend/billing/invoices/page.js +6 -172
  87. package/dist/modules/billing/backend/billing/invoices/page.js.map +2 -2
  88. package/dist/modules/billing/backend/billing/items/[id]/page.js +51 -7
  89. package/dist/modules/billing/backend/billing/items/[id]/page.js.map +2 -2
  90. package/dist/modules/billing/backend/billing/items/create/page.js +9 -6
  91. package/dist/modules/billing/backend/billing/items/create/page.js.map +2 -2
  92. package/dist/modules/billing/backend/billing/items/page.js +8 -2
  93. package/dist/modules/billing/backend/billing/items/page.js.map +2 -2
  94. package/dist/modules/billing/backend/billing/payments/[id]/page.js +297 -0
  95. package/dist/modules/billing/backend/billing/payments/[id]/page.js.map +7 -0
  96. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js +21 -0
  97. package/dist/modules/billing/backend/billing/payments/[id]/page.meta.js.map +7 -0
  98. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js +74 -0
  99. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.js.map +7 -0
  100. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js +21 -0
  101. package/dist/modules/billing/backend/billing/payments/imports/[id]/page.meta.js.map +7 -0
  102. package/dist/modules/billing/backend/billing/payments/page.js +58 -0
  103. package/dist/modules/billing/backend/billing/payments/page.js.map +7 -0
  104. package/dist/modules/billing/backend/billing/payments/page.meta.js +18 -0
  105. package/dist/modules/billing/backend/billing/payments/page.meta.js.map +7 -0
  106. package/dist/modules/billing/backend/billing/runs/[id]/page.js +49 -14
  107. package/dist/modules/billing/backend/billing/runs/[id]/page.js.map +2 -2
  108. package/dist/modules/billing/backend/billing/runs/page.js +58 -7
  109. package/dist/modules/billing/backend/billing/runs/page.js.map +2 -2
  110. package/dist/modules/billing/backend/billing/statements/[id]/page.js +61 -6
  111. package/dist/modules/billing/backend/billing/statements/[id]/page.js.map +2 -2
  112. package/dist/modules/billing/backend/billing/statements/page.js +1 -1
  113. package/dist/modules/billing/backend/billing/statements/page.js.map +2 -2
  114. package/dist/modules/billing/backend/billing/topups/page.js +1 -1
  115. package/dist/modules/billing/backend/billing/topups/page.js.map +2 -2
  116. package/dist/modules/billing/backend/billing/transactions/page.js +1 -1
  117. package/dist/modules/billing/backend/billing/transactions/page.js.map +2 -2
  118. package/dist/modules/billing/backend/billing/usage/create/page.js +3 -2
  119. package/dist/modules/billing/backend/billing/usage/create/page.js.map +2 -2
  120. package/dist/modules/billing/backend/config/billing/page.js +472 -0
  121. package/dist/modules/billing/backend/config/billing/page.js.map +7 -0
  122. package/dist/modules/billing/backend/config/billing/page.meta.js +19 -0
  123. package/dist/modules/billing/backend/config/billing/page.meta.js.map +7 -0
  124. package/dist/modules/billing/cli.js +22 -1
  125. package/dist/modules/billing/cli.js.map +2 -2
  126. package/dist/modules/billing/commands/accounts.js +6 -0
  127. package/dist/modules/billing/commands/accounts.js.map +2 -2
  128. package/dist/modules/billing/commands/index.js +3 -0
  129. package/dist/modules/billing/commands/index.js.map +2 -2
  130. package/dist/modules/billing/commands/invoices.js +269 -167
  131. package/dist/modules/billing/commands/invoices.js.map +2 -2
  132. package/dist/modules/billing/commands/items.js +108 -0
  133. package/dist/modules/billing/commands/items.js.map +2 -2
  134. package/dist/modules/billing/commands/memos.js +179 -0
  135. package/dist/modules/billing/commands/memos.js.map +7 -0
  136. package/dist/modules/billing/commands/payments.js +379 -0
  137. package/dist/modules/billing/commands/payments.js.map +7 -0
  138. package/dist/modules/billing/commands/runs.js +151 -4
  139. package/dist/modules/billing/commands/runs.js.map +2 -2
  140. package/dist/modules/billing/commands/settings.js +59 -0
  141. package/dist/modules/billing/commands/settings.js.map +7 -0
  142. package/dist/modules/billing/commands/topups.js +9 -2
  143. package/dist/modules/billing/commands/topups.js.map +2 -2
  144. package/dist/modules/billing/commands/transactions.js +2 -2
  145. package/dist/modules/billing/commands/transactions.js.map +2 -2
  146. package/dist/modules/billing/components/AccountForm.js +149 -42
  147. package/dist/modules/billing/components/AccountForm.js.map +2 -2
  148. package/dist/modules/billing/components/AccountInvoicesSection.js +64 -0
  149. package/dist/modules/billing/components/AccountInvoicesSection.js.map +7 -0
  150. package/dist/modules/billing/components/AccountPaymentsSection.js +43 -0
  151. package/dist/modules/billing/components/AccountPaymentsSection.js.map +7 -0
  152. package/dist/modules/billing/components/AccountPicker.js +56 -0
  153. package/dist/modules/billing/components/AccountPicker.js.map +7 -0
  154. package/dist/modules/billing/components/AllocatePaymentDialog.js +134 -0
  155. package/dist/modules/billing/components/AllocatePaymentDialog.js.map +7 -0
  156. package/dist/modules/billing/components/BillingDataTable.js +21 -0
  157. package/dist/modules/billing/components/BillingDataTable.js.map +7 -0
  158. package/dist/modules/billing/components/BillingSettingsLink.js +19 -0
  159. package/dist/modules/billing/components/BillingSettingsLink.js.map +7 -0
  160. package/dist/modules/billing/components/ChangeItemPriceDialog.js +158 -0
  161. package/dist/modules/billing/components/ChangeItemPriceDialog.js.map +7 -0
  162. package/dist/modules/billing/components/CreateMemoDialog.js +200 -0
  163. package/dist/modules/billing/components/CreateMemoDialog.js.map +7 -0
  164. package/dist/modules/billing/components/CustomerLabel.js +33 -0
  165. package/dist/modules/billing/components/CustomerLabel.js.map +7 -0
  166. package/dist/modules/billing/components/InvoicesTable.js +270 -0
  167. package/dist/modules/billing/components/InvoicesTable.js.map +7 -0
  168. package/dist/modules/billing/components/ItemForm.js +176 -44
  169. package/dist/modules/billing/components/ItemForm.js.map +2 -2
  170. package/dist/modules/billing/components/ItemPriceField.js +46 -0
  171. package/dist/modules/billing/components/ItemPriceField.js.map +7 -0
  172. package/dist/modules/billing/components/LineFormDialog.js +25 -2
  173. package/dist/modules/billing/components/LineFormDialog.js.map +2 -2
  174. package/dist/modules/billing/components/ManualPaymentDialog.js +122 -0
  175. package/dist/modules/billing/components/ManualPaymentDialog.js.map +7 -0
  176. package/dist/modules/billing/components/MoneyInput.js +14 -0
  177. package/dist/modules/billing/components/MoneyInput.js.map +7 -0
  178. package/dist/modules/billing/components/PaymentUploadDialog.js +115 -0
  179. package/dist/modules/billing/components/PaymentUploadDialog.js.map +7 -0
  180. package/dist/modules/billing/components/PaymentsTable.js +171 -0
  181. package/dist/modules/billing/components/PaymentsTable.js.map +7 -0
  182. package/dist/modules/billing/components/PostRunDraftsButton.js +105 -0
  183. package/dist/modules/billing/components/PostRunDraftsButton.js.map +7 -0
  184. package/dist/modules/billing/components/PrepaidPanel.js +14 -4
  185. package/dist/modules/billing/components/PrepaidPanel.js.map +2 -2
  186. package/dist/modules/billing/components/RunCyclesSection.js +209 -0
  187. package/dist/modules/billing/components/RunCyclesSection.js.map +7 -0
  188. package/dist/modules/billing/components/TestRunActions.js +127 -0
  189. package/dist/modules/billing/components/TestRunActions.js.map +7 -0
  190. package/dist/modules/billing/components/account/AccountItemsTab.js +157 -0
  191. package/dist/modules/billing/components/account/AccountItemsTab.js.map +7 -0
  192. package/dist/modules/billing/components/account/AccountRunBillingDialog.js +122 -0
  193. package/dist/modules/billing/components/account/AccountRunBillingDialog.js.map +7 -0
  194. package/dist/modules/billing/components/account/AccountRunsTab.js +152 -0
  195. package/dist/modules/billing/components/account/AccountRunsTab.js.map +7 -0
  196. package/dist/modules/billing/components/account/AccountStatementsTab.js +98 -0
  197. package/dist/modules/billing/components/account/AccountStatementsTab.js.map +7 -0
  198. package/dist/modules/billing/components/account/AccountSummaryCards.js +125 -0
  199. package/dist/modules/billing/components/account/AccountSummaryCards.js.map +7 -0
  200. package/dist/modules/billing/components/account/AccountUsageTab.js +91 -0
  201. package/dist/modules/billing/components/account/AccountUsageTab.js.map +7 -0
  202. package/dist/modules/billing/components/account/AccountView.js +73 -0
  203. package/dist/modules/billing/components/account/AccountView.js.map +7 -0
  204. package/dist/modules/billing/components/account/accountRow.js +39 -0
  205. package/dist/modules/billing/components/account/accountRow.js.map +7 -0
  206. package/dist/modules/billing/components/customerNames.js +42 -0
  207. package/dist/modules/billing/components/customerNames.js.map +7 -0
  208. package/dist/modules/billing/data/entities.js +560 -4
  209. package/dist/modules/billing/data/entities.js.map +2 -2
  210. package/dist/modules/billing/data/entityIds.js +10 -1
  211. package/dist/modules/billing/data/entityIds.js.map +2 -2
  212. package/dist/modules/billing/data/validators.js +269 -15
  213. package/dist/modules/billing/data/validators.js.map +2 -2
  214. package/dist/modules/billing/events.js +50 -0
  215. package/dist/modules/billing/events.js.map +2 -2
  216. package/dist/modules/billing/i18n/de.json +880 -0
  217. package/dist/modules/billing/i18n/en.json +457 -21
  218. package/dist/modules/billing/i18n/es.json +880 -0
  219. package/dist/modules/billing/i18n/pl.json +458 -22
  220. package/dist/modules/billing/index.js +6 -2
  221. package/dist/modules/billing/index.js.map +2 -2
  222. package/dist/modules/billing/lib/accountFinancials.js +58 -0
  223. package/dist/modules/billing/lib/accountFinancials.js.map +7 -0
  224. package/dist/modules/billing/lib/accountLock.js +14 -0
  225. package/dist/modules/billing/lib/accountLock.js.map +7 -0
  226. package/dist/modules/billing/lib/accountStatus.js +9 -7
  227. package/dist/modules/billing/lib/accountStatus.js.map +2 -2
  228. package/dist/modules/billing/lib/accountSummary.js +106 -0
  229. package/dist/modules/billing/lib/accountSummary.js.map +7 -0
  230. package/dist/modules/billing/lib/allocation.js +356 -0
  231. package/dist/modules/billing/lib/allocation.js.map +7 -0
  232. package/dist/modules/billing/lib/allocationEvents.js +22 -0
  233. package/dist/modules/billing/lib/allocationEvents.js.map +7 -0
  234. package/dist/modules/billing/lib/billPeriod.js +15 -1
  235. package/dist/modules/billing/lib/billPeriod.js.map +2 -2
  236. package/dist/modules/billing/lib/billRunEngine.js +237 -102
  237. package/dist/modules/billing/lib/billRunEngine.js.map +2 -2
  238. package/dist/modules/billing/lib/billingSettings.js +116 -0
  239. package/dist/modules/billing/lib/billingSettings.js.map +7 -0
  240. package/dist/modules/billing/lib/commandRoute.js +81 -0
  241. package/dist/modules/billing/lib/commandRoute.js.map +7 -0
  242. package/dist/modules/billing/lib/customerLookup.js +97 -0
  243. package/dist/modules/billing/lib/customerLookup.js.map +7 -0
  244. package/dist/modules/billing/lib/dateOnly.js +13 -0
  245. package/dist/modules/billing/lib/dateOnly.js.map +7 -0
  246. package/dist/modules/billing/lib/documentParties.js +16 -0
  247. package/dist/modules/billing/lib/documentParties.js.map +7 -0
  248. package/dist/modules/billing/lib/documentTypes.js +27 -0
  249. package/dist/modules/billing/lib/documentTypes.js.map +7 -0
  250. package/dist/modules/billing/lib/dueDate.js +18 -0
  251. package/dist/modules/billing/lib/dueDate.js.map +7 -0
  252. package/dist/modules/billing/lib/iban.js +29 -0
  253. package/dist/modules/billing/lib/iban.js.map +7 -0
  254. package/dist/modules/billing/lib/invoiceDelivery.js +133 -0
  255. package/dist/modules/billing/lib/invoiceDelivery.js.map +7 -0
  256. package/dist/modules/billing/lib/invoiceLanguages.js +28 -0
  257. package/dist/modules/billing/lib/invoiceLanguages.js.map +7 -0
  258. package/dist/modules/billing/lib/invoiceWriter.js +107 -99
  259. package/dist/modules/billing/lib/invoiceWriter.js.map +2 -2
  260. package/dist/modules/billing/lib/itemBillingState.js +27 -0
  261. package/dist/modules/billing/lib/itemBillingState.js.map +7 -0
  262. package/dist/modules/billing/lib/itemPriceChange.js +18 -0
  263. package/dist/modules/billing/lib/itemPriceChange.js.map +7 -0
  264. package/dist/modules/billing/lib/itemSelector.js.map +2 -2
  265. package/dist/modules/billing/lib/lineAmounts.js +32 -0
  266. package/dist/modules/billing/lib/lineAmounts.js.map +7 -0
  267. package/dist/modules/billing/lib/memoGuards.js +25 -0
  268. package/dist/modules/billing/lib/memoGuards.js.map +7 -0
  269. package/dist/modules/billing/lib/navigation.js +48 -0
  270. package/dist/modules/billing/lib/navigation.js.map +7 -0
  271. package/dist/modules/billing/lib/numbering.js +120 -0
  272. package/dist/modules/billing/lib/numbering.js.map +7 -0
  273. package/dist/modules/billing/lib/overdue.js +36 -0
  274. package/dist/modules/billing/lib/overdue.js.map +7 -0
  275. package/dist/modules/billing/lib/paymentCsv.js +125 -0
  276. package/dist/modules/billing/lib/paymentCsv.js.map +7 -0
  277. package/dist/modules/billing/lib/paymentGateway.js +15 -0
  278. package/dist/modules/billing/lib/paymentGateway.js.map +7 -0
  279. package/dist/modules/billing/lib/paymentImport.js +164 -0
  280. package/dist/modules/billing/lib/paymentImport.js.map +7 -0
  281. package/dist/modules/billing/lib/paymentMatcher.js +36 -0
  282. package/dist/modules/billing/lib/paymentMatcher.js.map +7 -0
  283. package/dist/modules/billing/lib/pdf/attachmentStore.js +46 -0
  284. package/dist/modules/billing/lib/pdf/attachmentStore.js.map +7 -0
  285. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js +7 -0
  286. package/dist/modules/billing/lib/pdf/fonts/embeddedFonts.js.map +7 -0
  287. package/dist/modules/billing/lib/pdf/invoiceModel.js +194 -0
  288. package/dist/modules/billing/lib/pdf/invoiceModel.js.map +7 -0
  289. package/dist/modules/billing/lib/pdf/invoicePdf.js +165 -0
  290. package/dist/modules/billing/lib/pdf/invoicePdf.js.map +7 -0
  291. package/dist/modules/billing/lib/pdf/invoiceTemplate.js +276 -0
  292. package/dist/modules/billing/lib/pdf/invoiceTemplate.js.map +7 -0
  293. package/dist/modules/billing/lib/prepaidConfig.js +17 -25
  294. package/dist/modules/billing/lib/prepaidConfig.js.map +2 -2
  295. package/dist/modules/billing/lib/prepaidConsume.js +26 -7
  296. package/dist/modules/billing/lib/prepaidConsume.js.map +2 -2
  297. package/dist/modules/billing/lib/prepaidPeriodClose.js +277 -158
  298. package/dist/modules/billing/lib/prepaidPeriodClose.js.map +2 -2
  299. package/dist/modules/billing/lib/prepaidTaxPoint.js +37 -0
  300. package/dist/modules/billing/lib/prepaidTaxPoint.js.map +7 -0
  301. package/dist/modules/billing/lib/seeds/demo.js +51 -3
  302. package/dist/modules/billing/lib/seeds/demo.js.map +2 -2
  303. package/dist/modules/billing/lib/seeds/paymentsSample.js +93 -0
  304. package/dist/modules/billing/lib/seeds/paymentsSample.js.map +7 -0
  305. package/dist/modules/billing/lib/seeds/sellerSettings.js +41 -0
  306. package/dist/modules/billing/lib/seeds/sellerSettings.js.map +7 -0
  307. package/dist/modules/billing/lib/servicePeriods.js +121 -0
  308. package/dist/modules/billing/lib/servicePeriods.js.map +7 -0
  309. package/dist/modules/billing/lib/testRunCleanup.js +24 -0
  310. package/dist/modules/billing/lib/testRunCleanup.js.map +7 -0
  311. package/dist/modules/billing/lib/topupCapture.js +2 -2
  312. package/dist/modules/billing/lib/topupCapture.js.map +2 -2
  313. package/dist/modules/billing/lib/topupReceipt.js +58 -79
  314. package/dist/modules/billing/lib/topupReceipt.js.map +2 -2
  315. package/dist/modules/billing/lib/usageRater.js.map +1 -1
  316. package/dist/modules/billing/lib/usageRunner.js +14 -6
  317. package/dist/modules/billing/lib/usageRunner.js.map +2 -2
  318. package/dist/modules/billing/lib/vat.js +39 -0
  319. package/dist/modules/billing/lib/vat.js.map +7 -0
  320. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js +15 -0
  321. package/dist/modules/billing/migrations/Migration20261114000000_billing_customer_kind.js.map +7 -0
  322. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js +25 -0
  323. package/dist/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.js.map +7 -0
  324. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js +115 -0
  325. package/dist/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.js.map +7 -0
  326. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js +47 -0
  327. package/dist/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.js.map +7 -0
  328. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js +89 -0
  329. package/dist/modules/billing/migrations/Migration20261117000000_billing_payments.js.map +7 -0
  330. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js +17 -0
  331. package/dist/modules/billing/migrations/Migration20261118000000_billing_memos.js.map +7 -0
  332. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js +27 -0
  333. package/dist/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.js.map +7 -0
  334. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js +29 -0
  335. package/dist/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.js.map +7 -0
  336. package/dist/modules/billing/setup.js +7 -184
  337. package/dist/modules/billing/setup.js.map +2 -2
  338. package/dist/modules/billing/subscribers/credit-memo-auto-send.js +16 -0
  339. package/dist/modules/billing/subscribers/credit-memo-auto-send.js.map +7 -0
  340. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js +16 -0
  341. package/dist/modules/billing/subscribers/debit-memo-apply-credit.js.map +7 -0
  342. package/dist/modules/billing/subscribers/debit-memo-auto-send.js +16 -0
  343. package/dist/modules/billing/subscribers/debit-memo-auto-send.js.map +7 -0
  344. package/dist/modules/billing/subscribers/invoice-apply-credit.js +34 -0
  345. package/dist/modules/billing/subscribers/invoice-apply-credit.js.map +7 -0
  346. package/dist/modules/billing/subscribers/invoice-auto-send.js +30 -0
  347. package/dist/modules/billing/subscribers/invoice-auto-send.js.map +7 -0
  348. package/dist/modules/billing/subscribers/payment-captured-to-paid.js +69 -33
  349. package/dist/modules/billing/subscribers/payment-captured-to-paid.js.map +2 -2
  350. package/dist/modules/billing/subscribers/prepaid-topup-captured.js +4 -6
  351. package/dist/modules/billing/subscribers/prepaid-topup-captured.js.map +2 -2
  352. package/generated/entities/billing_account/index.ts +3 -0
  353. package/generated/entities/billing_allocation/index.ts +18 -0
  354. package/generated/entities/billing_document_sequence/index.ts +11 -0
  355. package/generated/entities/billing_invoice/index.ts +38 -0
  356. package/generated/entities/billing_invoice_line/index.ts +24 -0
  357. package/generated/entities/billing_item/index.ts +4 -0
  358. package/generated/entities/billing_payment/index.ts +25 -0
  359. package/generated/entities/billing_payment_import/index.ts +14 -0
  360. package/generated/entities/billing_run/index.ts +0 -1
  361. package/generated/entities/billing_run_outcome/index.ts +1 -0
  362. package/generated/entities/billing_settings/index.ts +26 -0
  363. package/generated/entities/billing_statement/index.ts +1 -0
  364. package/generated/entities.ids.generated.ts +7 -0
  365. package/package.json +5 -1
  366. package/src/modules/billing/__integration__/TC-BILL-004.spec.ts +171 -0
  367. package/src/modules/billing/__integration__/TC-BILL-005.spec.ts +134 -0
  368. package/src/modules/billing/__integration__/TC-BILL-006.spec.ts +101 -0
  369. package/src/modules/billing/__integration__/TC-BILL-007.spec.ts +140 -0
  370. package/src/modules/billing/__integration__/TC-BILL-008.spec.ts +108 -0
  371. package/src/modules/billing/__integration__/TC-BILL-009.spec.ts +113 -0
  372. package/src/modules/billing/__integration__/TC-BILL-010.spec.ts +118 -0
  373. package/src/modules/billing/__integration__/TC-BILL-011.spec.ts +142 -0
  374. package/src/modules/billing/__integration__/TC-BILL-012.spec.ts +115 -0
  375. package/src/modules/billing/__integration__/TC-BILL-013.spec.ts +118 -0
  376. package/src/modules/billing/__integration__/sellerSettings.ts +24 -0
  377. package/src/modules/billing/__tests__/commands/invoices.test.ts +436 -354
  378. package/src/modules/billing/__tests__/commands/itemsBulk.test.ts +33 -0
  379. package/src/modules/billing/__tests__/commands/memos.test.ts +205 -0
  380. package/src/modules/billing/__tests__/commands/payments.test.ts +241 -0
  381. package/src/modules/billing/__tests__/commands/runsDelete.test.ts +146 -0
  382. package/src/modules/billing/__tests__/commands/runsPostDrafts.test.ts +106 -0
  383. package/src/modules/billing/__tests__/commands/topups.test.ts +11 -0
  384. package/src/modules/billing/__tests__/entityRegistry.test.ts +8 -1
  385. package/src/modules/billing/__tests__/helpers/prepaidMockEm.ts +28 -11
  386. package/src/modules/billing/__tests__/i18n/dictionaries.test.ts +64 -0
  387. package/src/modules/billing/__tests__/lib/accountFinancials.test.ts +53 -0
  388. package/src/modules/billing/__tests__/lib/accountSummary.test.ts +97 -0
  389. package/src/modules/billing/__tests__/lib/allocation.test.ts +319 -0
  390. package/src/modules/billing/__tests__/lib/billRunEngine.test.ts +228 -17
  391. package/src/modules/billing/__tests__/lib/billingSettings.test.ts +109 -0
  392. package/src/modules/billing/__tests__/lib/customerLookup.test.ts +156 -0
  393. package/src/modules/billing/__tests__/lib/dueDate.test.ts +35 -0
  394. package/src/modules/billing/__tests__/lib/iban.test.ts +38 -0
  395. package/src/modules/billing/__tests__/lib/invoiceDelivery.test.ts +206 -0
  396. package/src/modules/billing/__tests__/lib/invoiceLanguages.test.ts +49 -0
  397. package/src/modules/billing/__tests__/lib/invoiceWriter.test.ts +212 -10
  398. package/src/modules/billing/__tests__/lib/itemBillingState.test.ts +42 -0
  399. package/src/modules/billing/__tests__/lib/itemPriceChange.test.ts +165 -0
  400. package/src/modules/billing/__tests__/lib/lineAmounts.test.ts +86 -0
  401. package/src/modules/billing/__tests__/lib/navigation.test.ts +71 -0
  402. package/src/modules/billing/__tests__/lib/numbering.test.ts +91 -0
  403. package/src/modules/billing/__tests__/lib/overdue.test.ts +36 -0
  404. package/src/modules/billing/__tests__/lib/paymentCsv.test.ts +148 -0
  405. package/src/modules/billing/__tests__/lib/paymentImport.test.ts +113 -0
  406. package/src/modules/billing/__tests__/lib/paymentsSample.test.ts +73 -0
  407. package/src/modules/billing/__tests__/lib/pdf/invoicePdf.test.ts +269 -0
  408. package/src/modules/billing/__tests__/lib/pdf/invoicePdfService.test.ts +147 -0
  409. package/src/modules/billing/__tests__/lib/prepaidPeriodClose.test.ts +374 -98
  410. package/src/modules/billing/__tests__/lib/prepaidTaxPoint.test.ts +118 -0
  411. package/src/modules/billing/__tests__/lib/seedsDemo.test.ts +53 -2
  412. package/src/modules/billing/__tests__/lib/sellerSettings.test.ts +28 -0
  413. package/src/modules/billing/__tests__/lib/servicePeriods.test.ts +190 -0
  414. package/src/modules/billing/__tests__/lib/topupCapture.test.ts +6 -28
  415. package/src/modules/billing/__tests__/lib/topupReceipt.test.ts +21 -52
  416. package/src/modules/billing/__tests__/lib/vat.test.ts +51 -0
  417. package/src/modules/billing/__tests__/package-isolation.test.ts +22 -8
  418. package/src/modules/billing/__tests__/setup.test.ts +13 -4
  419. package/src/modules/billing/__tests__/validators.test.ts +197 -3
  420. package/src/modules/billing/acl.ts +12 -0
  421. package/src/modules/billing/api/accounts/[id]/summary/route.ts +62 -0
  422. package/src/modules/billing/api/accounts/route.ts +59 -4
  423. package/src/modules/billing/api/export/account/[id]/route.ts +13 -11
  424. package/src/modules/billing/api/invoices/[id]/memos/route.ts +63 -0
  425. package/src/modules/billing/api/invoices/[id]/pdf/route.ts +80 -0
  426. package/src/modules/billing/api/invoices/[id]/route.ts +57 -70
  427. package/src/modules/billing/api/invoices/[id]/send/route.ts +127 -0
  428. package/src/modules/billing/api/invoices/add-line/route.ts +2 -2
  429. package/src/modules/billing/api/invoices/delete-draft/route.ts +33 -0
  430. package/src/modules/billing/api/invoices/edit-line/route.ts +2 -2
  431. package/src/modules/billing/api/invoices/post/route.ts +7 -6
  432. package/src/modules/billing/api/invoices/remove-line/route.ts +2 -2
  433. package/src/modules/billing/api/invoices/route.ts +81 -86
  434. package/src/modules/billing/api/items/bulk/route.ts +2 -1
  435. package/src/modules/billing/api/items/change-price/route.ts +54 -0
  436. package/src/modules/billing/api/items/route.ts +23 -7
  437. package/src/modules/billing/api/payment-imports/[id]/route.ts +49 -0
  438. package/src/modules/billing/api/payment-imports/route.ts +57 -0
  439. package/src/modules/billing/api/payments/[id]/route.ts +63 -0
  440. package/src/modules/billing/api/payments/allocate/route.ts +35 -0
  441. package/src/modules/billing/api/payments/assign/route.ts +36 -0
  442. package/src/modules/billing/api/payments/convert-to-topup/route.ts +35 -0
  443. package/src/modules/billing/api/payments/ignore/route.ts +35 -0
  444. package/src/modules/billing/api/payments/import/route.ts +72 -0
  445. package/src/modules/billing/api/payments/restore/route.ts +35 -0
  446. package/src/modules/billing/api/payments/route.ts +147 -0
  447. package/src/modules/billing/api/payments/unallocate/route.ts +35 -0
  448. package/src/modules/billing/api/payments/unassign/route.ts +35 -0
  449. package/src/modules/billing/api/run-outcomes/route.ts +49 -6
  450. package/src/modules/billing/api/runs/[id]/post-drafts/route.ts +71 -0
  451. package/src/modules/billing/api/runs/[id]/route.ts +59 -0
  452. package/src/modules/billing/api/runs/retry-failed/route.ts +2 -1
  453. package/src/modules/billing/api/runs/route.ts +15 -7
  454. package/src/modules/billing/api/settings/number-preview/route.ts +46 -0
  455. package/src/modules/billing/api/settings/route.ts +116 -0
  456. package/src/modules/billing/api/statements/route.ts +5 -0
  457. package/src/modules/billing/api/test-invoices/wipe/route.ts +2 -2
  458. package/src/modules/billing/backend/billing/accounts/[id]/page.tsx +151 -372
  459. package/src/modules/billing/backend/billing/accounts/create/page.tsx +6 -0
  460. package/src/modules/billing/backend/billing/accounts/page.tsx +70 -4
  461. package/src/modules/billing/backend/billing/invoices/[id]/page.tsx +444 -95
  462. package/src/modules/billing/backend/billing/invoices/page.tsx +8 -227
  463. package/src/modules/billing/backend/billing/items/[id]/page.tsx +75 -6
  464. package/src/modules/billing/backend/billing/items/create/page.tsx +13 -8
  465. package/src/modules/billing/backend/billing/items/page.tsx +18 -2
  466. package/src/modules/billing/backend/billing/payments/[id]/page.meta.ts +19 -0
  467. package/src/modules/billing/backend/billing/payments/[id]/page.tsx +415 -0
  468. package/src/modules/billing/backend/billing/payments/imports/[id]/page.meta.ts +19 -0
  469. package/src/modules/billing/backend/billing/payments/imports/[id]/page.tsx +106 -0
  470. package/src/modules/billing/backend/billing/payments/page.meta.ts +16 -0
  471. package/src/modules/billing/backend/billing/payments/page.tsx +56 -0
  472. package/src/modules/billing/backend/billing/runs/[id]/page.tsx +49 -18
  473. package/src/modules/billing/backend/billing/runs/page.tsx +59 -8
  474. package/src/modules/billing/backend/billing/statements/[id]/page.tsx +86 -5
  475. package/src/modules/billing/backend/billing/statements/page.tsx +1 -1
  476. package/src/modules/billing/backend/billing/topups/page.tsx +1 -1
  477. package/src/modules/billing/backend/billing/transactions/page.tsx +1 -1
  478. package/src/modules/billing/backend/billing/usage/create/page.tsx +3 -2
  479. package/src/modules/billing/backend/config/billing/page.meta.ts +21 -0
  480. package/src/modules/billing/backend/config/billing/page.tsx +549 -0
  481. package/src/modules/billing/cli.ts +31 -1
  482. package/src/modules/billing/commands/accounts.ts +6 -0
  483. package/src/modules/billing/commands/index.ts +3 -0
  484. package/src/modules/billing/commands/invoices.ts +364 -231
  485. package/src/modules/billing/commands/items.ts +143 -0
  486. package/src/modules/billing/commands/memos.ts +218 -0
  487. package/src/modules/billing/commands/payments.ts +428 -0
  488. package/src/modules/billing/commands/runs.ts +205 -3
  489. package/src/modules/billing/commands/settings.ts +77 -0
  490. package/src/modules/billing/commands/topups.ts +12 -3
  491. package/src/modules/billing/commands/transactions.ts +2 -2
  492. package/src/modules/billing/components/AccountForm.tsx +199 -53
  493. package/src/modules/billing/components/AccountInvoicesSection.tsx +75 -0
  494. package/src/modules/billing/components/AccountPaymentsSection.tsx +42 -0
  495. package/src/modules/billing/components/AccountPicker.tsx +70 -0
  496. package/src/modules/billing/components/AllocatePaymentDialog.tsx +167 -0
  497. package/src/modules/billing/components/BillingDataTable.tsx +34 -0
  498. package/src/modules/billing/components/BillingSettingsLink.tsx +25 -0
  499. package/src/modules/billing/components/ChangeItemPriceDialog.tsx +180 -0
  500. package/src/modules/billing/components/CreateMemoDialog.tsx +232 -0
  501. package/src/modules/billing/components/CustomerLabel.tsx +53 -0
  502. package/src/modules/billing/components/InvoicesTable.tsx +344 -0
  503. package/src/modules/billing/components/ItemForm.tsx +233 -44
  504. package/src/modules/billing/components/ItemPriceField.tsx +81 -0
  505. package/src/modules/billing/components/LineFormDialog.tsx +25 -1
  506. package/src/modules/billing/components/ManualPaymentDialog.tsx +140 -0
  507. package/src/modules/billing/components/MoneyInput.tsx +26 -0
  508. package/src/modules/billing/components/PaymentUploadDialog.tsx +148 -0
  509. package/src/modules/billing/components/PaymentsTable.tsx +213 -0
  510. package/src/modules/billing/components/PostRunDraftsButton.tsx +129 -0
  511. package/src/modules/billing/components/PrepaidPanel.tsx +19 -6
  512. package/src/modules/billing/components/RunCyclesSection.tsx +298 -0
  513. package/src/modules/billing/components/TestRunActions.tsx +152 -0
  514. package/src/modules/billing/components/account/AccountItemsTab.tsx +192 -0
  515. package/src/modules/billing/components/account/AccountRunBillingDialog.tsx +147 -0
  516. package/src/modules/billing/components/account/AccountRunsTab.tsx +201 -0
  517. package/src/modules/billing/components/account/AccountStatementsTab.tsx +120 -0
  518. package/src/modules/billing/components/account/AccountSummaryCards.tsx +153 -0
  519. package/src/modules/billing/components/account/AccountUsageTab.tsx +122 -0
  520. package/src/modules/billing/components/account/AccountView.tsx +101 -0
  521. package/src/modules/billing/components/account/accountRow.ts +70 -0
  522. package/src/modules/billing/components/customerNames.ts +62 -0
  523. package/src/modules/billing/data/entities.ts +656 -6
  524. package/src/modules/billing/data/entityIds.ts +9 -0
  525. package/src/modules/billing/data/validators.ts +388 -8
  526. package/src/modules/billing/events.ts +56 -3
  527. package/src/modules/billing/i18n/de.json +880 -0
  528. package/src/modules/billing/i18n/en.json +457 -21
  529. package/src/modules/billing/i18n/es.json +880 -0
  530. package/src/modules/billing/i18n/pl.json +458 -22
  531. package/src/modules/billing/index.ts +6 -2
  532. package/src/modules/billing/lib/accountFinancials.ts +84 -0
  533. package/src/modules/billing/lib/accountLock.ts +32 -0
  534. package/src/modules/billing/lib/accountStatus.ts +11 -9
  535. package/src/modules/billing/lib/accountSummary.ts +172 -0
  536. package/src/modules/billing/lib/allocation.ts +515 -0
  537. package/src/modules/billing/lib/allocationEvents.ts +26 -0
  538. package/src/modules/billing/lib/billPeriod.ts +19 -0
  539. package/src/modules/billing/lib/billRunEngine.ts +350 -150
  540. package/src/modules/billing/lib/billingSettings.ts +145 -0
  541. package/src/modules/billing/lib/commandRoute.ts +112 -0
  542. package/src/modules/billing/lib/customerLookup.ts +157 -0
  543. package/src/modules/billing/lib/dateOnly.ts +22 -0
  544. package/src/modules/billing/lib/documentParties.ts +23 -0
  545. package/src/modules/billing/lib/documentTypes.ts +37 -0
  546. package/src/modules/billing/lib/dueDate.ts +26 -0
  547. package/src/modules/billing/lib/iban.ts +36 -0
  548. package/src/modules/billing/lib/invoiceDelivery.ts +196 -0
  549. package/src/modules/billing/lib/invoiceLanguages.ts +57 -0
  550. package/src/modules/billing/lib/invoiceWriter.ts +162 -148
  551. package/src/modules/billing/lib/itemBillingState.ts +67 -0
  552. package/src/modules/billing/lib/itemPriceChange.ts +56 -0
  553. package/src/modules/billing/lib/itemSelector.ts +4 -1
  554. package/src/modules/billing/lib/lineAmounts.ts +61 -0
  555. package/src/modules/billing/lib/memoGuards.ts +41 -0
  556. package/src/modules/billing/lib/navigation.ts +64 -0
  557. package/src/modules/billing/lib/numbering.ts +174 -0
  558. package/src/modules/billing/lib/overdue.ts +60 -0
  559. package/src/modules/billing/lib/paymentCsv.ts +171 -0
  560. package/src/modules/billing/lib/paymentGateway.ts +52 -0
  561. package/src/modules/billing/lib/paymentImport.ts +228 -0
  562. package/src/modules/billing/lib/paymentMatcher.ts +88 -0
  563. package/src/modules/billing/lib/pdf/attachmentStore.ts +60 -0
  564. package/src/modules/billing/lib/pdf/fonts/NotoSans-Bold.ttf +0 -0
  565. package/src/modules/billing/lib/pdf/fonts/NotoSans-Regular.ttf +0 -0
  566. package/src/modules/billing/lib/pdf/fonts/OFL.txt +93 -0
  567. package/src/modules/billing/lib/pdf/fonts/embeddedFonts.ts +9 -0
  568. package/src/modules/billing/lib/pdf/invoiceModel.ts +329 -0
  569. package/src/modules/billing/lib/pdf/invoicePdf.ts +220 -0
  570. package/src/modules/billing/lib/pdf/invoiceTemplate.ts +338 -0
  571. package/src/modules/billing/lib/prepaidConfig.ts +27 -36
  572. package/src/modules/billing/lib/prepaidConsume.ts +29 -6
  573. package/src/modules/billing/lib/prepaidPeriodClose.ts +407 -239
  574. package/src/modules/billing/lib/prepaidTaxPoint.ts +76 -0
  575. package/src/modules/billing/lib/seeds/demo.ts +79 -6
  576. package/src/modules/billing/lib/seeds/paymentsSample.ts +136 -0
  577. package/src/modules/billing/lib/seeds/sellerSettings.ts +78 -0
  578. package/src/modules/billing/lib/servicePeriods.ts +226 -0
  579. package/src/modules/billing/lib/testRunCleanup.ts +42 -0
  580. package/src/modules/billing/lib/topupCapture.ts +3 -4
  581. package/src/modules/billing/lib/topupReceipt.ts +70 -103
  582. package/src/modules/billing/lib/usageRater.ts +1 -1
  583. package/src/modules/billing/lib/usageRunner.ts +17 -5
  584. package/src/modules/billing/lib/vat.ts +56 -0
  585. package/src/modules/billing/migrations/Migration20261114000000_billing_customer_kind.ts +25 -0
  586. package/src/modules/billing/migrations/Migration20261114100000_billing_cycle_semantics.ts +37 -0
  587. package/src/modules/billing/migrations/Migration20261115000000_billing_invoice_ledger.ts +132 -0
  588. package/src/modules/billing/migrations/Migration20261116000000_billing_invoice_documents.ts +58 -0
  589. package/src/modules/billing/migrations/Migration20261117000000_billing_payments.ts +97 -0
  590. package/src/modules/billing/migrations/Migration20261118000000_billing_memos.ts +21 -0
  591. package/src/modules/billing/migrations/Migration20261119000000_billing_operator_feedback.ts +39 -0
  592. package/src/modules/billing/migrations/Migration20261120000000_billing_allocation_scope_indexes.ts +35 -0
  593. package/src/modules/billing/setup.ts +11 -237
  594. package/src/modules/billing/subscribers/credit-memo-auto-send.ts +15 -0
  595. package/src/modules/billing/subscribers/debit-memo-apply-credit.ts +15 -0
  596. package/src/modules/billing/subscribers/debit-memo-auto-send.ts +15 -0
  597. package/src/modules/billing/subscribers/invoice-apply-credit.ts +39 -0
  598. package/src/modules/billing/subscribers/invoice-auto-send.ts +46 -0
  599. package/src/modules/billing/subscribers/payment-captured-to-paid.ts +84 -62
  600. package/src/modules/billing/subscribers/prepaid-topup-captured.ts +4 -8
  601. package/dist/modules/billing/lib/invoiceStatus.js +0 -38
  602. package/dist/modules/billing/lib/invoiceStatus.js.map +0 -7
  603. package/src/modules/billing/__tests__/lib/invoiceStatus.test.ts +0 -97
  604. package/src/modules/billing/lib/invoiceStatus.ts +0 -61
@@ -0,0 +1,196 @@
1
+ import type { EntityManager } from '@mikro-orm/postgresql'
2
+ import { z } from 'zod'
3
+ import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
+ import type { SendEmailOptions } from '@open-mercato/shared/lib/email/send'
5
+ import { BillingAccount, BillingInvoice } from '../data/entities'
6
+ import { emitBillingEvent } from '../events'
7
+ import { resolveInvoiceLanguage } from './invoiceLanguages'
8
+ import { formatIban } from './iban'
9
+ import { documentLocale, formatDocumentDate, formatDocumentMoney, type DocumentTranslate } from './pdf/invoiceModel'
10
+ import { getInvoicePdf, loadDocumentTranslator, type TranslatorLoader } from './pdf/invoicePdf'
11
+
12
+ /**
13
+ * Emails a posted billing document to the account's invoice address with
14
+ * the frozen PDF attached (Spec B), through the platform's standard
15
+ * `sendEmail()` — no custom transport.
16
+ *
17
+ * - posted (non-draft, non-test) documents only → 409 `billing.invoice.not_posted`;
18
+ * - the connector's placeholder addresses (`@invalid.local`) and malformed
19
+ * emails are refused → 422 `billing.invoice.email_invalid`, nothing sent;
20
+ * - every attempt appends `{ at, to, status, error?, trigger }` to
21
+ * `metadata.deliveries` (atomic jsonb append — concurrent sends never
22
+ * lose an entry) and emits `billing.invoice.sent` / `send_failed`;
23
+ * - transport errors (e.g. `EMAIL_TRANSPORT_NOT_CONFIGURED`) are recorded
24
+ * as a `failed` delivery and returned, never thrown — posting is
25
+ * unaffected and the operator sees the reason.
26
+ */
27
+
28
+ export type DeliveryTrigger = 'manual' | 'auto'
29
+
30
+ export type InvoiceDelivery = {
31
+ at: string
32
+ to: string
33
+ status: 'sent' | 'failed'
34
+ error?: string | null
35
+ trigger: DeliveryTrigger
36
+ }
37
+
38
+ export type SendInvoiceResult = { invoiceId: string; to: string; status: 'sent' | 'failed'; error: string | null }
39
+
40
+ export type SendEmailFn = (options: SendEmailOptions) => Promise<void>
41
+
42
+ export type SendInvoiceDeps = {
43
+ sendEmail?: SendEmailFn
44
+ loadTranslator?: TranslatorLoader
45
+ now?: () => Date
46
+ }
47
+
48
+ const PLACEHOLDER_DOMAIN = '@invalid.local'
49
+ const emailSchema = z.string().trim().email()
50
+
51
+ export function isDeliverableEmail(value: string | null | undefined): value is string {
52
+ if (!value) return false
53
+ if (value.trim().toLowerCase().endsWith(PLACEHOLDER_DOMAIN)) return false
54
+ return emailSchema.safeParse(value).success
55
+ }
56
+
57
+ export function hasSuccessfulDelivery(
58
+ invoice: Pick<BillingInvoice, 'metadata'>,
59
+ trigger?: DeliveryTrigger,
60
+ ): boolean {
61
+ const deliveries = (invoice.metadata?.deliveries ?? []) as InvoiceDelivery[]
62
+ return Array.isArray(deliveries) && deliveries.some((d) => d.status === 'sent' && (!trigger || d.trigger === trigger))
63
+ }
64
+
65
+ function escapeHtml(value: string): string {
66
+ return value.replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c] ?? c)
67
+ }
68
+
69
+ function fill(template: string, params: Record<string, string>): string {
70
+ return template.replace(/\{(\w+)\}/g, (match, key: string) => params[key] ?? match)
71
+ }
72
+
73
+ /** Subject + plain-text and HTML bodies in the document language. Pure. */
74
+ export function buildInvoiceEmail(params: {
75
+ invoice: Pick<BillingInvoice, 'number' | 'totalGross' | 'currencyCode' | 'dueDate'>
76
+ buyerName: string
77
+ sellerName: string | null
78
+ iban: string | null
79
+ language: string
80
+ translate: DocumentTranslate
81
+ }): { subject: string; text: string; html: string } {
82
+ const { invoice, translate: t } = params
83
+ const locale = documentLocale(params.language)
84
+ const values = {
85
+ number: invoice.number,
86
+ name: params.buyerName,
87
+ amount: formatDocumentMoney(invoice.totalGross, invoice.currencyCode, locale).replace(/[  ]/g, ' '),
88
+ dueDate: formatDocumentDate(invoice.dueDate ?? null, locale),
89
+ iban: params.iban ? formatIban(params.iban) : '',
90
+ }
91
+ const paragraphs = [
92
+ fill(t('billing.email.invoice.greeting', 'Hello {name},'), values),
93
+ fill(t('billing.email.invoice.body', 'Please find attached invoice {number} for {amount}, due on {dueDate}.'), values),
94
+ params.iban
95
+ ? fill(t('billing.email.invoice.payment', 'Please pay to IBAN {iban} with the transfer title {number}.'), values)
96
+ : '',
97
+ [t('billing.email.invoice.closing', 'Kind regards,'), params.sellerName ?? ''].filter(Boolean).join('\n'),
98
+ ].filter(Boolean)
99
+ return {
100
+ subject: fill(t('billing.email.invoice.subject', 'Invoice {number}'), values),
101
+ text: paragraphs.join('\n\n'),
102
+ html: paragraphs.map((p) => `<p>${escapeHtml(p).replace(/\n/g, '<br/>')}</p>`).join(''),
103
+ }
104
+ }
105
+
106
+ async function defaultSendEmail(options: SendEmailOptions): Promise<void> {
107
+ const { sendEmail } = await import('@open-mercato/shared/lib/email/send')
108
+ await sendEmail(options)
109
+ }
110
+
111
+ async function appendDelivery(em: EntityManager, invoice: BillingInvoice, delivery: InvoiceDelivery): Promise<void> {
112
+ await em.execute(
113
+ `UPDATE billing_invoices
114
+ SET metadata = jsonb_set(COALESCE(metadata, '{}'::jsonb), '{deliveries}',
115
+ COALESCE(metadata->'deliveries', '[]'::jsonb) || ?::jsonb),
116
+ updated_at = now()
117
+ WHERE id = ? AND tenant_id = ? AND organization_id = ?`,
118
+ [JSON.stringify([delivery]), invoice.id, invoice.tenantId, invoice.organizationId],
119
+ )
120
+ const deliveries = Array.isArray(invoice.metadata?.deliveries) ? (invoice.metadata?.deliveries as InvoiceDelivery[]) : []
121
+ invoice.metadata = { ...(invoice.metadata ?? {}), deliveries: [...deliveries, delivery] }
122
+ }
123
+
124
+ export async function sendInvoice(
125
+ em: EntityManager,
126
+ params: { invoiceId: string; tenantId: string; organizationId: string; trigger: DeliveryTrigger },
127
+ deps: SendInvoiceDeps = {},
128
+ ): Promise<SendInvoiceResult> {
129
+ const scope = { tenantId: params.tenantId, organizationId: params.organizationId }
130
+ const invoice = await em.findOne(BillingInvoice, { id: params.invoiceId, ...scope, deletedAt: null } as never)
131
+ if (!invoice) throw new CrudHttpError(404, { error: 'Invoice not found' })
132
+ if (invoice.status === 'draft' || invoice.testRun) {
133
+ throw new CrudHttpError(409, {
134
+ error: 'Only posted invoices can be sent',
135
+ code: 'billing.invoice.not_posted',
136
+ })
137
+ }
138
+
139
+ // The live account address wins (an operator may fix a bad address and
140
+ // resend); the frozen buyer snapshot is the fallback for a deleted account.
141
+ const account = await em.findOne(BillingAccount, { id: invoice.billAccountId, ...scope } as never)
142
+ const buyer = (invoice.buyerSnapshot ?? {}) as Record<string, unknown>
143
+ const to = account?.invoiceEmail ?? (typeof buyer.invoice_email === 'string' ? buyer.invoice_email : '')
144
+ if (!isDeliverableEmail(to)) {
145
+ throw new CrudHttpError(422, {
146
+ error: 'The billing account has no deliverable invoice email',
147
+ code: 'billing.invoice.email_invalid',
148
+ })
149
+ }
150
+
151
+ const language = resolveInvoiceLanguage(
152
+ (typeof buyer.invoice_language === 'string' && buyer.invoice_language) || account?.invoiceLanguage,
153
+ )
154
+ const translate = await (deps.loadTranslator ?? loadDocumentTranslator)(language)
155
+ const seller = (invoice.sellerSnapshot ?? {}) as Record<string, unknown>
156
+ const email = buildInvoiceEmail({
157
+ invoice,
158
+ buyerName: (typeof buyer.name === 'string' && buyer.name) || account?.name || '',
159
+ sellerName: typeof seller.legal_name === 'string' ? seller.legal_name : null,
160
+ iban: typeof seller.iban === 'string' ? seller.iban : null,
161
+ language,
162
+ translate,
163
+ })
164
+
165
+ const now = deps.now ?? (() => new Date())
166
+ let error: string | null = null
167
+ try {
168
+ const pdf = await getInvoicePdf(em, invoice, { loadTranslator: deps.loadTranslator })
169
+ await (deps.sendEmail ?? defaultSendEmail)({
170
+ to,
171
+ subject: email.subject,
172
+ text: email.text,
173
+ html: email.html,
174
+ attachments: [{ filename: pdf.fileName, content: Buffer.from(pdf.bytes).toString('base64'), contentType: 'application/pdf' }],
175
+ tenantId: params.tenantId,
176
+ organizationId: params.organizationId,
177
+ })
178
+ } catch (err) {
179
+ error = err instanceof Error ? err.message : String(err)
180
+ console.warn('[billing.invoice.send] delivery failed', invoice.id, error)
181
+ }
182
+
183
+ const status: InvoiceDelivery['status'] = error ? 'failed' : 'sent'
184
+ await appendDelivery(em, invoice, { at: now().toISOString(), to, status, error, trigger: params.trigger })
185
+ await emitBillingEvent(status === 'sent' ? 'billing.invoice.sent' : 'billing.invoice.send_failed', {
186
+ invoiceId: invoice.id,
187
+ invoiceNumber: invoice.number,
188
+ billAccountId: invoice.billAccountId,
189
+ to,
190
+ trigger: params.trigger,
191
+ error,
192
+ tenantId: invoice.tenantId,
193
+ organizationId: invoice.organizationId,
194
+ })
195
+ return { invoiceId: invoice.id, to, status, error }
196
+ }
@@ -0,0 +1,57 @@
1
+ import type { Locale } from '@open-mercato/shared/lib/i18n/config'
2
+
3
+ /**
4
+ * Invoice languages the built-in billing document template can render.
5
+ *
6
+ * Single source of truth for the account form's language picklist, the
7
+ * `invoiceLanguage` validator, the `Intl` locale used for money / dates on
8
+ * documents and emails, and the dictionary the document translator loads.
9
+ * Adding a language means adding `i18n/<code>.json` AND one entry here — the
10
+ * dictionary-parity test fails if either is missing.
11
+ *
12
+ * Every `code` must be a UI locale the framework ships (`Locale`), because the
13
+ * document translator loads the regular module dictionary for it. The
14
+ * `satisfies` clause below turns a code the framework does not ship into a
15
+ * compile error.
16
+ *
17
+ * `label` is the language's own name (endonym), so it reads correctly no
18
+ * matter which UI locale the operator uses — it is intentionally not routed
19
+ * through i18n. Order: default first, then product priority.
20
+ *
21
+ * Specs: .ai/specs/2026-11-14-billing-account-workspace.md (D22),
22
+ * .ai/specs/2026-11-15-billing-multi-language.md. Narrowing the list per
23
+ * organization (`BillingSettings.enabled_invoice_languages`) arrives with the
24
+ * standalone-ledger spec; until then every supported language is selectable.
25
+ */
26
+
27
+ export const SUPPORTED_INVOICE_LANGUAGES = [
28
+ { code: 'en', label: 'English', intlLocale: 'en-GB' },
29
+ { code: 'es', label: 'Español', intlLocale: 'es-ES' },
30
+ { code: 'de', label: 'Deutsch', intlLocale: 'de-DE' },
31
+ { code: 'pl', label: 'Polski', intlLocale: 'pl-PL' },
32
+ ] as const satisfies ReadonlyArray<{ code: Locale; label: string; intlLocale: string }>
33
+
34
+ export type InvoiceLanguageCode = (typeof SUPPORTED_INVOICE_LANGUAGES)[number]['code']
35
+
36
+ export const INVOICE_LANGUAGE_CODES = SUPPORTED_INVOICE_LANGUAGES.map((l) => l.code) as [
37
+ InvoiceLanguageCode,
38
+ ...InvoiceLanguageCode[],
39
+ ]
40
+
41
+ export const DEFAULT_INVOICE_LANGUAGE: InvoiceLanguageCode = 'en'
42
+
43
+ export function isSupportedInvoiceLanguage(value: string): value is InvoiceLanguageCode {
44
+ return (INVOICE_LANGUAGE_CODES as readonly string[]).includes(value)
45
+ }
46
+
47
+ /** Normalizes a stored / requested language to a supported code (`' PL '` → `pl`), else the default. */
48
+ export function resolveInvoiceLanguage(value: string | null | undefined): InvoiceLanguageCode {
49
+ const normalized = typeof value === 'string' ? value.trim().toLowerCase() : ''
50
+ return isSupportedInvoiceLanguage(normalized) ? normalized : DEFAULT_INVOICE_LANGUAGE
51
+ }
52
+
53
+ /** The `Intl` locale documents in this language format money and dates with. */
54
+ export function invoiceIntlLocale(language: string | null | undefined): string {
55
+ const code = resolveInvoiceLanguage(language)
56
+ return SUPPORTED_INVOICE_LANGUAGES.find((l) => l.code === code)?.intlLocale ?? 'en-GB'
57
+ }
@@ -1,62 +1,61 @@
1
1
  import type { EntityManager } from '@mikro-orm/postgresql'
2
- import type { AwilixContainer } from 'awilix'
3
- import {
4
- SalesInvoice,
5
- SalesInvoiceLine,
6
- } from '@open-mercato/core/modules/sales/data/entities'
7
- import type { SalesDocumentNumberGenerator } from '@open-mercato/core/modules/sales/services/salesDocumentNumberGenerator'
2
+ import { BillingInvoice, BillingInvoiceLine } from '../data/entities'
8
3
  import type { BillingAccount, BillingItem } from '../data/entities'
9
4
  import type { BillPeriod } from './billPeriod'
10
- import {
11
- __resetInvoiceStatusCacheForTests,
12
- resolveInvoiceStatusEntryId,
13
- } from './invoiceStatus'
5
+ import type { RecurringLine } from './servicePeriods'
6
+ import { draftNumber, testNumber } from './numbering'
7
+ import { sumTotals } from './vat'
8
+ import { lineVat, priceBasisOf, unitPriceNetOf, type PriceBasis } from './lineAmounts'
14
9
 
15
10
  /**
16
- * Writes a `core/sales` draft invoice for a billing account's bill period.
11
+ * Writes a billing-owned draft invoice (`billing_invoices` +
12
+ * `billing_invoice_lines`) for one bill date of an account — Spec 0: billing
13
+ * no longer records anything in core `sales`.
17
14
  *
18
- * Two notable deviations from the spec, documented in CHANGELOG / spec:
19
- *
20
- * 1. `SalesInvoice.invoice_number` is `NOT NULL` in the upstream
21
- * schema, but the spec says "sequence number assigned at Post."
22
- * Real-mode invoices get a real number via
23
- * `salesDocumentNumberGenerator.generate({ kind: 'invoice' })` at
24
- * *draft create time* — the spec's intent (gap-free numbering via
25
- * atomic SQL upsert) is preserved; the timing differs because the
26
- * upstream column is `NOT NULL`. "Post" then just transitions
27
- * status `draft → posted`.
28
- *
29
- * 2. Test-mode invoices use a `TEST-<uuid>` placeholder number (not
30
- * the real sequence) so test runs do not burn production numbers
31
- * and can be repeated idempotently.
32
- *
33
- * VAT is NOT computed here — `core/sales`'s tax service owns the
34
- * per-line `tax_rate` / `tax_amount` calculation, and runs on POST.
35
- * Drafts therefore carry zeroes in the tax columns; the totals reflect
36
- * net amounts only.
15
+ * - Real drafts get a temporary `DRAFT-…` number; the series number is
16
+ * claimed only at post (gap-free numbering — no number is burnt by a
17
+ * deleted draft).
18
+ * - Test drafts get `TEST-…` and `test_run = true`; they are never posted.
19
+ * - Spec B (B-D2): every line carries its item's VAT rate; tax is rounded
20
+ * per line (half-up, 2 dp) and totals are sums of the rounded lines.
21
+ * - Spec F (F-D2): line amounts are on the item's `price_basis`; net / tax
22
+ * / gross come from `lineVat`, so a typed gross is reproduced exactly.
37
23
  */
38
24
 
39
25
  export type WriteMode = 'real' | 'test' | 'dry'
40
26
 
41
27
  export type InvoiceLineDescriptor = {
42
- /** Maps to billing_item_id on the resulting SalesInvoiceLine.metadata. */
28
+ /** Maps to `billing_invoice_lines.billing_item_id`. */
43
29
  billingItemId: string
44
30
  /** From the spec's `metadata.billing_type` for traceability. */
45
31
  billingType: 'one_time' | 'recurring' | 'usage'
46
32
  description: string
47
- /** Net unit price (numeric string for SalesInvoiceLine.unit_price_net). */
48
- unitPriceNet: string
33
+ /** Unit price on `priceBasis` (numeric string). The writer derives `unit_price_net`. */
34
+ unitPrice: string
49
35
  /** Quantity (string for the numeric(18,4) column). */
50
36
  quantity: string
51
- /** Total net (string). */
52
- totalNetAmount: string
37
+ /** Extended line amount on `priceBasis` (unit × quantity × proration). */
38
+ lineAmount: string
39
+ /** Spec F: the basis `unitPrice` / `lineAmount` are on (the item's `price_basis`). */
40
+ priceBasis: PriceBasis
41
+ /** Spec B: VAT rate in percent (numeric string), from the item. */
42
+ taxRate: string
53
43
  /** Optional per-line metadata extension — e.g. usage tier breakdown. */
54
44
  metadataExtra?: Record<string, unknown>
45
+ /**
46
+ * Spec A: inclusive service period the line covers (recurring + usage).
47
+ * Written to line metadata and used for the invoice's display period.
48
+ */
49
+ servicePeriodStart?: Date
50
+ servicePeriodEnd?: Date
55
51
  }
56
52
 
57
53
  export type WriteInvoiceParams = {
58
54
  mode: WriteMode
59
55
  account: BillingAccount
56
+ /** Spec A: the bill date this invoice is for — the dedupe key. */
57
+ billDate: Date
58
+ /** Display period: min/max of the line service periods (see `displayPeriod`). */
60
59
  period: BillPeriod
61
60
  billRunId: string
62
61
  lines: InvoiceLineDescriptor[]
@@ -66,148 +65,105 @@ export type WrittenInvoice = {
66
65
  invoiceId: string | null
67
66
  invoiceNumber: string | null
68
67
  totalNet: string
68
+ totalGross: string
69
69
  }
70
70
 
71
- const DRAFT_INVOICE_STATUS_VALUE = 'draft'
71
+ /** @deprecated kept for test-suite compatibility; the writer has no cache any more. */
72
+ export function __resetInvoiceWriterCacheForTests(): void {}
72
73
 
73
- /**
74
- * Test hook — clears the shared resolver's status cache so the engine
75
- * test suite stays deterministic across mocked-EM environments.
76
- */
77
- export function __resetInvoiceWriterCacheForTests(): void {
78
- __resetInvoiceStatusCacheForTests()
79
- }
80
-
81
- function generateTestInvoiceNumber(): string {
82
- // Match the pattern operators search by: `TEST-` prefix + random
83
- // suffix so the test wipe-out endpoint can filter via `LIKE 'TEST-%'`
84
- // (defensive — primary filter is `metadata.test_run=true`).
85
- const random = Math.floor(Math.random() * 1e9)
86
- .toString(16)
87
- .padStart(8, '0')
88
- return `TEST-${Date.now().toString(16)}-${random}`
89
- }
90
-
91
- /**
92
- * 2dp half-up rounding that survives binary floating-point edge cases
93
- * (e.g. `1.005 * 100 === 100.49999...` would otherwise round DOWN to
94
- * `1.00` under naive `Math.round`). The `+ EPSILON` nudge pushes
95
- * representations like 100.4999...8 up to the true half-up boundary.
96
- * Matches the spec's "2 decimal places, half-up" rounding policy.
97
- */
74
+ /** 2dp half-up rounding (survives 1.005-style binary edges). */
98
75
  function roundHalfUp2dp(value: number): number {
99
- return Math.round((value + Number.EPSILON) * 100) / 100
76
+ const sign = value < 0 ? -1 : 1
77
+ return (sign * Math.round((Math.abs(value) + Number.EPSILON) * 100)) / 100
100
78
  }
101
79
 
102
80
  function format4dp(value: number): string {
103
81
  return roundHalfUp2dp(value).toFixed(4)
104
82
  }
105
83
 
106
- function sumNetTotal(lines: InvoiceLineDescriptor[]): string {
107
- let total = 0
108
- for (const line of lines) {
109
- total += Number.parseFloat(line.totalNetAmount)
110
- }
111
- return format4dp(total)
84
+ /** VAT rate of an item as a numeric string; items predating Spec B read as 0. */
85
+ export function itemTaxRate(item: Pick<BillingItem, 'taxRate'>): string {
86
+ const rate = Number.parseFloat(String(item.taxRate ?? '0'))
87
+ return Number.isFinite(rate) && rate >= 0 ? String(rate) : '0'
88
+ }
89
+
90
+ function isoDate(date: Date): string {
91
+ return new Date(date).toISOString().slice(0, 10)
112
92
  }
113
93
 
114
94
  export async function writeDraftInvoice(
115
95
  em: EntityManager,
116
- container: AwilixContainer,
117
96
  params: WriteInvoiceParams,
118
97
  ): Promise<WrittenInvoice> {
119
- const totalNet = sumNetTotal(params.lines)
98
+ const amounts = params.lines.map((line) =>
99
+ lineVat({ amount: line.lineAmount, priceBasis: line.priceBasis, taxRate: line.taxRate }),
100
+ )
101
+ const totals = sumTotals(amounts)
120
102
 
121
103
  if (params.mode === 'dry') {
122
104
  // Compute totals; persist nothing.
123
- return { invoiceId: null, invoiceNumber: null, totalNet }
105
+ return { invoiceId: null, invoiceNumber: null, totalNet: totals.subtotalNet, totalGross: totals.totalGross }
124
106
  }
125
107
 
126
108
  const { account, period, billRunId, lines, mode } = params
127
-
128
- let invoiceNumber: string
129
- if (mode === 'test') {
130
- invoiceNumber = generateTestInvoiceNumber()
131
- } else {
132
- const numberGenerator = container.resolve(
133
- 'salesDocumentNumberGenerator',
134
- ) as SalesDocumentNumberGenerator
135
- const generated = await numberGenerator.generate({
136
- organizationId: account.organizationId,
137
- tenantId: account.tenantId,
138
- kind: 'invoice',
139
- })
140
- invoiceNumber = generated.number
141
- }
142
-
143
- const statusEntryId = await resolveInvoiceStatusEntryId(
144
- em,
145
- account.tenantId,
146
- account.organizationId,
147
- 'draft',
148
- )
149
-
109
+ const invoiceNumber = mode === 'test' ? testNumber() : draftNumber()
150
110
  const now = new Date()
151
- const invoice = em.create(SalesInvoice, {
111
+
112
+ const invoice = em.create(BillingInvoice, {
152
113
  organizationId: account.organizationId,
153
114
  tenantId: account.tenantId,
154
- invoiceNumber,
155
- statusEntryId,
156
- status: DRAFT_INVOICE_STATUS_VALUE,
157
- issueDate: now,
115
+ billAccountId: account.id,
116
+ documentType: 'invoice',
117
+ number: invoiceNumber,
118
+ series: null,
119
+ status: 'draft',
120
+ testRun: mode === 'test',
121
+ billRunId,
122
+ billDate: params.billDate,
123
+ periodStart: period.periodStart,
124
+ periodEnd: period.periodEnd,
158
125
  currencyCode: account.currencyCode,
159
- subtotalNetAmount: totalNet,
160
- subtotalGrossAmount: totalNet, // gross == net for drafts (tax computed on post)
161
- discountTotalAmount: '0',
162
- taxTotalAmount: '0',
163
- grandTotalNetAmount: totalNet,
164
- grandTotalGrossAmount: totalNet,
165
- paidTotalAmount: '0',
166
- outstandingAmount: totalNet,
167
- metadata: {
168
- bill_run_id: billRunId,
169
- bill_account_id: account.id,
170
- bill_period_start: period.periodStart.toISOString().slice(0, 10),
171
- bill_period_end: period.periodEnd.toISOString().slice(0, 10),
172
- test_run: mode === 'test' ? true : undefined,
173
- },
126
+ subtotalNet: totals.subtotalNet,
127
+ taxTotal: totals.taxTotal,
128
+ totalGross: totals.totalGross,
129
+ paidTotal: '0',
130
+ creditedTotal: '0',
131
+ outstanding: totals.totalGross,
174
132
  createdAt: now,
175
133
  updatedAt: now,
176
134
  })
177
135
  em.persist(invoice)
136
+ await em.flush() // need the invoice id for the lines
178
137
 
179
- let lineNumber = 1
180
- for (const line of lines) {
181
- const invoiceLine = em.create(SalesInvoiceLine, {
182
- invoice,
183
- organizationId: account.organizationId,
184
- tenantId: account.tenantId,
185
- lineNumber: lineNumber++,
186
- kind: 'product',
187
- description: line.description,
188
- quantity: line.quantity,
189
- normalizedQuantity: line.quantity,
190
- currencyCode: account.currencyCode,
191
- unitPriceNet: line.unitPriceNet,
192
- unitPriceGross: line.unitPriceNet, // gross == net until tax computed on post
193
- discountAmount: '0',
194
- discountPercent: '0',
195
- taxRate: '0',
196
- taxAmount: '0',
197
- totalNetAmount: line.totalNetAmount,
198
- totalGrossAmount: line.totalNetAmount,
199
- metadata: {
200
- billing_item_id: line.billingItemId,
201
- billing_type: line.billingType,
202
- ...(line.metadataExtra ?? {}),
203
- },
204
- })
205
- em.persist(invoiceLine)
206
- }
207
-
138
+ lines.forEach((line, index) => {
139
+ const amount = amounts[index]
140
+ em.persist(
141
+ em.create(BillingInvoiceLine, {
142
+ organizationId: account.organizationId,
143
+ tenantId: account.tenantId,
144
+ invoiceId: invoice.id,
145
+ lineNumber: index + 1,
146
+ billingItemId: line.billingItemId,
147
+ billingType: line.billingType,
148
+ description: line.description,
149
+ quantity: line.quantity,
150
+ unitPriceNet:
151
+ line.priceBasis === 'gross' ? unitPriceNetOf(line.unitPrice, 'gross', line.taxRate) : line.unitPrice,
152
+ taxRate: line.taxRate,
153
+ netAmount: format4dp(amount.net),
154
+ taxAmount: format4dp(amount.tax),
155
+ grossAmount: format4dp(amount.gross),
156
+ servicePeriodStart: line.servicePeriodStart ?? null,
157
+ servicePeriodEnd: line.servicePeriodEnd ?? null,
158
+ metadata: line.metadataExtra ?? null,
159
+ createdAt: now,
160
+ updatedAt: now,
161
+ }),
162
+ )
163
+ })
208
164
  await em.flush()
209
165
 
210
- return { invoiceId: invoice.id, invoiceNumber, totalNet }
166
+ return { invoiceId: invoice.id, invoiceNumber, totalNet: totals.subtotalNet, totalGross: totals.totalGross }
211
167
  }
212
168
 
213
169
  /**
@@ -238,9 +194,11 @@ export function buildInvoiceLinesFromItems(
238
194
  billingItemId: item.id,
239
195
  billingType: 'one_time',
240
196
  description: item.description,
241
- unitPriceNet: amount,
197
+ unitPrice: amount,
242
198
  quantity: '1',
243
- totalNetAmount: amount,
199
+ lineAmount: amount,
200
+ priceBasis: priceBasisOf(item.priceBasis),
201
+ taxRate: itemTaxRate(item),
244
202
  })
245
203
  } else if (item.type === 'recurring') {
246
204
  const rate = item.rateJson as { unit_price?: number } | null
@@ -250,11 +208,67 @@ export function buildInvoiceLinesFromItems(
250
208
  billingItemId: item.id,
251
209
  billingType: 'recurring',
252
210
  description: item.description,
253
- unitPriceNet: unitPrice,
211
+ unitPrice,
254
212
  quantity: '1',
255
- totalNetAmount: unitPrice,
213
+ lineAmount: unitPrice,
214
+ priceBasis: priceBasisOf(item.priceBasis),
215
+ taxRate: itemTaxRate(item),
256
216
  })
257
217
  }
258
218
  }
259
219
  return lines
260
220
  }
221
+
222
+ /**
223
+ * Display period of an invoice: min start / max end over the lines' service
224
+ * periods, falling back to `fallback` (the arrears window) when no line
225
+ * carries one (e.g. a one_time-only invoice).
226
+ */
227
+ export function displayPeriod(lines: readonly InvoiceLineDescriptor[], fallback: BillPeriod): BillPeriod {
228
+ let start: Date | null = null
229
+ let end: Date | null = null
230
+ for (const line of lines) {
231
+ if (line.servicePeriodStart && (!start || line.servicePeriodStart < start)) start = line.servicePeriodStart
232
+ if (line.servicePeriodEnd && (!end || line.servicePeriodEnd > end)) end = line.servicePeriodEnd
233
+ }
234
+ return { periodStart: start ?? fallback.periodStart, periodEnd: end ?? fallback.periodEnd }
235
+ }
236
+
237
+ /**
238
+ * Spec A: invoice lines for computed recurring service periods (charges and
239
+ * credits). Full-period charges keep the bare item description; partial
240
+ * periods and credits append the covered dates so the line is
241
+ * self-explanatory on any document (language-neutral — the PDF renders
242
+ * localized labels later).
243
+ */
244
+ export function buildRecurringLineDescriptors(
245
+ item: Pick<BillingItem, 'id' | 'description' | 'chargeTiming' | 'taxRate' | 'priceBasis'>,
246
+ lines: readonly RecurringLine[],
247
+ ): InvoiceLineDescriptor[] {
248
+ return lines.map((line) => {
249
+ const amount = format4dp(line.amount)
250
+ const dated = line.proration !== null || line.kind === 'credit'
251
+ return {
252
+ billingItemId: item.id,
253
+ billingType: 'recurring' as const,
254
+ description: dated
255
+ ? `${item.description} · ${isoDate(line.servicePeriodStart)} – ${isoDate(line.servicePeriodEnd)}`
256
+ : item.description,
257
+ unitPrice: amount,
258
+ quantity: '1',
259
+ lineAmount: amount,
260
+ // Proration and credit lines inherit the item's rate (Spec B) and basis (Spec F).
261
+ priceBasis: priceBasisOf(item.priceBasis),
262
+ taxRate: itemTaxRate(item),
263
+ servicePeriodStart: line.servicePeriodStart,
264
+ servicePeriodEnd: line.servicePeriodEnd,
265
+ metadataExtra: {
266
+ charge_timing: item.chargeTiming,
267
+ ...(line.proration
268
+ ? { proration: { active_days: line.proration.activeDays, period_days: line.proration.periodDays } }
269
+ : {}),
270
+ ...(line.kind === 'credit' ? { credit_for: item.id } : {}),
271
+ },
272
+ }
273
+ })
274
+ }