@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
@@ -14,6 +14,12 @@ import {
14
14
  import { Switch } from "@open-mercato/ui/primitives/switch";
15
15
  import { Kbd, KbdShortcut } from "@open-mercato/ui/primitives/kbd";
16
16
  import { useT } from "@open-mercato/shared/lib/i18n/context";
17
+ import Link from "next/link";
18
+ import { SegmentedControl, SegmentedControlItem } from "@open-mercato/ui/primitives/segmented-control";
19
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
20
+ import { ItemPriceField } from "./ItemPriceField.js";
21
+ import { BILLING_SETTINGS_HREF } from "./BillingSettingsLink.js";
22
+ import { counterpartAmount } from "../lib/lineAmounts.js";
17
23
  const EMPTY_VALUES = {
18
24
  billAccountId: "",
19
25
  type: "recurring",
@@ -28,7 +34,11 @@ const EMPTY_VALUES = {
28
34
  uomCode: "",
29
35
  subscriptionId: "",
30
36
  subscriptionItemId: "",
31
- isActive: true
37
+ isActive: true,
38
+ chargeTiming: "in_advance",
39
+ prorate: true,
40
+ taxRate: "",
41
+ priceBasis: "net"
32
42
  };
33
43
  function todayIso() {
34
44
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
@@ -49,18 +59,64 @@ function ItemForm({
49
59
  mode,
50
60
  accountOptions,
51
61
  initial,
62
+ accountBillingMode,
63
+ accountCurrency,
52
64
  submitting = false,
53
65
  onSubmit,
54
66
  onCancel
55
67
  }) {
56
68
  const t = useT();
57
69
  const [values, setValues] = React.useState(() => mergeInitial(initial));
70
+ const selectedAccount = accountOptions?.find((option) => option.id === values.billAccountId);
71
+ const isPrepaidAccount = accountBillingMode === "prepaid" || selectedAccount?.billing_mode === "prepaid";
72
+ const currencyCode = accountCurrency ?? selectedAccount?.currency_code ?? "";
58
73
  const [error, setError] = React.useState(null);
59
74
  const isEdit = mode === "edit";
60
75
  const initialKey = React.useMemo(() => JSON.stringify(initial ?? null), [initial]);
61
76
  React.useEffect(() => {
62
77
  setValues(mergeInitial(initial));
63
78
  }, [initialKey, initial]);
79
+ const [prepaidTaxPoint, setPrepaidTaxPoint] = React.useState("topup");
80
+ React.useEffect(() => {
81
+ let cancelled = false;
82
+ void apiCall(
83
+ "/api/billing/settings"
84
+ ).then((call) => {
85
+ if (cancelled || !call.ok) return;
86
+ const settings = call.result?.settings;
87
+ if (settings?.prepaidTaxPoint === "consumption") setPrepaidTaxPoint("consumption");
88
+ const rate = settings?.defaultTaxRate;
89
+ if (!isEdit && typeof rate === "number") {
90
+ setValues((prev) => prev.taxRate === "" ? { ...prev, taxRate: String(rate) } : prev);
91
+ }
92
+ });
93
+ return () => {
94
+ cancelled = true;
95
+ };
96
+ }, [isEdit]);
97
+ const taxLocked = isPrepaidAccount && prepaidTaxPoint === "topup";
98
+ const basisForcedNet = values.type === "usage" && values.usageMode === "tiered";
99
+ const effectiveBasis = basisForcedNet ? "net" : values.priceBasis;
100
+ const handleBasisChange = React.useCallback((next) => {
101
+ const target = next === "gross" ? "gross" : "net";
102
+ setValues((prev) => {
103
+ if (prev.priceBasis === target) return prev;
104
+ const rate = toNumber(prev.taxRate) ?? 0;
105
+ const convert = (raw, decimals) => {
106
+ const amount = toNumber(raw);
107
+ if (amount === null) return raw;
108
+ const converted = decimals > 2 ? prev.priceBasis === "gross" ? amount / (1 + rate / 100) : amount * (1 + rate / 100) : counterpartAmount(amount, prev.priceBasis, rate);
109
+ return String(Number(converted.toFixed(decimals)));
110
+ };
111
+ return {
112
+ ...prev,
113
+ priceBasis: target,
114
+ oneTimeAmount: convert(prev.oneTimeAmount, 2),
115
+ recurringUnitPrice: convert(prev.recurringUnitPrice, 2),
116
+ usageUnitPrice: convert(prev.usageUnitPrice, 6)
117
+ };
118
+ });
119
+ }, []);
64
120
  const set = React.useCallback(
65
121
  (key, value) => {
66
122
  setValues((prev) => ({ ...prev, [key]: value }));
@@ -147,6 +203,11 @@ function ItemForm({
147
203
  setError(t("billing.items.form.error.uom_required", "UoM code is required for usage items"));
148
204
  return;
149
205
  }
206
+ const taxRate = toNumber(values.taxRate);
207
+ if (values.taxRate.trim() !== "" && (taxRate == null || taxRate < 0 || taxRate > 100)) {
208
+ setError(t("billing.items.form.error.tax_rate_invalid", "VAT rate must be between 0 and 100"));
209
+ return;
210
+ }
150
211
  const rateJsonResult = buildRateJson();
151
212
  if (typeof rateJsonResult === "string") {
152
213
  setError(rateJsonResult);
@@ -168,17 +229,26 @@ function ItemForm({
168
229
  if (values.subscriptionItemId.trim()) {
169
230
  payload.subscriptionItemId = values.subscriptionItemId.trim();
170
231
  }
232
+ if (values.type === "recurring") {
233
+ payload.chargeTiming = values.chargeTiming;
234
+ payload.prorate = values.prorate;
235
+ }
236
+ if (taxRate != null) payload.taxRate = taxRate;
237
+ payload.priceBasis = effectiveBasis;
171
238
  if (isEdit) payload.isActive = values.isActive;
172
239
  await onSubmit(payload);
173
- }, [buildRateJson, isEdit, onSubmit, t, values]);
240
+ }, [buildRateJson, effectiveBasis, isEdit, onSubmit, t, values]);
174
241
  const handleKeyDown = React.useCallback(
175
242
  (event) => {
176
243
  if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
177
244
  event.preventDefault();
178
245
  void handleSubmit();
246
+ } else if (event.key === "Escape") {
247
+ event.preventDefault();
248
+ onCancel();
179
249
  }
180
250
  },
181
- [handleSubmit]
251
+ [handleSubmit, onCancel]
182
252
  );
183
253
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-border bg-card p-6", onKeyDown: handleKeyDown, children: [
184
254
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
@@ -270,41 +340,112 @@ function ItemForm({
270
340
  ) })
271
341
  ] })
272
342
  ] }),
273
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mt-6 mb-2", children: t("billing.items.form.rate.title", "Rate") }),
343
+ /* @__PURE__ */ jsxs("div", { className: "mt-6 mb-2 flex flex-wrap items-center justify-between gap-3", children: [
344
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t("billing.items.form.rate.title", "Rate") }),
345
+ /* @__PURE__ */ jsxs(
346
+ SegmentedControl,
347
+ {
348
+ value: effectiveBasis,
349
+ onValueChange: handleBasisChange,
350
+ disabled: taxLocked || basisForcedNet,
351
+ "aria-label": t("billing.items.form.price_basis", "Price entered as"),
352
+ children: [
353
+ /* @__PURE__ */ jsx(SegmentedControlItem, { value: "net", children: t("billing.items.price_basis.net", "Net") }),
354
+ /* @__PURE__ */ jsx(SegmentedControlItem, { value: "gross", children: t("billing.items.price_basis.gross", "Gross") })
355
+ ]
356
+ }
357
+ )
358
+ ] }),
359
+ basisForcedNet && !taxLocked ? /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-muted-foreground", children: t("billing.items.form.price_basis.tiered_net", "Tiered rates are always entered net.") }) : null,
274
360
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
275
- values.type === "one_time" ? /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
276
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
277
- t("billing.items.form.rate.amount", "Amount"),
278
- /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
279
- ] }),
361
+ values.type === "one_time" ? /* @__PURE__ */ jsx(
362
+ ItemPriceField,
363
+ {
364
+ label: t("billing.items.form.rate.amount", "Amount"),
365
+ value: values.oneTimeAmount,
366
+ onChange: (next) => set("oneTimeAmount", next),
367
+ step: "0.01",
368
+ currencyCode,
369
+ priceBasis: effectiveBasis,
370
+ taxRate: values.taxRate,
371
+ showCounterpart: !taxLocked,
372
+ help: t("billing.items.form.rate.amount.help", "Negative allowed for refunds / credits / proration credits.")
373
+ }
374
+ ) : null,
375
+ values.type === "recurring" ? /* @__PURE__ */ jsx(
376
+ ItemPriceField,
377
+ {
378
+ label: t("billing.items.form.rate.unit_price_recurring", "Unit price per cycle"),
379
+ value: values.recurringUnitPrice,
380
+ onChange: (next) => set("recurringUnitPrice", next),
381
+ step: "0.01",
382
+ currencyCode,
383
+ priceBasis: effectiveBasis,
384
+ taxRate: values.taxRate,
385
+ showCounterpart: !taxLocked
386
+ }
387
+ ) : null,
388
+ values.type === "usage" && values.usageMode === "simple" ? /* @__PURE__ */ jsx(
389
+ ItemPriceField,
390
+ {
391
+ label: t("billing.items.form.rate.unit_price_usage", "Unit price per unit consumed"),
392
+ value: values.usageUnitPrice,
393
+ onChange: (next) => set("usageUnitPrice", next),
394
+ step: "0.000001",
395
+ currencyCode,
396
+ priceBasis: effectiveBasis,
397
+ taxRate: values.taxRate,
398
+ showCounterpart: !taxLocked,
399
+ precision: 6
400
+ }
401
+ ) : null,
402
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
403
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.items.form.tax_rate", "VAT rate (%)") }),
280
404
  /* @__PURE__ */ jsx(
281
405
  Input,
282
406
  {
283
407
  type: "number",
284
- step: "0.0001",
285
- value: values.oneTimeAmount,
286
- onChange: (event) => set("oneTimeAmount", event.currentTarget.value)
408
+ min: "0",
409
+ max: "100",
410
+ step: "0.01",
411
+ value: values.taxRate,
412
+ disabled: taxLocked,
413
+ onChange: (event) => set("taxRate", event.currentTarget.value)
287
414
  }
288
415
  ),
289
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t(
290
- "billing.items.form.rate.amount.help",
291
- "Negative allowed for refunds / credits / proration credits."
292
- ) })
293
- ] }) : null,
416
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: taxLocked ? /* @__PURE__ */ jsxs(Fragment, { children: [
417
+ t("billing.items.form.tax_rate.prepaid_topup", "For prepaid accounts, tax is allocated at top-up."),
418
+ " ",
419
+ /* @__PURE__ */ jsx(Link, { href: BILLING_SETTINGS_HREF, className: "text-primary hover:underline", children: t("billing.settings.link", "Billing settings") })
420
+ ] }) : isPrepaidAccount ? t("billing.items.form.tax_rate.prepaid_consumption", "Charged from the balance including VAT.") : isEdit ? t("billing.items.form.tax_rate.help_edit", "Applies to future invoices; posted invoices keep their rate.") : t("billing.items.form.tax_rate.help", "Defaults to the organization VAT rate from billing settings.") })
421
+ ] }),
294
422
  values.type === "recurring" ? /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
295
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
296
- t("billing.items.form.rate.unit_price_recurring", "Unit price per cycle"),
297
- /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
298
- ] }),
299
- /* @__PURE__ */ jsx(
300
- Input,
423
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.items.form.charge_timing", "Charge timing") }),
424
+ /* @__PURE__ */ jsxs(
425
+ Select,
301
426
  {
302
- type: "number",
303
- step: "0.0001",
304
- value: values.recurringUnitPrice,
305
- onChange: (event) => set("recurringUnitPrice", event.currentTarget.value)
427
+ value: values.chargeTiming,
428
+ onValueChange: (next) => set("chargeTiming", next === "in_arrears" ? "in_arrears" : "in_advance"),
429
+ children: [
430
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
431
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
432
+ /* @__PURE__ */ jsx(SelectItem, { value: "in_advance", children: t("billing.items.charge_timing.in_advance", "In advance") }),
433
+ /* @__PURE__ */ jsx(SelectItem, { value: "in_arrears", children: t("billing.items.charge_timing.in_arrears", "In arrears") })
434
+ ] })
435
+ ]
306
436
  }
307
- )
437
+ ),
438
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: values.chargeTiming === "in_arrears" ? t("billing.items.form.charge_timing.help_arrears", "Each bill date charges the period that just ended.") : t("billing.items.form.charge_timing.help_advance", "Each bill date charges the coming period.") })
439
+ ] }) : null,
440
+ values.type === "recurring" ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 text-sm", children: [
441
+ /* @__PURE__ */ jsxs("div", { children: [
442
+ /* @__PURE__ */ jsx("div", { className: "font-medium", children: t("billing.items.form.prorate", "Prorate partial periods") }),
443
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t(
444
+ "billing.items.form.prorate.help",
445
+ "Charge by calendar days when the item starts or ends mid-period, and credit unused days if it ends after being billed in advance."
446
+ ) })
447
+ ] }),
448
+ /* @__PURE__ */ jsx(Switch, { checked: values.prorate, onCheckedChange: (next) => set("prorate", next) })
308
449
  ] }) : null,
309
450
  values.type === "usage" ? /* @__PURE__ */ jsxs(Fragment, { children: [
310
451
  /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
@@ -324,21 +465,7 @@ function ItemForm({
324
465
  }
325
466
  )
326
467
  ] }),
327
- values.usageMode === "simple" ? /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
328
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
329
- t("billing.items.form.rate.unit_price_usage", "Unit price per unit consumed"),
330
- /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
331
- ] }),
332
- /* @__PURE__ */ jsx(
333
- Input,
334
- {
335
- type: "number",
336
- step: "0.000001",
337
- value: values.usageUnitPrice,
338
- onChange: (event) => set("usageUnitPrice", event.currentTarget.value)
339
- }
340
- )
341
- ] }) : /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm col-span-2", children: [
468
+ values.usageMode === "tiered" ? /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm col-span-2", children: [
342
469
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
343
470
  t("billing.items.form.rate.tiered_json", "Tiered rate JSON"),
344
471
  /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
@@ -352,11 +479,16 @@ function ItemForm({
352
479
  onChange: (event) => set("usageTieredJson", event.currentTarget.value)
353
480
  }
354
481
  ),
482
+ currencyCode ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
483
+ t("billing.items.form.rate.prices_in", "Prices in"),
484
+ " ",
485
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: currencyCode })
486
+ ] }) : null,
355
487
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t(
356
488
  "billing.items.form.rate.tiered_json.help",
357
489
  "Models: volume / graduated / flat. Exactly one tier must have `up_to: null` (the open-ended top tier). Server-side validators reject malformed shapes."
358
490
  ) })
359
- ] }),
491
+ ] }) : null,
360
492
  /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
361
493
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
362
494
  t("billing.items.form.uom_code", "UoM code"),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/billing/components/ItemForm.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Kbd, KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n/**\n * Shared form for Billing Item create + edit.\n *\n * The `rate_json` field is type-discriminated per the spec's\n * \"Rate Formats\" section:\n * - `one_time`: `{ amount }` \u2014 one number.\n * - `recurring`: `{ unit_price }` \u2014 one number.\n * - `usage` (simple): `{ unit_price }` + required `uomCode`.\n * - `usage` (tiered, advanced): operator pastes raw JSON\n * `{ model, tiers: [...] }` into the textarea \u2014 for occasional\n * hand-built tiered rates. The integration flow uses CPQ \u2192\n * connector to land tiered rates without UI input.\n *\n * `type` is immutable on edit (per `billingItemUpdateSchema`); we\n * hide the `type` selector in `mode='edit'` so it can't even be\n * surfaced.\n */\n\ntype BillingItemType = 'one_time' | 'recurring' | 'usage'\ntype UsageRateMode = 'simple' | 'tiered'\n\nexport type ItemFormValues = {\n billAccountId: string\n type: BillingItemType\n billStartDate: string\n billEndDate: string\n description: string\n // Rate is type-dependent \u2014 we keep the inputs separate and assemble\n // `rate_json` on submit.\n oneTimeAmount: string\n recurringUnitPrice: string\n usageMode: UsageRateMode\n usageUnitPrice: string\n usageTieredJson: string\n uomCode: string\n subscriptionId: string\n subscriptionItemId: string\n isActive: boolean\n}\n\n// The accounts list API (`/api/billing/accounts`) returns snake_case\n// rows \u2014 mirror that here so the picker reads real values. (camelCase\n// resolved to `undefined`, rendering \"Name \u00B7 undefined \u00B7 undefined\".)\nexport type AccountOption = {\n id: string\n name: string\n customer_id: string\n currency_code: string\n}\n\nexport type ItemFormProps = {\n mode: 'create' | 'edit'\n /** For create mode: full list of accounts the operator can attach to. */\n accountOptions?: AccountOption[]\n initial?: Partial<ItemFormValues>\n submitting?: boolean\n /** Returns the assembled payload (with rate_json built). */\n onSubmit: (assembled: AssembledItemPayload) => Promise<void> | void\n onCancel: () => void\n}\n\nexport type AssembledItemPayload = {\n billAccountId: string\n type: BillingItemType\n billStartDate: string\n billEndDate?: string\n description: string\n rateJson: Record<string, unknown>\n uomCode?: string\n subscriptionId?: string\n subscriptionItemId?: string\n isActive?: boolean\n}\n\nconst EMPTY_VALUES: ItemFormValues = {\n billAccountId: '',\n type: 'recurring',\n billStartDate: '',\n billEndDate: '',\n description: '',\n oneTimeAmount: '',\n recurringUnitPrice: '',\n usageMode: 'simple',\n usageUnitPrice: '',\n usageTieredJson: '',\n uomCode: '',\n subscriptionId: '',\n subscriptionItemId: '',\n isActive: true,\n}\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction mergeInitial(initial: Partial<ItemFormValues> | undefined): ItemFormValues {\n return {\n ...EMPTY_VALUES,\n billStartDate: EMPTY_VALUES.billStartDate || todayIso(),\n ...initial,\n }\n}\n\nfunction toNumber(raw: string): number | null {\n if (raw.trim() === '') return null\n const n = Number.parseFloat(raw)\n return Number.isFinite(n) ? n : null\n}\n\nexport function ItemForm({\n mode,\n accountOptions,\n initial,\n submitting = false,\n onSubmit,\n onCancel,\n}: ItemFormProps) {\n const t = useT()\n const [values, setValues] = React.useState<ItemFormValues>(() => mergeInitial(initial))\n const [error, setError] = React.useState<string | null>(null)\n const isEdit = mode === 'edit'\n\n const initialKey = React.useMemo(() => JSON.stringify(initial ?? null), [initial])\n React.useEffect(() => {\n setValues(mergeInitial(initial))\n }, [initialKey, initial])\n\n const set = React.useCallback(\n <K extends keyof ItemFormValues>(key: K, value: ItemFormValues[K]) => {\n setValues((prev) => ({ ...prev, [key]: value }))\n },\n [],\n )\n\n const buildRateJson = React.useCallback((): Record<string, unknown> | string => {\n switch (values.type) {\n case 'one_time': {\n const amount = toNumber(values.oneTimeAmount)\n if (amount == null) {\n return t('billing.items.form.error.amount_invalid', 'Amount must be a number')\n }\n return { amount }\n }\n case 'recurring': {\n const unit = toNumber(values.recurringUnitPrice)\n if (unit == null || unit < 0) {\n return t(\n 'billing.items.form.error.unit_price_invalid',\n 'Unit price must be a non-negative number',\n )\n }\n return { unit_price: unit }\n }\n case 'usage': {\n if (values.usageMode === 'simple') {\n const unit = toNumber(values.usageUnitPrice)\n if (unit == null || unit < 0) {\n return t(\n 'billing.items.form.error.unit_price_invalid',\n 'Unit price must be a non-negative number',\n )\n }\n return { unit_price: unit }\n }\n // Tiered: parse the textarea as JSON.\n const raw = values.usageTieredJson.trim()\n if (!raw) {\n return t(\n 'billing.items.form.error.tiered_required',\n 'Tiered rate JSON is required for tiered usage',\n )\n }\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n !('model' in parsed) ||\n !('tiers' in parsed)\n ) {\n return t(\n 'billing.items.form.error.tiered_shape',\n 'Tiered JSON must include `model` and `tiers`',\n )\n }\n return parsed\n } catch {\n return t(\n 'billing.items.form.error.tiered_json',\n 'Tiered rate JSON is not valid JSON',\n )\n }\n }\n }\n }, [\n t,\n values.oneTimeAmount,\n values.recurringUnitPrice,\n values.type,\n values.usageMode,\n values.usageTieredJson,\n values.usageUnitPrice,\n ])\n\n const handleSubmit = React.useCallback(async () => {\n if (!isEdit && !values.billAccountId.trim()) {\n setError(t('billing.items.form.error.account_required', 'Billing Account is required'))\n return\n }\n if (!values.description.trim()) {\n setError(t('billing.items.form.error.description_required', 'Description is required'))\n return\n }\n if (!values.billStartDate) {\n setError(t('billing.items.form.error.start_date_required', 'Bill start date is required'))\n return\n }\n if (values.type === 'usage' && !values.uomCode.trim()) {\n setError(t('billing.items.form.error.uom_required', 'UoM code is required for usage items'))\n return\n }\n const rateJsonResult = buildRateJson()\n if (typeof rateJsonResult === 'string') {\n // builder returned an error message\n setError(rateJsonResult)\n return\n }\n setError(null)\n\n const payload: AssembledItemPayload = {\n billAccountId: values.billAccountId,\n type: values.type,\n billStartDate: values.billStartDate,\n description: values.description.trim(),\n rateJson: rateJsonResult,\n }\n if (values.billEndDate) payload.billEndDate = values.billEndDate\n if (values.type === 'usage' && values.uomCode.trim()) {\n payload.uomCode = values.uomCode.trim()\n }\n if (values.subscriptionId.trim()) payload.subscriptionId = values.subscriptionId.trim()\n if (values.subscriptionItemId.trim()) {\n payload.subscriptionItemId = values.subscriptionItemId.trim()\n }\n if (isEdit) payload.isActive = values.isActive\n await onSubmit(payload)\n }, [buildRateJson, isEdit, onSubmit, t, values])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-6\" onKeyDown={handleKeyDown}>\n <div className=\"grid grid-cols-2 gap-4\">\n {!isEdit ? (\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.account', 'Billing Account')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Select\n value={values.billAccountId}\n onValueChange={(next) => set('billAccountId', next)}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('billing.items.form.account.placeholder', 'Select an account')} />\n </SelectTrigger>\n <SelectContent>\n {(accountOptions ?? []).map((opt) => (\n <SelectItem key={opt.id} value={opt.id}>\n {opt.name} \u00B7 {opt.customer_id} \u00B7 {opt.currency_code}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n ) : null}\n\n {!isEdit ? (\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.type', 'Type')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Select\n value={values.type}\n onValueChange={(next) => set('type', next as BillingItemType)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"one_time\">\n {t('billing.items.form.type.one_time', 'One-time charge')}\n </SelectItem>\n <SelectItem value=\"recurring\">\n {t('billing.items.form.type.recurring', 'Recurring (per cycle)')}\n </SelectItem>\n <SelectItem value=\"usage\">\n {t('billing.items.form.type.usage', 'Usage (metered)')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n ) : null}\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.start_date', 'Bill start date')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n type=\"date\"\n value={values.billStartDate}\n onChange={(event) => set('billStartDate', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.end_date', 'Bill end date (optional)')}\n </span>\n <Input\n type=\"date\"\n value={values.billEndDate}\n onChange={(event) => set('billEndDate', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.description', 'Description')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Textarea\n value={values.description}\n rows={2}\n onChange={(event) => set('description', event.currentTarget.value)}\n />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.description.help',\n 'Shown verbatim on the invoice line.',\n )}\n </span>\n </label>\n </div>\n\n <h3 className=\"text-sm font-semibold mt-6 mb-2\">\n {t('billing.items.form.rate.title', 'Rate')}\n </h3>\n <div className=\"grid grid-cols-2 gap-4\">\n {values.type === 'one_time' ? (\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.rate.amount', 'Amount')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n type=\"number\"\n step=\"0.0001\"\n value={values.oneTimeAmount}\n onChange={(event) => set('oneTimeAmount', event.currentTarget.value)}\n />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.rate.amount.help',\n 'Negative allowed for refunds / credits / proration credits.',\n )}\n </span>\n </label>\n ) : null}\n\n {values.type === 'recurring' ? (\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.rate.unit_price_recurring', 'Unit price per cycle')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n type=\"number\"\n step=\"0.0001\"\n value={values.recurringUnitPrice}\n onChange={(event) => set('recurringUnitPrice', event.currentTarget.value)}\n />\n </label>\n ) : null}\n\n {values.type === 'usage' ? (\n <>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.rate.usage_mode', 'Rate model')}\n </span>\n <Select\n value={values.usageMode}\n onValueChange={(next) => set('usageMode', next as UsageRateMode)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"simple\">\n {t('billing.items.form.rate.usage_mode.simple', 'Simple flat rate')}\n </SelectItem>\n <SelectItem value=\"tiered\">\n {t('billing.items.form.rate.usage_mode.tiered', 'Tiered (volume / graduated / flat)')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n\n {values.usageMode === 'simple' ? (\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.rate.unit_price_usage', 'Unit price per unit consumed')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n type=\"number\"\n step=\"0.000001\"\n value={values.usageUnitPrice}\n onChange={(event) => set('usageUnitPrice', event.currentTarget.value)}\n />\n </label>\n ) : (\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.rate.tiered_json', 'Tiered rate JSON')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Textarea\n rows={6}\n value={values.usageTieredJson}\n placeholder='{ \"model\": \"graduated\", \"tiers\": [{ \"up_to\": 10000, \"unit_price\": 0 }, { \"up_to\": null, \"unit_price\": 0.001 }] }'\n onChange={(event) => set('usageTieredJson', event.currentTarget.value)}\n />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.rate.tiered_json.help',\n 'Models: volume / graduated / flat. Exactly one tier must have `up_to: null` (the open-ended top tier). Server-side validators reject malformed shapes.',\n )}\n </span>\n </label>\n )}\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.uom_code', 'UoM code')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n value={values.uomCode}\n placeholder=\"api_request, gb, seat, \u2026\"\n onChange={(event) => set('uomCode', event.currentTarget.value)}\n />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.uom_code.help',\n 'Must exactly match the `uom_code` on usage records. No conversion.',\n )}\n </span>\n </label>\n </>\n ) : null}\n </div>\n\n <h3 className=\"text-sm font-semibold mt-6 mb-2\">\n {t('billing.items.form.refs.title', 'External references (optional)')}\n </h3>\n <div className=\"grid grid-cols-2 gap-4\">\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.subscription_id', 'Subscription ID')}\n </span>\n <Input\n value={values.subscriptionId}\n onChange={(event) => set('subscriptionId', event.currentTarget.value)}\n />\n </label>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.subscription_item_id', 'Subscription item ID')}\n </span>\n <Input\n value={values.subscriptionItemId}\n onChange={(event) => set('subscriptionItemId', event.currentTarget.value)}\n />\n </label>\n </div>\n\n {isEdit ? (\n <div className=\"mt-6 flex items-center justify-between gap-3\">\n <div>\n <div className=\"text-sm font-medium\">\n {t('billing.items.form.active', 'Active')}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.active.help',\n 'Inactive items are skipped by the Bill Run engine.',\n )}\n </div>\n </div>\n <Switch\n checked={values.isActive}\n onCheckedChange={(next) => set('isActive', next)}\n />\n </div>\n ) : null}\n\n {error ? <div className=\"mt-4 text-sm text-destructive\">{error}</div> : null}\n\n <div className=\"mt-6 flex items-center justify-between gap-3\">\n <span className=\"text-xs text-muted-foreground\">\n <KbdShortcut keys={['Ctrl/\u2318', 'Enter']} />{' '}\n {t('billing.items.form.submit_hint', 'to save,')} <Kbd>Esc</Kbd>{' '}\n {t('billing.items.form.cancel_hint', 'to discard')}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onCancel}>\n {t('billing.common.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={() => void handleSubmit()} disabled={submitting}>\n {submitting\n ? t('billing.items.form.saving', 'Saving\u2026')\n : isEdit\n ? t('billing.items.form.save', 'Save changes')\n : t('billing.items.form.create', 'Create item')}\n </Button>\n </div>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Textarea } from '@open-mercato/ui/primitives/textarea'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { Kbd, KbdShortcut } from '@open-mercato/ui/primitives/kbd'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport Link from 'next/link'\nimport { SegmentedControl, SegmentedControlItem } from '@open-mercato/ui/primitives/segmented-control'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { ItemPriceField } from './ItemPriceField'\nimport { BILLING_SETTINGS_HREF } from './BillingSettingsLink'\nimport { counterpartAmount, type PriceBasis } from '../lib/lineAmounts'\n\n/**\n * Shared form for Billing Item create + edit.\n *\n * The `rate_json` field is type-discriminated per the spec's\n * \"Rate Formats\" section:\n * - `one_time`: `{ amount }` \u2014 one number.\n * - `recurring`: `{ unit_price }` \u2014 one number.\n * - `usage` (simple): `{ unit_price }` + required `uomCode`.\n * - `usage` (tiered, advanced): operator pastes raw JSON\n * `{ model, tiers: [...] }` into the textarea \u2014 for occasional\n * hand-built tiered rates. The integration flow uses CPQ \u2192\n * connector to land tiered rates without UI input.\n *\n * `type` is immutable on edit (per `billingItemUpdateSchema`); we\n * hide the `type` selector in `mode='edit'` so it can't even be\n * surfaced.\n */\n\ntype BillingItemType = 'one_time' | 'recurring' | 'usage'\ntype UsageRateMode = 'simple' | 'tiered'\n\nexport type ItemFormValues = {\n billAccountId: string\n type: BillingItemType\n billStartDate: string\n billEndDate: string\n description: string\n // Rate is type-dependent \u2014 we keep the inputs separate and assemble\n // `rate_json` on submit.\n oneTimeAmount: string\n recurringUnitPrice: string\n usageMode: UsageRateMode\n usageUnitPrice: string\n usageTieredJson: string\n uomCode: string\n subscriptionId: string\n subscriptionItemId: string\n isActive: boolean\n // Spec A \u2014 recurring only.\n chargeTiming: 'in_advance' | 'in_arrears'\n prorate: boolean\n // Spec B \u2014 VAT %. Empty on create \u2192 the organization default.\n taxRate: string\n // Spec F (F-D2) \u2014 the basis the price inputs are entered on.\n priceBasis: PriceBasis\n}\n\n// The accounts list API (`/api/billing/accounts`) returns snake_case\n// rows \u2014 mirror that here so the picker reads real values. (camelCase\n// resolved to `undefined`, rendering \"Name \u00B7 undefined \u00B7 undefined\".)\nexport type AccountOption = {\n id: string\n name: string\n customer_id: string\n currency_code: string\n billing_mode?: 'postpaid' | 'prepaid'\n}\n\nexport type ItemFormProps = {\n mode: 'create' | 'edit'\n /** For create mode: full list of accounts the operator can attach to. */\n accountOptions?: AccountOption[]\n initial?: Partial<ItemFormValues>\n /** Edit mode: the item's account mode (prepaid tax-point rules, Spec F). */\n accountBillingMode?: 'postpaid' | 'prepaid'\n /** Edit mode: the item's account currency, shown next to the rate (Spec F, F2). */\n accountCurrency?: string\n submitting?: boolean\n /** Returns the assembled payload (with rate_json built). */\n onSubmit: (assembled: AssembledItemPayload) => Promise<void> | void\n onCancel: () => void\n}\n\nexport type AssembledItemPayload = {\n billAccountId: string\n type: BillingItemType\n billStartDate: string\n billEndDate?: string\n description: string\n rateJson: Record<string, unknown>\n uomCode?: string\n subscriptionId?: string\n subscriptionItemId?: string\n isActive?: boolean\n chargeTiming?: 'in_advance' | 'in_arrears'\n prorate?: boolean\n taxRate?: number\n priceBasis?: PriceBasis\n}\n\nconst EMPTY_VALUES: ItemFormValues = {\n billAccountId: '',\n type: 'recurring',\n billStartDate: '',\n billEndDate: '',\n description: '',\n oneTimeAmount: '',\n recurringUnitPrice: '',\n usageMode: 'simple',\n usageUnitPrice: '',\n usageTieredJson: '',\n uomCode: '',\n subscriptionId: '',\n subscriptionItemId: '',\n isActive: true,\n chargeTiming: 'in_advance',\n prorate: true,\n taxRate: '',\n priceBasis: 'net',\n}\n\nfunction todayIso(): string {\n return new Date().toISOString().slice(0, 10)\n}\n\nfunction mergeInitial(initial: Partial<ItemFormValues> | undefined): ItemFormValues {\n return {\n ...EMPTY_VALUES,\n billStartDate: EMPTY_VALUES.billStartDate || todayIso(),\n ...initial,\n }\n}\n\nfunction toNumber(raw: string): number | null {\n if (raw.trim() === '') return null\n const n = Number.parseFloat(raw)\n return Number.isFinite(n) ? n : null\n}\n\nexport function ItemForm({\n mode,\n accountOptions,\n initial,\n accountBillingMode,\n accountCurrency,\n submitting = false,\n onSubmit,\n onCancel,\n}: ItemFormProps) {\n const t = useT()\n const [values, setValues] = React.useState<ItemFormValues>(() => mergeInitial(initial))\n const selectedAccount = accountOptions?.find((option) => option.id === values.billAccountId)\n const isPrepaidAccount = accountBillingMode === 'prepaid' || selectedAccount?.billing_mode === 'prepaid'\n // Read-only: the currency always comes from the parent account.\n const currencyCode = accountCurrency ?? selectedAccount?.currency_code ?? ''\n const [error, setError] = React.useState<string | null>(null)\n const isEdit = mode === 'edit'\n\n const initialKey = React.useMemo(() => JSON.stringify(initial ?? null), [initial])\n React.useEffect(() => {\n setValues(mergeInitial(initial))\n }, [initialKey, initial])\n\n // Spec B: prefill the VAT rate with the organization default on create.\n // Spec F (F-D3): the prepaid tax point decides whether VAT applies to\n // prepaid charges at all. Best effort \u2014 without settings access the server\n // applies the default rate and the form assumes the `topup` default.\n const [prepaidTaxPoint, setPrepaidTaxPoint] = React.useState<'topup' | 'consumption'>('topup')\n React.useEffect(() => {\n let cancelled = false\n void apiCall<{ settings?: { defaultTaxRate?: number; prepaidTaxPoint?: 'topup' | 'consumption' } }>(\n '/api/billing/settings',\n ).then((call) => {\n if (cancelled || !call.ok) return\n const settings = call.result?.settings\n if (settings?.prepaidTaxPoint === 'consumption') setPrepaidTaxPoint('consumption')\n const rate = settings?.defaultTaxRate\n if (!isEdit && typeof rate === 'number') {\n setValues((prev) => (prev.taxRate === '' ? { ...prev, taxRate: String(rate) } : prev))\n }\n })\n return () => {\n cancelled = true\n }\n }, [isEdit])\n\n // Prepaid + tax at top-up: VAT was charged on the receipt, so the item\n // price is debited as entered \u2014 the VAT rate and basis do not apply.\n const taxLocked = isPrepaidAccount && prepaidTaxPoint === 'topup'\n // Tiered usage rates are always net (P-D4).\n const basisForcedNet = values.type === 'usage' && values.usageMode === 'tiered'\n const effectiveBasis: PriceBasis = basisForcedNet ? 'net' : values.priceBasis\n\n // Switching the basis converts the typed prices, so the price stays the same.\n const handleBasisChange = React.useCallback((next: string) => {\n const target: PriceBasis = next === 'gross' ? 'gross' : 'net'\n setValues((prev) => {\n if (prev.priceBasis === target) return prev\n const rate = toNumber(prev.taxRate) ?? 0\n const convert = (raw: string, decimals: number): string => {\n const amount = toNumber(raw)\n if (amount === null) return raw\n const converted =\n decimals > 2\n ? prev.priceBasis === 'gross'\n ? amount / (1 + rate / 100)\n : amount * (1 + rate / 100)\n : counterpartAmount(amount, prev.priceBasis, rate)\n return String(Number(converted.toFixed(decimals)))\n }\n return {\n ...prev,\n priceBasis: target,\n oneTimeAmount: convert(prev.oneTimeAmount, 2),\n recurringUnitPrice: convert(prev.recurringUnitPrice, 2),\n usageUnitPrice: convert(prev.usageUnitPrice, 6),\n }\n })\n }, [])\n\n const set = React.useCallback(\n <K extends keyof ItemFormValues>(key: K, value: ItemFormValues[K]) => {\n setValues((prev) => ({ ...prev, [key]: value }))\n },\n [],\n )\n\n const buildRateJson = React.useCallback((): Record<string, unknown> | string => {\n switch (values.type) {\n case 'one_time': {\n const amount = toNumber(values.oneTimeAmount)\n if (amount == null) {\n return t('billing.items.form.error.amount_invalid', 'Amount must be a number')\n }\n return { amount }\n }\n case 'recurring': {\n const unit = toNumber(values.recurringUnitPrice)\n if (unit == null || unit < 0) {\n return t(\n 'billing.items.form.error.unit_price_invalid',\n 'Unit price must be a non-negative number',\n )\n }\n return { unit_price: unit }\n }\n case 'usage': {\n if (values.usageMode === 'simple') {\n const unit = toNumber(values.usageUnitPrice)\n if (unit == null || unit < 0) {\n return t(\n 'billing.items.form.error.unit_price_invalid',\n 'Unit price must be a non-negative number',\n )\n }\n return { unit_price: unit }\n }\n // Tiered: parse the textarea as JSON.\n const raw = values.usageTieredJson.trim()\n if (!raw) {\n return t(\n 'billing.items.form.error.tiered_required',\n 'Tiered rate JSON is required for tiered usage',\n )\n }\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>\n if (\n typeof parsed !== 'object' ||\n parsed === null ||\n !('model' in parsed) ||\n !('tiers' in parsed)\n ) {\n return t(\n 'billing.items.form.error.tiered_shape',\n 'Tiered JSON must include `model` and `tiers`',\n )\n }\n return parsed\n } catch {\n return t(\n 'billing.items.form.error.tiered_json',\n 'Tiered rate JSON is not valid JSON',\n )\n }\n }\n }\n }, [\n t,\n values.oneTimeAmount,\n values.recurringUnitPrice,\n values.type,\n values.usageMode,\n values.usageTieredJson,\n values.usageUnitPrice,\n ])\n\n const handleSubmit = React.useCallback(async () => {\n if (!isEdit && !values.billAccountId.trim()) {\n setError(t('billing.items.form.error.account_required', 'Billing Account is required'))\n return\n }\n if (!values.description.trim()) {\n setError(t('billing.items.form.error.description_required', 'Description is required'))\n return\n }\n if (!values.billStartDate) {\n setError(t('billing.items.form.error.start_date_required', 'Bill start date is required'))\n return\n }\n if (values.type === 'usage' && !values.uomCode.trim()) {\n setError(t('billing.items.form.error.uom_required', 'UoM code is required for usage items'))\n return\n }\n const taxRate = toNumber(values.taxRate)\n if (values.taxRate.trim() !== '' && (taxRate == null || taxRate < 0 || taxRate > 100)) {\n setError(t('billing.items.form.error.tax_rate_invalid', 'VAT rate must be between 0 and 100'))\n return\n }\n const rateJsonResult = buildRateJson()\n if (typeof rateJsonResult === 'string') {\n // builder returned an error message\n setError(rateJsonResult)\n return\n }\n setError(null)\n\n const payload: AssembledItemPayload = {\n billAccountId: values.billAccountId,\n type: values.type,\n billStartDate: values.billStartDate,\n description: values.description.trim(),\n rateJson: rateJsonResult,\n }\n if (values.billEndDate) payload.billEndDate = values.billEndDate\n if (values.type === 'usage' && values.uomCode.trim()) {\n payload.uomCode = values.uomCode.trim()\n }\n if (values.subscriptionId.trim()) payload.subscriptionId = values.subscriptionId.trim()\n if (values.subscriptionItemId.trim()) {\n payload.subscriptionItemId = values.subscriptionItemId.trim()\n }\n if (values.type === 'recurring') {\n payload.chargeTiming = values.chargeTiming\n payload.prorate = values.prorate\n }\n if (taxRate != null) payload.taxRate = taxRate\n payload.priceBasis = effectiveBasis\n if (isEdit) payload.isActive = values.isActive\n await onSubmit(payload)\n }, [buildRateJson, effectiveBasis, isEdit, onSubmit, t, values])\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n } else if (event.key === 'Escape') {\n event.preventDefault()\n onCancel()\n }\n },\n [handleSubmit, onCancel],\n )\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-6\" onKeyDown={handleKeyDown}>\n <div className=\"grid grid-cols-2 gap-4\">\n {!isEdit ? (\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.account', 'Billing Account')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Select\n value={values.billAccountId}\n onValueChange={(next) => set('billAccountId', next)}\n >\n <SelectTrigger>\n <SelectValue placeholder={t('billing.items.form.account.placeholder', 'Select an account')} />\n </SelectTrigger>\n <SelectContent>\n {(accountOptions ?? []).map((opt) => (\n <SelectItem key={opt.id} value={opt.id}>\n {opt.name} \u00B7 {opt.customer_id} \u00B7 {opt.currency_code}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </label>\n ) : null}\n\n {!isEdit ? (\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.type', 'Type')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Select\n value={values.type}\n onValueChange={(next) => set('type', next as BillingItemType)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"one_time\">\n {t('billing.items.form.type.one_time', 'One-time charge')}\n </SelectItem>\n <SelectItem value=\"recurring\">\n {t('billing.items.form.type.recurring', 'Recurring (per cycle)')}\n </SelectItem>\n <SelectItem value=\"usage\">\n {t('billing.items.form.type.usage', 'Usage (metered)')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n ) : null}\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.start_date', 'Bill start date')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n type=\"date\"\n value={values.billStartDate}\n onChange={(event) => set('billStartDate', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.end_date', 'Bill end date (optional)')}\n </span>\n <Input\n type=\"date\"\n value={values.billEndDate}\n onChange={(event) => set('billEndDate', event.currentTarget.value)}\n />\n </label>\n\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.description', 'Description')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Textarea\n value={values.description}\n rows={2}\n onChange={(event) => set('description', event.currentTarget.value)}\n />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.description.help',\n 'Shown verbatim on the invoice line.',\n )}\n </span>\n </label>\n </div>\n\n <div className=\"mt-6 mb-2 flex flex-wrap items-center justify-between gap-3\">\n <h3 className=\"text-sm font-semibold\">{t('billing.items.form.rate.title', 'Rate')}</h3>\n <SegmentedControl\n value={effectiveBasis}\n onValueChange={handleBasisChange}\n disabled={taxLocked || basisForcedNet}\n aria-label={t('billing.items.form.price_basis', 'Price entered as')}\n >\n <SegmentedControlItem value=\"net\">{t('billing.items.price_basis.net', 'Net')}</SegmentedControlItem>\n <SegmentedControlItem value=\"gross\">{t('billing.items.price_basis.gross', 'Gross')}</SegmentedControlItem>\n </SegmentedControl>\n </div>\n {basisForcedNet && !taxLocked ? (\n <p className=\"mb-2 text-xs text-muted-foreground\">\n {t('billing.items.form.price_basis.tiered_net', 'Tiered rates are always entered net.')}\n </p>\n ) : null}\n <div className=\"grid grid-cols-2 gap-4\">\n {values.type === 'one_time' ? (\n <ItemPriceField\n label={t('billing.items.form.rate.amount', 'Amount')}\n value={values.oneTimeAmount}\n onChange={(next) => set('oneTimeAmount', next)}\n step=\"0.01\"\n currencyCode={currencyCode}\n priceBasis={effectiveBasis}\n taxRate={values.taxRate}\n showCounterpart={!taxLocked}\n help={t('billing.items.form.rate.amount.help', 'Negative allowed for refunds / credits / proration credits.')}\n />\n ) : null}\n\n {values.type === 'recurring' ? (\n <ItemPriceField\n label={t('billing.items.form.rate.unit_price_recurring', 'Unit price per cycle')}\n value={values.recurringUnitPrice}\n onChange={(next) => set('recurringUnitPrice', next)}\n step=\"0.01\"\n currencyCode={currencyCode}\n priceBasis={effectiveBasis}\n taxRate={values.taxRate}\n showCounterpart={!taxLocked}\n />\n ) : null}\n\n {values.type === 'usage' && values.usageMode === 'simple' ? (\n <ItemPriceField\n label={t('billing.items.form.rate.unit_price_usage', 'Unit price per unit consumed')}\n value={values.usageUnitPrice}\n onChange={(next) => set('usageUnitPrice', next)}\n step=\"0.000001\"\n currencyCode={currencyCode}\n priceBasis={effectiveBasis}\n taxRate={values.taxRate}\n showCounterpart={!taxLocked}\n precision={6}\n />\n ) : null}\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">{t('billing.items.form.tax_rate', 'VAT rate (%)')}</span>\n <Input\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"0.01\"\n value={values.taxRate}\n disabled={taxLocked}\n onChange={(event) => set('taxRate', event.currentTarget.value)}\n />\n <span className=\"text-xs text-muted-foreground\">\n {taxLocked ? (\n <>\n {t('billing.items.form.tax_rate.prepaid_topup', 'For prepaid accounts, tax is allocated at top-up.')}{' '}\n <Link href={BILLING_SETTINGS_HREF} className=\"text-primary hover:underline\">\n {t('billing.settings.link', 'Billing settings')}\n </Link>\n </>\n ) : isPrepaidAccount ? (\n t('billing.items.form.tax_rate.prepaid_consumption', 'Charged from the balance including VAT.')\n ) : isEdit ? (\n t('billing.items.form.tax_rate.help_edit', 'Applies to future invoices; posted invoices keep their rate.')\n ) : (\n t('billing.items.form.tax_rate.help', 'Defaults to the organization VAT rate from billing settings.')\n )}\n </span>\n </label>\n\n {values.type === 'recurring' ? (\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.charge_timing', 'Charge timing')}\n </span>\n <Select\n value={values.chargeTiming}\n onValueChange={(next) => set('chargeTiming', next === 'in_arrears' ? 'in_arrears' : 'in_advance')}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"in_advance\">\n {t('billing.items.charge_timing.in_advance', 'In advance')}\n </SelectItem>\n <SelectItem value=\"in_arrears\">\n {t('billing.items.charge_timing.in_arrears', 'In arrears')}\n </SelectItem>\n </SelectContent>\n </Select>\n <span className=\"text-xs text-muted-foreground\">\n {values.chargeTiming === 'in_arrears'\n ? t('billing.items.form.charge_timing.help_arrears', 'Each bill date charges the period that just ended.')\n : t('billing.items.form.charge_timing.help_advance', 'Each bill date charges the coming period.')}\n </span>\n </label>\n ) : null}\n\n {values.type === 'recurring' ? (\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <div>\n <div className=\"font-medium\">\n {t('billing.items.form.prorate', 'Prorate partial periods')}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.prorate.help',\n 'Charge by calendar days when the item starts or ends mid-period, and credit unused days if it ends after being billed in advance.',\n )}\n </div>\n </div>\n <Switch checked={values.prorate} onCheckedChange={(next) => set('prorate', next)} />\n </div>\n ) : null}\n\n {values.type === 'usage' ? (\n <>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.rate.usage_mode', 'Rate model')}\n </span>\n <Select\n value={values.usageMode}\n onValueChange={(next) => set('usageMode', next as UsageRateMode)}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"simple\">\n {t('billing.items.form.rate.usage_mode.simple', 'Simple flat rate')}\n </SelectItem>\n <SelectItem value=\"tiered\">\n {t('billing.items.form.rate.usage_mode.tiered', 'Tiered (volume / graduated / flat)')}\n </SelectItem>\n </SelectContent>\n </Select>\n </label>\n\n {values.usageMode === 'tiered' ? (\n <label className=\"flex flex-col gap-1 text-sm col-span-2\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.rate.tiered_json', 'Tiered rate JSON')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Textarea\n rows={6}\n value={values.usageTieredJson}\n placeholder='{ \"model\": \"graduated\", \"tiers\": [{ \"up_to\": 10000, \"unit_price\": 0 }, { \"up_to\": null, \"unit_price\": 0.001 }] }'\n onChange={(event) => set('usageTieredJson', event.currentTarget.value)}\n />\n {currencyCode ? (\n <span className=\"text-xs text-muted-foreground\">\n {t('billing.items.form.rate.prices_in', 'Prices in')} <span className=\"font-medium\">{currencyCode}</span>\n </span>\n ) : null}\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.rate.tiered_json.help',\n 'Models: volume / graduated / flat. Exactly one tier must have `up_to: null` (the open-ended top tier). Server-side validators reject malformed shapes.',\n )}\n </span>\n </label>\n ) : null}\n\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.uom_code', 'UoM code')}\n <span className=\"text-destructive\"> *</span>\n </span>\n <Input\n value={values.uomCode}\n placeholder=\"api_request, gb, seat, \u2026\"\n onChange={(event) => set('uomCode', event.currentTarget.value)}\n />\n <span className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.uom_code.help',\n 'Must exactly match the `uom_code` on usage records. No conversion.',\n )}\n </span>\n </label>\n </>\n ) : null}\n </div>\n\n <h3 className=\"text-sm font-semibold mt-6 mb-2\">\n {t('billing.items.form.refs.title', 'External references (optional)')}\n </h3>\n <div className=\"grid grid-cols-2 gap-4\">\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.subscription_id', 'Subscription ID')}\n </span>\n <Input\n value={values.subscriptionId}\n onChange={(event) => set('subscriptionId', event.currentTarget.value)}\n />\n </label>\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {t('billing.items.form.subscription_item_id', 'Subscription item ID')}\n </span>\n <Input\n value={values.subscriptionItemId}\n onChange={(event) => set('subscriptionItemId', event.currentTarget.value)}\n />\n </label>\n </div>\n\n {isEdit ? (\n <div className=\"mt-6 flex items-center justify-between gap-3\">\n <div>\n <div className=\"text-sm font-medium\">\n {t('billing.items.form.active', 'Active')}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {t(\n 'billing.items.form.active.help',\n 'Inactive items are skipped by the Bill Run engine.',\n )}\n </div>\n </div>\n <Switch\n checked={values.isActive}\n onCheckedChange={(next) => set('isActive', next)}\n />\n </div>\n ) : null}\n\n {error ? <div className=\"mt-4 text-sm text-destructive\">{error}</div> : null}\n\n <div className=\"mt-6 flex items-center justify-between gap-3\">\n <span className=\"text-xs text-muted-foreground\">\n <KbdShortcut keys={['Ctrl/\u2318', 'Enter']} />{' '}\n {t('billing.items.form.submit_hint', 'to save,')} <Kbd>Esc</Kbd>{' '}\n {t('billing.items.form.cancel_hint', 'to discard')}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button type=\"button\" variant=\"outline\" onClick={onCancel}>\n {t('billing.common.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" onClick={() => void handleSubmit()} disabled={submitting}>\n {submitting\n ? t('billing.items.form.saving', 'Saving\u2026')\n : isEdit\n ? t('billing.items.form.save', 'Save changes')\n : t('billing.items.form.create', 'Create item')}\n </Button>\n </div>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,46 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
+ import { MoneyInput } from "./MoneyInput.js";
5
+ import { counterpartAmount } from "../lib/lineAmounts.js";
6
+ function parse(raw) {
7
+ if (raw.trim() === "") return null;
8
+ const n = Number.parseFloat(raw);
9
+ return Number.isFinite(n) ? n : null;
10
+ }
11
+ function ItemPriceField({
12
+ label,
13
+ value,
14
+ onChange,
15
+ step,
16
+ currencyCode,
17
+ priceBasis,
18
+ taxRate,
19
+ showCounterpart,
20
+ precision = 2,
21
+ help
22
+ }) {
23
+ const t = useT();
24
+ const amount = parse(value);
25
+ const rate = parse(taxRate) ?? 0;
26
+ const counterpart = showCounterpart && amount !== null ? precision > 2 ? priceBasis === "gross" ? amount / (1 + rate / 100) : amount * (1 + rate / 100) : counterpartAmount(amount, priceBasis, rate) : null;
27
+ return /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
28
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
29
+ label,
30
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: " *" })
31
+ ] }),
32
+ /* @__PURE__ */ jsx(MoneyInput, { currencyCode, step, value, onChange: (event) => onChange(event.currentTarget.value) }),
33
+ counterpart !== null ? /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
34
+ priceBasis === "gross" ? t("billing.items.form.price.net_preview", "Net") : t("billing.items.form.price.gross_preview", "Gross"),
35
+ " ",
36
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: counterpart.toFixed(precision) }),
37
+ currencyCode ? ` ${currencyCode}` : "",
38
+ ` \xB7 ${t("billing.items.form.price.vat_preview", "VAT")} ${rate} %`
39
+ ] }) : null,
40
+ help ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: help }) : null
41
+ ] });
42
+ }
43
+ export {
44
+ ItemPriceField
45
+ };
46
+ //# sourceMappingURL=ItemPriceField.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/billing/components/ItemPriceField.tsx"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { MoneyInput } from './MoneyInput'\nimport { counterpartAmount, type PriceBasis } from '../lib/lineAmounts'\n\nexport type ItemPriceFieldProps = {\n label: string\n value: string\n onChange: (next: string) => void\n step: string\n currencyCode: string\n priceBasis: PriceBasis\n /** VAT rate in percent as typed in the form ('' = unknown). */\n taxRate: string\n /** False when VAT does not apply to this price (prepaid, tax at top-up). */\n showCounterpart: boolean\n /**\n * Decimals of the preview. 2 = the line rule (`lineVat`). Higher values are\n * for per-unit usage prices, where 2 dp would read as 0.00; those are\n * indicative only \u2014 the invoice rounds the extended line once.\n */\n precision?: number\n help?: React.ReactNode\n}\n\nfunction parse(raw: string): number | null {\n if (raw.trim() === '') return null\n const n = Number.parseFloat(raw)\n return Number.isFinite(n) ? n : null\n}\n\n/**\n * One item price input (Spec F, F2 + F3): the currency suffix from the\n * account, and below it the other side of the price, computed with the same\n * `lineVat` rule the invoice uses (\"Gross 60.27 EUR\" / \"Net 49.00 EUR\").\n */\nexport function ItemPriceField({\n label,\n value,\n onChange,\n step,\n currencyCode,\n priceBasis,\n taxRate,\n showCounterpart,\n precision = 2,\n help,\n}: ItemPriceFieldProps) {\n const t = useT()\n const amount = parse(value)\n const rate = parse(taxRate) ?? 0\n const counterpart =\n showCounterpart && amount !== null\n ? precision > 2\n ? priceBasis === 'gross'\n ? amount / (1 + rate / 100)\n : amount * (1 + rate / 100)\n : counterpartAmount(amount, priceBasis, rate)\n : null\n return (\n <label className=\"flex flex-col gap-1 text-sm\">\n <span className=\"text-muted-foreground\">\n {label}\n <span className=\"text-destructive\"> *</span>\n </span>\n <MoneyInput currencyCode={currencyCode} step={step} value={value} onChange={(event) => onChange(event.currentTarget.value)} />\n {counterpart !== null ? (\n <span className=\"text-xs text-muted-foreground tabular-nums\">\n {priceBasis === 'gross'\n ? t('billing.items.form.price.net_preview', 'Net')\n : t('billing.items.form.price.gross_preview', 'Gross')}{' '}\n <span className=\"font-medium text-foreground\">{counterpart.toFixed(precision)}</span>\n {currencyCode ? ` ${currencyCode}` : ''}\n {` \u00B7 ${t('billing.items.form.price.vat_preview', 'VAT')} ${rate} %`}\n </span>\n ) : null}\n {help ? <span className=\"text-xs text-muted-foreground\">{help}</span> : null}\n </label>\n )\n}\n"],
5
+ "mappings": ";AA8DM,SAEE,KAFF;AA5DN,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAC3B,SAAS,yBAA0C;AAsBnD,SAAS,MAAM,KAA4B;AACzC,MAAI,IAAI,KAAK,MAAM,GAAI,QAAO;AAC9B,QAAM,IAAI,OAAO,WAAW,GAAG;AAC/B,SAAO,OAAO,SAAS,CAAC,IAAI,IAAI;AAClC;AAOO,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AACF,GAAwB;AACtB,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,MAAM,KAAK;AAC1B,QAAM,OAAO,MAAM,OAAO,KAAK;AAC/B,QAAM,cACJ,mBAAmB,WAAW,OAC1B,YAAY,IACV,eAAe,UACb,UAAU,IAAI,OAAO,OACrB,UAAU,IAAI,OAAO,OACvB,kBAAkB,QAAQ,YAAY,IAAI,IAC5C;AACN,SACE,qBAAC,WAAM,WAAU,+BACf;AAAA,yBAAC,UAAK,WAAU,yBACb;AAAA;AAAA,MACD,oBAAC,UAAK,WAAU,oBAAmB,gBAAE;AAAA,OACvC;AAAA,IACA,oBAAC,cAAW,cAA4B,MAAY,OAAc,UAAU,CAAC,UAAU,SAAS,MAAM,cAAc,KAAK,GAAG;AAAA,IAC3H,gBAAgB,OACf,qBAAC,UAAK,WAAU,8CACb;AAAA,qBAAe,UACZ,EAAE,wCAAwC,KAAK,IAC/C,EAAE,0CAA0C,OAAO;AAAA,MAAG;AAAA,MAC1D,oBAAC,UAAK,WAAU,+BAA+B,sBAAY,QAAQ,SAAS,GAAE;AAAA,MAC7E,eAAe,IAAI,YAAY,KAAK;AAAA,MACpC,SAAM,EAAE,wCAAwC,KAAK,CAAC,IAAI,IAAI;AAAA,OACjE,IACE;AAAA,IACH,OAAO,oBAAC,UAAK,WAAU,iCAAiC,gBAAK,IAAU;AAAA,KAC1E;AAEJ;",
6
+ "names": []
7
+ }
@@ -38,6 +38,7 @@ function LineFormDialog({
38
38
  const [totalOverride, setTotalOverride] = React.useState(
39
39
  initial?.totalNetAmount != null ? String(initial.totalNetAmount) : ""
40
40
  );
41
+ const [taxRate, setTaxRate] = React.useState(initial?.taxRate != null ? String(initial.taxRate) : "");
41
42
  const [error, setError] = React.useState(null);
42
43
  React.useEffect(() => {
43
44
  if (open) {
@@ -45,6 +46,7 @@ function LineFormDialog({
45
46
  setQuantity(initial?.quantity != null ? String(initial.quantity) : "");
46
47
  setUnitPriceNet(initial?.unitPriceNet != null ? String(initial.unitPriceNet) : "");
47
48
  setTotalOverride(initial?.totalNetAmount != null ? String(initial.totalNetAmount) : "");
49
+ setTaxRate(initial?.taxRate != null ? String(initial.taxRate) : "");
48
50
  setError(null);
49
51
  }
50
52
  }, [open, initial]);
@@ -74,14 +76,20 @@ function LineFormDialog({
74
76
  }
75
77
  totalOverrideValue = parsed;
76
78
  }
79
+ const taxRateValue = toNumber(taxRate);
80
+ if (taxRate.trim() !== "" && (taxRateValue == null || taxRateValue < 0 || taxRateValue > 100)) {
81
+ setError(t("billing.invoices.lines.form.error.tax_rate_invalid", "VAT rate must be between 0 and 100"));
82
+ return;
83
+ }
77
84
  setError(null);
78
85
  await onSubmit({
79
86
  description: trimmedDescription,
80
87
  quantity: quantityValue,
81
88
  unitPriceNet: unitPriceValue,
82
- totalNetAmount: totalOverrideValue
89
+ totalNetAmount: totalOverrideValue,
90
+ taxRate: taxRateValue ?? void 0
83
91
  });
84
- }, [description, onSubmit, quantity, t, totalOverride, unitPriceNet]);
92
+ }, [description, onSubmit, quantity, t, taxRate, totalOverride, unitPriceNet]);
85
93
  const handleKeyDown = React.useCallback(
86
94
  (event) => {
87
95
  if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
@@ -145,6 +153,21 @@ function LineFormDialog({
145
153
  )
146
154
  ] })
147
155
  ] }),
156
+ /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
157
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.invoices.lines.form.tax_rate", "VAT rate (%)") }),
158
+ /* @__PURE__ */ jsx(
159
+ Input,
160
+ {
161
+ type: "number",
162
+ value: taxRate,
163
+ min: "0",
164
+ max: "100",
165
+ step: "0.01",
166
+ placeholder: mode === "add" ? t("billing.invoices.lines.form.tax_rate.default", "Organization default") : "",
167
+ onChange: (event) => setTaxRate(event.currentTarget.value)
168
+ }
169
+ )
170
+ ] }),
148
171
  /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-sm", children: [
149
172
  /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("billing.invoices.lines.form.total_override", "Total override (optional)") }),
150
173
  /* @__PURE__ */ jsx(