shutro-erp 0.2.0 → 0.3.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 (232) hide show
  1. package/README.md +17 -10
  2. package/dist/account_portal/.kosh/bindings.d.ts +22 -0
  3. package/dist/account_portal/.kosh/bindings.js +15 -0
  4. package/dist/account_portal/.kosh/schema.d.ts +1350 -0
  5. package/dist/account_portal/index.d.ts +3 -0
  6. package/dist/account_portal/index.js +73 -0
  7. package/dist/account_portal/module.d.ts +5983 -0
  8. package/dist/account_portal/module.js +18 -0
  9. package/dist/accounting/.kosh/bindings.js +1 -1
  10. package/dist/accounting/.kosh/schema.d.ts +142 -9
  11. package/dist/accounting/chart.d.ts +40 -8
  12. package/dist/accounting/chart.js +124 -30
  13. package/dist/accounting/closing.d.ts +6 -1
  14. package/dist/accounting/closing.js +5 -1
  15. package/dist/accounting/demo.js +6 -5
  16. package/dist/accounting/index.d.ts +3 -3
  17. package/dist/accounting/index.js +200 -23
  18. package/dist/accounting/module.d.ts +3326 -1086
  19. package/dist/accounting/module.js +408 -66
  20. package/dist/accounting/payments.d.ts +25 -0
  21. package/dist/accounting/payments.js +96 -4
  22. package/dist/accounting/web/RegisterPayment.d.ts +3 -2
  23. package/dist/accounting/web/RegisterPayment.js +39 -6
  24. package/dist/accounting/web/SettingsPage.js +5 -1
  25. package/dist/accounting/web/messages.js +10 -0
  26. package/dist/cli/index.d.ts +1 -1
  27. package/dist/cli/index.js +1 -1
  28. package/dist/cli/init.d.ts +5 -0
  29. package/dist/cli/init.js +195 -53
  30. package/dist/cli/launch.d.ts +16 -0
  31. package/dist/cli/launch.js +138 -0
  32. package/dist/cli/prompt.d.ts +35 -0
  33. package/dist/cli/prompt.js +46 -0
  34. package/dist/cli/templates.d.ts +11 -3
  35. package/dist/cli/templates.js +32 -7
  36. package/dist/cli/tui.d.ts +7 -0
  37. package/dist/cli/tui.js +254 -0
  38. package/dist/cli/ui.d.ts +25 -5
  39. package/dist/cli/ui.js +49 -9
  40. package/dist/contacts/.kosh/bindings.js +1 -1
  41. package/dist/contacts/.kosh/schema.d.ts +3 -1
  42. package/dist/contacts/index.js +24 -1
  43. package/dist/contacts/module.d.ts +42 -0
  44. package/dist/contacts/module.js +3 -1
  45. package/dist/import/convert.d.ts +55 -0
  46. package/dist/import/convert.js +224 -0
  47. package/dist/import/csv.d.ts +28 -0
  48. package/dist/import/csv.js +115 -0
  49. package/dist/import/index.d.ts +33 -0
  50. package/dist/import/index.js +60 -0
  51. package/dist/import/mapping.d.ts +15 -0
  52. package/dist/import/mapping.js +33 -0
  53. package/dist/import/match.d.ts +24 -0
  54. package/dist/import/match.js +60 -0
  55. package/dist/import/models.d.ts +2 -0
  56. package/dist/import/models.js +102 -0
  57. package/dist/import/routes.d.ts +13 -0
  58. package/dist/import/routes.js +392 -0
  59. package/dist/import/run.d.ts +131 -0
  60. package/dist/import/run.js +568 -0
  61. package/dist/import/service.d.ts +153 -0
  62. package/dist/import/service.js +367 -0
  63. package/dist/import/web/ImportButton.d.ts +7 -0
  64. package/dist/import/web/ImportButton.js +31 -0
  65. package/dist/import/web/ImportWizard.d.ts +75 -0
  66. package/dist/import/web/ImportWizard.js +217 -0
  67. package/dist/import/web/index.d.ts +13 -0
  68. package/dist/import/web/index.js +18 -0
  69. package/dist/import/web/messages.d.ts +2 -0
  70. package/dist/import/web/messages.js +139 -0
  71. package/dist/import/worker.d.ts +3 -0
  72. package/dist/import/worker.js +52 -0
  73. package/dist/import/xlsx.d.ts +20 -0
  74. package/dist/import/xlsx.js +192 -0
  75. package/dist/l10n_bd/.kosh/bindings.d.ts +22 -0
  76. package/dist/l10n_bd/.kosh/bindings.js +15 -0
  77. package/dist/l10n_bd/.kosh/schema.d.ts +1281 -0
  78. package/dist/l10n_bd/documents.d.ts +8 -0
  79. package/dist/l10n_bd/documents.js +183 -0
  80. package/dist/l10n_bd/index.d.ts +8 -0
  81. package/dist/l10n_bd/index.js +156 -0
  82. package/dist/l10n_bd/module.d.ts +5705 -0
  83. package/dist/l10n_bd/module.js +163 -0
  84. package/dist/l10n_bd/mushak.d.ts +67 -0
  85. package/dist/l10n_bd/mushak.js +139 -0
  86. package/dist/l10n_bd/routes.d.ts +3 -0
  87. package/dist/l10n_bd/routes.js +205 -0
  88. package/dist/l10n_bd/web/MushakPage.d.ts +7 -0
  89. package/dist/l10n_bd/web/MushakPage.js +53 -0
  90. package/dist/l10n_bd/web/index.d.ts +13 -0
  91. package/dist/l10n_bd/web/index.js +18 -0
  92. package/dist/l10n_bd/web/messages.d.ts +2 -0
  93. package/dist/l10n_bd/web/messages.js +101 -0
  94. package/dist/l10n_in/.kosh/bindings.d.ts +22 -0
  95. package/dist/l10n_in/.kosh/bindings.js +15 -0
  96. package/dist/l10n_in/.kosh/schema.d.ts +1343 -0
  97. package/dist/l10n_in/documents.d.ts +8 -0
  98. package/dist/l10n_in/documents.js +114 -0
  99. package/dist/l10n_in/einvoice.d.ts +253 -0
  100. package/dist/l10n_in/einvoice.js +485 -0
  101. package/dist/l10n_in/gstr.d.ts +109 -0
  102. package/dist/l10n_in/gstr.js +430 -0
  103. package/dist/l10n_in/index.d.ts +20 -0
  104. package/dist/l10n_in/index.js +232 -0
  105. package/dist/l10n_in/module.d.ts +7823 -0
  106. package/dist/l10n_in/module.js +427 -0
  107. package/dist/l10n_in/routes-einvoice.d.ts +15 -0
  108. package/dist/l10n_in/routes-einvoice.js +124 -0
  109. package/dist/l10n_in/routes.d.ts +17 -0
  110. package/dist/l10n_in/routes.js +282 -0
  111. package/dist/l10n_in/web/EInvoicePanel.d.ts +8 -0
  112. package/dist/l10n_in/web/EInvoicePanel.js +63 -0
  113. package/dist/l10n_in/web/GstPage.d.ts +7 -0
  114. package/dist/l10n_in/web/GstPage.js +57 -0
  115. package/dist/l10n_in/web/GstSettings.d.ts +10 -0
  116. package/dist/l10n_in/web/GstSettings.js +36 -0
  117. package/dist/l10n_in/web/index.d.ts +15 -0
  118. package/dist/l10n_in/web/index.js +22 -0
  119. package/dist/l10n_in/web/messages.d.ts +2 -0
  120. package/dist/l10n_in/web/messages.js +166 -0
  121. package/dist/modules-sdk/demo.d.ts +2 -0
  122. package/dist/modules-sdk/demo.js +7 -3
  123. package/dist/modules-sdk/index.d.ts +2 -2
  124. package/dist/modules-sdk/index.js +1 -1
  125. package/dist/modules-sdk/manifest.d.ts +132 -0
  126. package/dist/modules-sdk/manifest.js +22 -0
  127. package/dist/notify/inbox.js +21 -0
  128. package/dist/notify/messages.js +4 -0
  129. package/dist/notify/models.d.ts +1 -1
  130. package/dist/notify/models.js +2 -0
  131. package/dist/notify/web/messages.js +4 -0
  132. package/dist/portal/.kosh/bindings.d.ts +22 -0
  133. package/dist/portal/.kosh/bindings.js +15 -0
  134. package/dist/portal/.kosh/schema.d.ts +346 -0
  135. package/dist/portal/announce.d.ts +16 -0
  136. package/dist/portal/announce.js +117 -0
  137. package/dist/portal/documents.d.ts +17 -0
  138. package/dist/portal/documents.js +61 -0
  139. package/dist/portal/index.d.ts +20 -0
  140. package/dist/portal/index.js +65 -0
  141. package/dist/portal/module.d.ts +1405 -0
  142. package/dist/portal/module.js +84 -0
  143. package/dist/portal/routes-documents.d.ts +2 -0
  144. package/dist/portal/routes-documents.js +391 -0
  145. package/dist/portal/routes.d.ts +13 -0
  146. package/dist/portal/routes.js +313 -0
  147. package/dist/portal/rules.d.ts +11 -0
  148. package/dist/portal/rules.js +25 -0
  149. package/dist/portal/web/PortalAccess.d.ts +8 -0
  150. package/dist/portal/web/PortalAccess.js +66 -0
  151. package/dist/portal/web/index.d.ts +12 -0
  152. package/dist/portal/web/index.js +17 -0
  153. package/dist/portal/web/messages.d.ts +2 -0
  154. package/dist/portal/web/messages.js +33 -0
  155. package/dist/product/.kosh/bindings.js +1 -1
  156. package/dist/product/.kosh/schema.d.ts +12 -3
  157. package/dist/product/demo.js +6 -5
  158. package/dist/product/index.d.ts +1 -0
  159. package/dist/product/index.js +17 -4
  160. package/dist/product/module.d.ts +346 -14
  161. package/dist/product/module.js +48 -18
  162. package/dist/product/taxes.d.ts +59 -0
  163. package/dist/product/taxes.js +61 -0
  164. package/dist/project/.kosh/bindings.js +1 -1
  165. package/dist/project/.kosh/schema.d.ts +3 -1
  166. package/dist/project/module.d.ts +20 -0
  167. package/dist/project_portal/.kosh/bindings.d.ts +22 -0
  168. package/dist/project_portal/.kosh/bindings.js +15 -0
  169. package/dist/project_portal/.kosh/schema.d.ts +671 -0
  170. package/dist/project_portal/index.d.ts +9 -0
  171. package/dist/project_portal/index.js +42 -0
  172. package/dist/project_portal/module.d.ts +2142 -0
  173. package/dist/project_portal/module.js +22 -0
  174. package/dist/purchase/.kosh/bindings.js +1 -1
  175. package/dist/purchase/.kosh/schema.d.ts +15 -4
  176. package/dist/purchase/demo.js +3 -3
  177. package/dist/purchase/index.js +1 -1
  178. package/dist/purchase/module.d.ts +232 -8
  179. package/dist/purchase/module.js +51 -9
  180. package/dist/purchase_account/.kosh/bindings.js +1 -1
  181. package/dist/purchase_account/.kosh/schema.d.ts +144 -10
  182. package/dist/purchase_account/module.d.ts +2008 -641
  183. package/dist/purchase_account/module.js +2 -2
  184. package/dist/purchase_portal/.kosh/bindings.d.ts +22 -0
  185. package/dist/purchase_portal/.kosh/bindings.js +15 -0
  186. package/dist/purchase_portal/.kosh/schema.d.ts +822 -0
  187. package/dist/purchase_portal/index.d.ts +9 -0
  188. package/dist/purchase_portal/index.js +53 -0
  189. package/dist/purchase_portal/module.d.ts +2758 -0
  190. package/dist/purchase_portal/module.js +18 -0
  191. package/dist/reporting/documents.js +20 -3
  192. package/dist/reporting/index.d.ts +1 -0
  193. package/dist/reporting/index.js +1 -0
  194. package/dist/reporting/routes-documents.js +9 -2
  195. package/dist/reporting/words.d.ts +11 -0
  196. package/dist/reporting/words.js +229 -0
  197. package/dist/sale_account/.kosh/bindings.js +1 -1
  198. package/dist/sale_account/.kosh/schema.d.ts +144 -10
  199. package/dist/sale_account/module.d.ts +2008 -641
  200. package/dist/sale_account/module.js +3 -3
  201. package/dist/sale_portal/.kosh/bindings.d.ts +22 -0
  202. package/dist/sale_portal/.kosh/bindings.js +15 -0
  203. package/dist/sale_portal/.kosh/schema.d.ts +736 -0
  204. package/dist/sale_portal/index.d.ts +9 -0
  205. package/dist/sale_portal/index.js +71 -0
  206. package/dist/sale_portal/module.d.ts +2718 -0
  207. package/dist/sale_portal/module.js +19 -0
  208. package/dist/sale_project/.kosh/bindings.js +1 -1
  209. package/dist/sale_project/.kosh/schema.d.ts +15 -4
  210. package/dist/sale_project/module.d.ts +390 -10
  211. package/dist/sales/.kosh/bindings.js +1 -1
  212. package/dist/sales/.kosh/schema.d.ts +15 -4
  213. package/dist/sales/demo.js +3 -3
  214. package/dist/sales/index.js +67 -1
  215. package/dist/sales/module.d.ts +232 -8
  216. package/dist/sales/module.js +51 -12
  217. package/dist/server/auth/base.js +2 -0
  218. package/dist/server/auth/service.d.ts +2 -0
  219. package/dist/server/auth/service.js +7 -1
  220. package/dist/server/index.d.ts +1 -1
  221. package/dist/server/index.js +1 -1
  222. package/dist/server/server.js +26 -1
  223. package/dist/ui/api.d.ts +2 -0
  224. package/dist/ui/app.js +1 -1
  225. package/dist/ui/context.d.ts +3 -1
  226. package/dist/ui/context.js +2 -2
  227. package/dist/ui/index.d.ts +1 -1
  228. package/dist/ui/index.js +1 -1
  229. package/dist/ui/lib/format.d.ts +2 -0
  230. package/dist/ui/lib/format.js +3 -1
  231. package/dist/ui/views/FormView.js +19 -6
  232. package/package.json +50 -2
@@ -0,0 +1,163 @@
1
+ /**
2
+ * The data layer of the Bangladesh localization (RFC 0016 §5.3): the taxes under the VAT and Supplementary Duty Act
3
+ * 2012 (VAT at the standard and reduced rates, exempt and zero-rated, supplementary duty charged before VAT, advance
4
+ * tax on imports), and the tax identifiers of companies and contacts — the 13-digit BIN and the 12-digit TIN —
5
+ * checked when saved. The taxes are data (`noupdate`: the company's to change); their accounts are set for each
6
+ * company when the localization is installed.
7
+ */
8
+ import { accountingShutro } from '../accounting/index.js';
9
+ import { contactsShutro } from '../contacts/index.js';
10
+ import { computeTaxes, taxRule } from '../product/index.js';
11
+ import { orm } from '../server/orm.js';
12
+ import { check, compute, defineModule, extendModel, f, record } from './.kosh/bindings.js';
13
+ const BIN = /^\d{13}$/;
14
+ const TIN = /^\d{12}$/;
15
+ export const PartnerIdentifiers = extendModel('res.partner', {
16
+ fields: {
17
+ /** Business Identification Number (VAT registration, 13 digits): on the tax invoice (Mushak 6.3). */
18
+ bin: f.char({ size: 13, label: 'BIN' }),
19
+ /** Taxpayer Identification Number (income tax, 12 digits). */
20
+ tin: f.char({ size: 12, label: 'TIN' }),
21
+ },
22
+ constraints: {
23
+ binFormat: {
24
+ ...check('res.partner', ['bin'], (ps) => ps.filter((p) => p.bin !== null && !BIN.test(p.bin))),
25
+ message: 'A BIN has 13 digits.',
26
+ },
27
+ tinFormat: {
28
+ ...check('res.partner', ['tin'], (ps) => ps.filter((p) => p.tin !== null && !TIN.test(p.tin))),
29
+ message: 'A TIN has 12 digits.',
30
+ },
31
+ },
32
+ });
33
+ export const CompanyIdentifiers = extendModel('res.company', {
34
+ fields: {
35
+ bin: f.char({ size: 13, label: 'BIN' }),
36
+ tin: f.char({ size: 12, label: 'TIN' }),
37
+ },
38
+ constraints: {
39
+ companyBinFormat: {
40
+ ...check('res.company', ['bin'], (cs) => cs.filter((c) => c.bin !== null && !BIN.test(c.bin))),
41
+ message: 'A BIN has 13 digits.',
42
+ },
43
+ companyTinFormat: {
44
+ ...check('res.company', ['tin'], (cs) => cs.filter((c) => c.tin !== null && !TIN.test(c.tin))),
45
+ message: 'A TIN has 12 digits.',
46
+ },
47
+ },
48
+ });
49
+ /** What a tax is in the Mushak returns: the VAT's rate band, supplementary duty or advance tax. */
50
+ export const MUSHAK_KINDS = {
51
+ standard: 'VAT, standard rate',
52
+ reduced: 'VAT, reduced rate',
53
+ zero: 'VAT, zero-rated',
54
+ exempt: 'VAT exempt',
55
+ sd: 'Supplementary duty',
56
+ at: 'Advance tax',
57
+ };
58
+ export const TaxMushak = extendModel('account.tax', {
59
+ fields: {
60
+ /** Where the tax goes in the Mushak 6.1, 6.2 and 9.1 (RFC 0016 §5.3); a tax without it is not reported. */
61
+ mushak: f.selection(MUSHAK_KINDS, { label: 'Mushak' }),
62
+ },
63
+ });
64
+ const TAX_DEPENDS = [
65
+ 'lineKind',
66
+ 'quantity',
67
+ 'priceUnit',
68
+ 'discount',
69
+ 'taxes.rate',
70
+ 'taxes.sequence',
71
+ 'taxes.amountType',
72
+ 'taxes.fixedAmount',
73
+ 'taxes.affectsBase',
74
+ 'taxes.priceIncluded',
75
+ 'taxes.reverseCharge',
76
+ 'taxes.mushak',
77
+ ];
78
+ /** A product line's taxes, summed by Mushak kind (as Accounting computes them: after discount, in order). */
79
+ export function mushakAmounts(l) {
80
+ if (l.lineKind !== 'product')
81
+ return null;
82
+ const taxes = [...l.taxes];
83
+ const quantity = l.quantity ?? orm.toDecimal(1);
84
+ const amount = quantity
85
+ .times(l.priceUnit ?? 0)
86
+ .times(orm.toDecimal(100).minus(l.discount ?? 0))
87
+ .div(100);
88
+ const result = computeTaxes(amount, quantity, taxes.map(taxRule));
89
+ const kindOf = new Map(taxes.map((t) => [t.id, t.mushak]));
90
+ let sd = orm.toDecimal(0);
91
+ let vat = orm.toDecimal(0);
92
+ for (const t of result.taxes) {
93
+ const kind = kindOf.get(t.tax);
94
+ if (kind === 'sd')
95
+ sd = sd.plus(t.amount);
96
+ else if (kind === 'standard' || kind === 'reduced' || kind === 'zero' || kind === 'exempt')
97
+ vat = vat.plus(t.amount);
98
+ }
99
+ return { sd, vat, total: result.total };
100
+ }
101
+ export const LineMushak = extendModel('account.move.line', {
102
+ fields: {
103
+ /** A product line's supplementary duty (Mushak 6.3 and the registers). */
104
+ sdAmount: f.monetary({
105
+ currency: 'currency',
106
+ store: true,
107
+ label: 'SD',
108
+ ...compute('account.move.line', 'sdAmount', [...TAX_DEPENDS], (lines) => {
109
+ for (const l of lines)
110
+ l.sdAmount = mushakAmounts(l)?.sd ?? null;
111
+ }),
112
+ }),
113
+ /** A product line's VAT, on its price plus SD. */
114
+ vatAmount: f.monetary({
115
+ currency: 'currency',
116
+ store: true,
117
+ label: 'VAT',
118
+ ...compute('account.move.line', 'vatAmount', [...TAX_DEPENDS], (lines) => {
119
+ for (const l of lines)
120
+ l.vatAmount = mushakAmounts(l)?.vat ?? null;
121
+ }),
122
+ }),
123
+ /** A product line's price with all its taxes. */
124
+ totalWithTaxes: f.monetary({
125
+ currency: 'currency',
126
+ store: true,
127
+ label: 'Total with taxes',
128
+ ...compute('account.move.line', 'totalWithTaxes', [...TAX_DEPENDS], (lines) => {
129
+ for (const l of lines)
130
+ l.totalWithTaxes = mushakAmounts(l)?.total ?? null;
131
+ }),
132
+ }),
133
+ },
134
+ });
135
+ const tax = (id, name, use, rate, mushak, extra = {}) => record('account.tax', id, { name, use, rate, mushak, ...extra }, { noupdate: true });
136
+ /** The ids the taxes keep from before 0.2.0, when Products shipped them (moved here by its migration). */
137
+ export const TAXES = [
138
+ // VAT: the standard 15 % and the reduced rates.
139
+ tax('tax_sale_vat15', 'VAT 15%', 'sale', '0.15', 'standard'),
140
+ tax('tax_sale_vat10', 'VAT 10%', 'sale', '0.10', 'reduced'),
141
+ tax('tax_sale_vat7_5', 'VAT 7.5%', 'sale', '0.075', 'reduced'),
142
+ tax('tax_sale_vat5', 'VAT 5%', 'sale', '0.05', 'reduced'),
143
+ tax('tax_sale_exempt', 'VAT exempt', 'sale', '0', 'exempt'),
144
+ tax('tax_sale_zero', 'VAT 0% (exports)', 'sale', '0', 'zero'),
145
+ tax('tax_purchase_vat15', 'VAT 15% (purchases)', 'purchase', '0.15', 'standard'),
146
+ tax('tax_purchase_vat5', 'VAT 5% (purchases)', 'purchase', '0.05', 'reduced'),
147
+ tax('tax_purchase_exempt', 'VAT exempt (purchases)', 'purchase', '0', 'exempt'),
148
+ // Supplementary duty: before VAT, which is charged on the price plus SD.
149
+ tax('tax_sale_sd10', 'SD 10%', 'sale', '0.10', 'sd', { sequence: 1, affectsBase: true }),
150
+ tax('tax_sale_sd20', 'SD 20%', 'sale', '0.20', 'sd', { sequence: 1, affectsBase: true }),
151
+ tax('tax_sale_sd30', 'SD 30%', 'sale', '0.30', 'sd', { sequence: 1, affectsBase: true }),
152
+ tax('tax_sale_sd45', 'SD 45%', 'sale', '0.45', 'sd', { sequence: 1, affectsBase: true }),
153
+ // Advance tax on imports, recoverable against the VAT due.
154
+ tax('tax_purchase_at5', 'AT 5% (imports)', 'purchase', '0.05', 'at', { sequence: 20 }),
155
+ ];
156
+ export const l10nBdShutro = defineModule({
157
+ name: 'l10n_bd',
158
+ version: '0.1.0',
159
+ depends: [accountingShutro, contactsShutro],
160
+ models: [],
161
+ extensions: [PartnerIdentifiers, CompanyIdentifiers, TaxMushak, LineMushak],
162
+ data: TAXES,
163
+ });
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The Mushak registers and the monthly return (RFC 0016 §5.3), from the posted documents of a month: the purchase
3
+ * register (Mushak 6.1) and the sales register (Mushak 6.2), a row per product line, and the summary of the
4
+ * monthly return (Mushak 9.1): supplies and purchases by VAT band with their SD and VAT, the adjustments (VAT
5
+ * deducted at source, advance tax) and the net tax. Filing stays on the NBR's online system: these prepare what is
6
+ * entered there. Amounts are in the company's currency (taka): a foreign-currency line converts at its document's
7
+ * rate, through its posted balance. Credit notes count negative. The stock columns of the registers wait for
8
+ * Inventory.
9
+ */
10
+ import { orm } from '../server/orm.js';
11
+ import type { Env } from './.kosh/bindings.js';
12
+ type Decimal = ReturnType<typeof orm.toDecimal>;
13
+ export type Side = 'sales' | 'purchases';
14
+ /** A line's VAT band, from the Mushak kind of its VAT tax (`unclassified`: no VAT tax with a kind). */
15
+ export type VatCategory = 'standard' | 'reduced' | 'zero' | 'exempt' | 'unclassified';
16
+ export declare const VAT_CATEGORIES: readonly VatCategory[];
17
+ export interface RegisterRow {
18
+ readonly date: string;
19
+ readonly number: string;
20
+ /** A credit note (its amounts are negative). */
21
+ readonly refund: boolean;
22
+ readonly partner: string;
23
+ readonly bin: string | null;
24
+ readonly description: string;
25
+ readonly quantity: Decimal;
26
+ readonly value: Decimal;
27
+ readonly sd: Decimal;
28
+ readonly vat: Decimal;
29
+ readonly total: Decimal;
30
+ readonly category: VatCategory;
31
+ }
32
+ export declare class MonthError extends Error {
33
+ }
34
+ /** The first and last day of `month` (`2026-10`). */
35
+ export declare function monthRange(month: string): {
36
+ readonly from: string;
37
+ readonly to: string;
38
+ };
39
+ /** The register of `side` (Mushak 6.1 for purchases, 6.2 for sales) for `month`. */
40
+ export declare function register(env: Env, side: Side, month: string): Promise<RegisterRow[]>;
41
+ export interface Band {
42
+ readonly value: Decimal;
43
+ readonly sd: Decimal;
44
+ readonly vat: Decimal;
45
+ }
46
+ /** The summary of the monthly return (Mushak 9.1). */
47
+ export interface Return91 {
48
+ readonly month: string;
49
+ /** Part 3: supplies, by VAT band. */
50
+ readonly supplies: Readonly<Record<VatCategory, Band>>;
51
+ /** Part 4: purchases, by VAT band. */
52
+ readonly purchases: Readonly<Record<VatCategory, Band>>;
53
+ /** Part 5: increasing adjustments — VAT we deducted at source from our suppliers. */
54
+ readonly vdsDeducted: Decimal;
55
+ /** Part 6: decreasing adjustments — VAT our customers deducted at source, advance tax paid on imports. */
56
+ readonly vdsByCustomers: Decimal;
57
+ readonly advanceTax: Decimal;
58
+ /** Part 7: the net tax. */
59
+ readonly outputVat: Decimal;
60
+ readonly inputVat: Decimal;
61
+ readonly sdPayable: Decimal;
62
+ /** Output VAT − input VAT + increasing − decreasing adjustments (negative: carried forward). */
63
+ readonly netVat: Decimal;
64
+ }
65
+ /** The monthly return's summary for `month`. */
66
+ export declare function return91(env: Env, month: string): Promise<Return91>;
67
+ export {};
@@ -0,0 +1,139 @@
1
+ /**
2
+ * The Mushak registers and the monthly return (RFC 0016 §5.3), from the posted documents of a month: the purchase
3
+ * register (Mushak 6.1) and the sales register (Mushak 6.2), a row per product line, and the summary of the
4
+ * monthly return (Mushak 9.1): supplies and purchases by VAT band with their SD and VAT, the adjustments (VAT
5
+ * deducted at source, advance tax) and the net tax. Filing stays on the NBR's online system: these prepare what is
6
+ * entered there. Amounts are in the company's currency (taka): a foreign-currency line converts at its document's
7
+ * rate, through its posted balance. Credit notes count negative. The stock columns of the registers wait for
8
+ * Inventory.
9
+ */
10
+ import { orm } from '../server/orm.js';
11
+ const ZERO = orm.toDecimal(0);
12
+ export const VAT_CATEGORIES = [
13
+ 'standard',
14
+ 'reduced',
15
+ 'zero',
16
+ 'exempt',
17
+ 'unclassified',
18
+ ];
19
+ const KINDS = {
20
+ sales: { invoice: 'out_invoice', debit: 'out_debit', refund: 'out_refund' },
21
+ purchases: { invoice: 'in_invoice', debit: 'in_debit', refund: 'in_refund' },
22
+ };
23
+ export class MonthError extends Error {
24
+ }
25
+ /** The first and last day of `month` (`2026-10`). */
26
+ export function monthRange(month) {
27
+ const match = /^(\d{4})-(0[1-9]|1[0-2])$/.exec(month);
28
+ if (match === null)
29
+ throw new MonthError('A month is written 2026-10.');
30
+ const [y, m] = [Number(match[1]), Number(match[2])];
31
+ const last = new Date(Date.UTC(y, m, 0)).getUTCDate();
32
+ return { from: `${month}-01`, to: `${month}-${String(last).padStart(2, '0')}` };
33
+ }
34
+ const round = (d) => d.toDecimalPlaces(2);
35
+ /** The posted invoices and credit notes of `side` in `month`, with what the registers read. */
36
+ async function documentsOf(env, side, month) {
37
+ const { from, to } = monthRange(month);
38
+ const kinds = KINDS[side];
39
+ const moves = await env.model('account.move').search({
40
+ where: {
41
+ kind: { in: [kinds.invoice, kinds.debit, kinds.refund] },
42
+ state: 'posted',
43
+ date: { gte: from, lte: to },
44
+ },
45
+ order: ['date asc', 'name asc'],
46
+ });
47
+ return moves.load({
48
+ partner: ['name', 'bin'],
49
+ invoiceLines: { taxes: ['mushak'] },
50
+ lines: { taxLineOf: ['mushak'] },
51
+ });
52
+ }
53
+ /** The register of `side` (Mushak 6.1 for purchases, 6.2 for sales) for `month`. */
54
+ export async function register(env, side, month) {
55
+ const rows = [];
56
+ for (const move of await documentsOf(env, side, month)) {
57
+ const refund = move.kind === KINDS[side].refund;
58
+ const sign = refund ? -1 : 1;
59
+ for (const line of move.invoiceLines) {
60
+ if (line.lineKind !== 'product')
61
+ continue;
62
+ const subtotal = line.subtotal?.amount ?? ZERO;
63
+ // The value in taka is the posted balance (credit on sales, debit on purchases); taxes follow its rate.
64
+ const value = (line.balance?.amount ?? ZERO).abs();
65
+ const rate = subtotal.isZero() ? orm.toDecimal(1) : value.div(subtotal.abs());
66
+ const sd = round((line.sdAmount?.amount ?? ZERO).times(rate));
67
+ const vat = round((line.vatAmount?.amount ?? ZERO).times(rate));
68
+ const kinds = [...line.taxes].map((t) => t.mushak);
69
+ const category = VAT_CATEGORIES.find((c) => c !== 'unclassified' && kinds.includes(c)) ??
70
+ 'unclassified';
71
+ rows.push({
72
+ date: String(move.date),
73
+ number: move.name ?? '',
74
+ refund,
75
+ partner: move.partner?.name ?? '',
76
+ bin: move.partner?.bin ?? null,
77
+ description: line.name ?? '',
78
+ quantity: (line.quantity ?? ZERO).times(sign),
79
+ value: value.times(sign),
80
+ sd: sd.times(sign),
81
+ vat: vat.times(sign),
82
+ total: value.plus(sd).plus(vat).times(sign),
83
+ category,
84
+ });
85
+ }
86
+ }
87
+ return rows;
88
+ }
89
+ function bands(rows) {
90
+ const out = Object.fromEntries(VAT_CATEGORIES.map((c) => [c, { value: ZERO, sd: ZERO, vat: ZERO }]));
91
+ for (const r of rows) {
92
+ const b = out[r.category];
93
+ out[r.category] = { value: b.value.plus(r.value), sd: b.sd.plus(r.sd), vat: b.vat.plus(r.vat) };
94
+ }
95
+ return out;
96
+ }
97
+ const sum = (bs, key) => VAT_CATEGORIES.reduce((s, c) => s.plus(bs[c][key]), ZERO);
98
+ /** The monthly return's summary for `month`. */
99
+ export async function return91(env, month) {
100
+ const { from, to } = monthRange(month);
101
+ const supplies = bands(await register(env, 'sales', month));
102
+ const purchases = bands(await register(env, 'purchases', month));
103
+ // Advance tax: the posted AT tax lines of the month's bills (net of credit notes).
104
+ let advanceTax = ZERO;
105
+ for (const move of await documentsOf(env, 'purchases', month))
106
+ for (const line of move.lines)
107
+ if (line.lineKind === 'tax' && line.taxLineOf?.mushak === 'at')
108
+ advanceTax = advanceTax.plus(line.balance?.amount ?? ZERO);
109
+ // VAT deducted at source: withholdings on the VAT of the month's posted payments, by direction.
110
+ let vdsDeducted = ZERO;
111
+ let vdsByCustomers = ZERO;
112
+ const payments = await env.model('account.payment').search({
113
+ where: { state: 'posted', date: { gte: from, lte: to } },
114
+ });
115
+ for (const p of await payments.load({ withholdings: { withholding: ['base', 'direction'] } }))
116
+ for (const w of p.withholdings) {
117
+ if (w.withholding.base !== 'tax')
118
+ continue;
119
+ const amount = w.amount?.amount ?? ZERO;
120
+ if (w.withholding.direction === 'vendor')
121
+ vdsDeducted = vdsDeducted.plus(amount);
122
+ else
123
+ vdsByCustomers = vdsByCustomers.plus(amount);
124
+ }
125
+ const outputVat = sum(supplies, 'vat');
126
+ const inputVat = sum(purchases, 'vat');
127
+ return {
128
+ month,
129
+ supplies,
130
+ purchases,
131
+ vdsDeducted,
132
+ vdsByCustomers,
133
+ advanceTax,
134
+ outputVat,
135
+ inputVat,
136
+ sdPayable: sum(supplies, 'sd'),
137
+ netVat: outputVat.minus(inputVat).plus(vdsDeducted).minus(vdsByCustomers).minus(advanceTax),
138
+ };
139
+ }
@@ -0,0 +1,3 @@
1
+ import { type ModuleContext } from '../server/index.js';
2
+ export type MushakForm = '6.1' | '6.2' | '9.1';
3
+ export declare function registerMushakRoutes(context: ModuleContext): void;
@@ -0,0 +1,205 @@
1
+ /**
2
+ * The Mushak registers and return over HTTP (RFC 0016 §5.3): `GET /api/v1/l10n_bd/mushak/:form?month=2026-10` for
3
+ * 6.1 (purchases), 6.2 (sales) and 9.1 (the monthly return's summary), as JSON for the screen, or with
4
+ * `format=xlsx` / `format=pdf` as a file. As the user: reading needs the documents' read right, a file their export
5
+ * right, and every file is in the audit log.
6
+ */
7
+ import { moneyFormat, renderPdf, workbook } from '../reporting/index.js';
8
+ import { audit, HttpProblem } from '../server/index.js';
9
+ import { MonthError, monthRange, register, return91, VAT_CATEGORIES, } from './mushak.js';
10
+ const TITLES = {
11
+ '6.1': 'Purchase register',
12
+ '6.2': 'Sales register',
13
+ '9.1': 'Monthly return (summary)',
14
+ };
15
+ const BANDS = {
16
+ standard: 'Standard rate (15 %)',
17
+ reduced: 'Reduced rates',
18
+ zero: 'Zero-rated',
19
+ exempt: 'Exempt',
20
+ unclassified: 'Without a Mushak VAT kind',
21
+ };
22
+ const money = (d) => d.toFixed(2);
23
+ /** The JSON of a register row. */
24
+ const rowJson = (r) => ({
25
+ ...r,
26
+ quantity: r.quantity.toString(),
27
+ value: money(r.value),
28
+ sd: money(r.sd),
29
+ vat: money(r.vat),
30
+ total: money(r.total),
31
+ });
32
+ const bandJson = (b) => ({ value: money(b.value), sd: money(b.sd), vat: money(b.vat) });
33
+ const bandsJson = (bs) => Object.fromEntries(VAT_CATEGORIES.map((c) => [c, bandJson(bs[c])]));
34
+ const returnJson = (r) => ({
35
+ month: r.month,
36
+ supplies: bandsJson(r.supplies),
37
+ purchases: bandsJson(r.purchases),
38
+ vdsDeducted: money(r.vdsDeducted),
39
+ vdsByCustomers: money(r.vdsByCustomers),
40
+ advanceTax: money(r.advanceTax),
41
+ outputVat: money(r.outputVat),
42
+ inputVat: money(r.inputVat),
43
+ sdPayable: money(r.sdPayable),
44
+ netVat: money(r.netVat),
45
+ });
46
+ /** The form as a table: a header row, then rows of text and amounts. */
47
+ function table(form, data) {
48
+ if (form !== '9.1') {
49
+ const partner = form === '6.2' ? 'Customer' : 'Supplier';
50
+ return [
51
+ [
52
+ 'No.',
53
+ 'Date',
54
+ 'Invoice or note',
55
+ partner,
56
+ 'BIN',
57
+ 'Description',
58
+ 'Quantity',
59
+ 'Value',
60
+ 'SD',
61
+ 'VAT',
62
+ 'Total',
63
+ 'VAT band',
64
+ ],
65
+ ...data.map((r, i) => [
66
+ String(i + 1),
67
+ r.date,
68
+ r.number,
69
+ r.partner,
70
+ r.bin ?? '',
71
+ r.description,
72
+ r.quantity.toString(),
73
+ r.value,
74
+ r.sd,
75
+ r.vat,
76
+ r.total,
77
+ BANDS[r.category],
78
+ ]),
79
+ ];
80
+ }
81
+ const r = data;
82
+ const bandRows = (bs) => VAT_CATEGORIES.map((c) => [BANDS[c], bs[c].value, bs[c].sd, bs[c].vat]);
83
+ return [
84
+ ['', 'Value', 'SD', 'VAT'],
85
+ ['Part 3 — supplies (output tax)', '', '', ''],
86
+ ...bandRows(r.supplies),
87
+ ['Part 4 — purchases (input tax)', '', '', ''],
88
+ ...bandRows(r.purchases),
89
+ ['Part 5 — increasing adjustments', '', '', ''],
90
+ ['VAT deducted at source from suppliers', '', '', r.vdsDeducted],
91
+ ['Part 6 — decreasing adjustments', '', '', ''],
92
+ ['VAT deducted at source by customers', '', '', r.vdsByCustomers],
93
+ ['Advance tax paid on imports', '', '', r.advanceTax],
94
+ ['Part 7 — net tax', '', '', ''],
95
+ ['Output VAT', '', '', r.outputVat],
96
+ ['Input VAT', '', '', r.inputVat],
97
+ ['Supplementary duty payable', '', r.sdPayable, ''],
98
+ ['Net VAT payable (negative: carried forward)', '', '', r.netVat],
99
+ ];
100
+ }
101
+ const isDecimal = (v) => typeof v !== 'string';
102
+ function sheet(form, month, rows, currency) {
103
+ const format = moneyFormat(currency);
104
+ return {
105
+ name: `Mushak ${form} ${month}`,
106
+ rows: rows.map((row, i) => row.map((v) => {
107
+ if (isDecimal(v))
108
+ return { decimal: v.toFixed(2), format };
109
+ // 9.1's part headings (a text in the first column only) are bold.
110
+ if (i > 0 && form === '9.1' && v.startsWith('Part '))
111
+ return { text: v, bold: true };
112
+ return v;
113
+ })),
114
+ widths: form === '9.1' ? [48, 16, 16, 16] : [6, 12, 18, 28, 16, 32, 10, 14, 12, 12, 14, 22],
115
+ };
116
+ }
117
+ const esc = (s) => s.replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' })[c]);
118
+ function html(form, month, company, rows, locale) {
119
+ const number = new Intl.NumberFormat(locale, {
120
+ minimumFractionDigits: 2,
121
+ maximumFractionDigits: 2,
122
+ });
123
+ const cell = (v, tag) => isDecimal(v)
124
+ ? `<${tag} class="n">${number.format(Number(v.toFixed(2)))}</${tag}>`
125
+ : `<${tag}>${esc(v)}</${tag}>`;
126
+ const [head, ...body] = rows;
127
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Mushak ${form} ${month}</title><style>
128
+ body { font: 9pt system-ui, sans-serif; color: #111; }
129
+ h1 { font-size: 14pt; margin: 0 0 2mm; } p { margin: 0 0 4mm; }
130
+ table { width: 100%; border-collapse: collapse; }
131
+ th, td { border-bottom: 1px solid #ccc; padding: 1.2mm 1.5mm; text-align: start; vertical-align: top; }
132
+ th { background: #f2f2f2; } .n { text-align: end; white-space: nowrap; }
133
+ </style></head><body><h1>Mushak ${form} — ${TITLES[form]}</h1><p>${esc(company.name)}${company.bin === null ? '' : ` · BIN ${esc(company.bin)}`} · ${month}</p><table><thead><tr>${(head ?? []).map((v) => cell(v, 'th')).join('')}</tr></thead><tbody>${body
134
+ .map((r) => `<tr>${r.map((v) => cell(v, 'td')).join('')}</tr>`)
135
+ .join('')}</tbody></table></body></html>`;
136
+ }
137
+ export function registerMushakRoutes(context) {
138
+ context.app.get('/api/v1/l10n_bd/mushak/:form', async (request, reply) => {
139
+ const form = request.params.form;
140
+ if (form !== '6.1' && form !== '6.2' && form !== '9.1')
141
+ throw new HttpProblem(404, 'unknown-form', 'Unknown form', 'The forms are 6.1, 6.2 and 9.1.');
142
+ const query = request.query;
143
+ const month = typeof query.month === 'string' ? query.month : '';
144
+ const format = query.format === 'xlsx' || query.format === 'pdf' ? query.format : 'json';
145
+ try {
146
+ monthRange(month);
147
+ }
148
+ catch (error) {
149
+ if (error instanceof MonthError)
150
+ throw new HttpProblem(400, 'invalid-month', 'Invalid month', error.message);
151
+ throw error;
152
+ }
153
+ const out = await request.env(async (raw) => {
154
+ const env = raw;
155
+ env.access.check('account.move', format === 'json' ? 'read' : 'export');
156
+ const data = form === '9.1'
157
+ ? await return91(env, month)
158
+ : await register(env, form === '6.2' ? 'sales' : 'purchases', month);
159
+ if (format === 'json')
160
+ return {
161
+ json: form === '9.1'
162
+ ? returnJson(data)
163
+ : { rows: data.map(rowJson) },
164
+ };
165
+ await audit(raw, {
166
+ kind: 'data.export',
167
+ summary: `Exported Mushak ${form} for ${month} as ${format}.`,
168
+ targetModel: 'account.move',
169
+ details: { form, month },
170
+ ip: request.ip,
171
+ });
172
+ const [company] = await env
173
+ .model('res.company')
174
+ .search({ where: { id: env.companyRef }, limit: 1 })
175
+ .then((c) => c.load({ currency: ['name'] }));
176
+ return {
177
+ rows: table(form, data),
178
+ company: {
179
+ name: company?.name ?? '',
180
+ bin: company?.bin ?? null,
181
+ currency: company?.currency.name ?? 'BDT',
182
+ locale: company?.numberGrouping === 'lakh' ? 'en-IN' : 'en-US',
183
+ },
184
+ };
185
+ // A file is an export: its audit event is written.
186
+ }, { write: format !== 'json' });
187
+ if ('json' in out)
188
+ return out.json;
189
+ const filename = `mushak-${form}-${month}.${format}`;
190
+ const content = format === 'xlsx'
191
+ ? workbook([sheet(form, month, out.rows, out.company.currency)])
192
+ : await renderPdf(html(form, month, out.company, out.rows, out.company.locale), {
193
+ paper: 'A4',
194
+ orientation: form === '9.1' ? 'portrait' : 'landscape',
195
+ blocks: [],
196
+ });
197
+ await reply
198
+ .type(format === 'xlsx'
199
+ ? 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
200
+ : 'application/pdf')
201
+ .header('content-disposition', `attachment; filename="${filename}"`)
202
+ .send(content);
203
+ return reply;
204
+ });
205
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Accounting › Reporting › Mushak returns (RFC 0016 §5.3): the sales register (6.2), the purchase register (6.1) and
3
+ * the monthly return's summary (9.1) of a month, as the server computes them, with their XLSX and PDF files.
4
+ */
5
+ import { type PageProps } from '../../ui/index.js';
6
+ import { type ReactElement } from 'react';
7
+ export declare function MushakPage({ api }: PageProps): ReactElement;
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Accounting › Reporting › Mushak returns (RFC 0016 §5.3): the sales register (6.2), the purchase register (6.1) and
4
+ * the monthly return's summary (9.1) of a month, as the server computes them, with their XLSX and PDF files.
5
+ */
6
+ import { ApiError, Button, Input, Label, useKosh } from '../../ui/index.js';
7
+ import { useMutation, useQuery } from '@tanstack/react-query';
8
+ import { useId, useState } from 'react';
9
+ import { useTranslation } from 'react-i18next';
10
+ const FORMS = ['6.2', '6.1', '9.1'];
11
+ const BANDS = ['standard', 'reduced', 'zero', 'exempt', 'unclassified'];
12
+ function saveFile(blob, filename) {
13
+ const url = URL.createObjectURL(blob);
14
+ const a = document.createElement('a');
15
+ a.href = url;
16
+ a.download = filename;
17
+ a.click();
18
+ setTimeout(() => URL.revokeObjectURL(url), 1000);
19
+ }
20
+ const cell = 'px-2 py-1';
21
+ const amount = `${cell} text-end tabular-nums`;
22
+ export function MushakPage({ api }) {
23
+ const { t } = useTranslation('l10n_bd');
24
+ const { format } = useKosh();
25
+ const id = useId();
26
+ const [form, setForm] = useState('6.2');
27
+ const [month, setMonth] = useState(new Date().toISOString().slice(0, 7));
28
+ const path = `/api/v1/l10n_bd/mushak/${form}?month=${month}`;
29
+ const data = useQuery({
30
+ queryKey: ['l10n_bd', 'mushak', form, month],
31
+ queryFn: () => api.call('GET', path),
32
+ enabled: /^\d{4}-\d{2}$/.test(month),
33
+ });
34
+ const download = useMutation({
35
+ mutationFn: async (kind) => {
36
+ const { blob, filename } = await api.download(`${path}&format=${kind}`, undefined, 'GET');
37
+ saveFile(blob, filename);
38
+ },
39
+ });
40
+ const error = data.error ?? download.error;
41
+ return (_jsxs("div", { className: "grid gap-4 p-4", children: [_jsx("h1", { className: "text-xl font-semibold", children: t('mushak.title') }), _jsxs("div", { className: "flex flex-wrap items-end gap-2", children: [_jsxs("fieldset", { className: "flex flex-wrap gap-1", children: [_jsx("legend", { className: "sr-only", children: t('mushak.form') }), FORMS.map((f) => (_jsx(Button, { variant: form === f ? 'primary' : 'secondary', "aria-pressed": form === f, onClick: () => setForm(f), children: t(`mushak.forms.${f.replace('.', '_')}`) }, f)))] }), _jsxs("div", { className: "grid gap-1", children: [_jsx(Label, { htmlFor: `${id}-month`, children: t('mushak.month') }), _jsx(Input, { id: `${id}-month`, type: "month", value: month, onChange: (e) => setMonth(e.target.value) })] }), _jsx(Button, { variant: "secondary", disabled: download.isPending, onClick: () => download.mutate('xlsx'), children: t('mushak.xlsx') }), _jsx(Button, { variant: "secondary", disabled: download.isPending, onClick: () => download.mutate('pdf'), children: t('mushak.pdf') })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: t('mushak.hint') }), error === null ? null : (_jsx("p", { role: "alert", className: "text-sm text-danger", children: error instanceof ApiError ? error.message : String(error) })), data.data === undefined ? null : 'rows' in data.data ? (_jsx(Register, { rows: data.data.rows, purchases: form === '6.1' })) : (_jsx(Summary, { data: data.data }))] }));
42
+ function Register({ rows, purchases, }) {
43
+ if (rows.length === 0)
44
+ return _jsx("p", { className: "text-sm text-muted-foreground", children: t('mushak.empty') });
45
+ return (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted", children: _jsxs("tr", { children: [_jsx("th", { className: `${cell} text-start`, children: t('mushak.date') }), _jsx("th", { className: `${cell} text-start`, children: t('mushak.number') }), _jsx("th", { className: `${cell} text-start`, children: t(purchases ? 'mushak.supplier' : 'mushak.customer') }), _jsx("th", { className: `${cell} text-start`, children: t('mushak.bin') }), _jsx("th", { className: `${cell} text-start`, children: t('mushak.description') }), _jsx("th", { className: `${cell} text-end`, children: t('mushak.value') }), _jsx("th", { className: `${cell} text-end`, children: t('mushak.sd') }), _jsx("th", { className: `${cell} text-end`, children: t('mushak.vat') }), _jsx("th", { className: `${cell} text-end`, children: t('mushak.total') }), _jsx("th", { className: `${cell} text-start`, children: t('mushak.band') })] }) }), _jsx("tbody", { children: rows.map((r, i) => (_jsxs("tr", { className: "border-b", children: [_jsx("td", { className: cell, children: format.date(r.date) }), _jsxs("td", { className: cell, children: [r.number, r.refund ? ` (${t('mushak.creditNote')})` : ''] }), _jsx("td", { className: cell, children: r.partner }), _jsx("td", { className: `${cell} tabular-nums`, children: r.bin ?? '' }), _jsx("td", { className: cell, children: r.description }), _jsx("td", { className: amount, children: format.decimal(r.value, 2) }), _jsx("td", { className: amount, children: format.decimal(r.sd, 2) }), _jsx("td", { className: amount, children: format.decimal(r.vat, 2) }), _jsx("td", { className: amount, children: format.decimal(r.total, 2) }), _jsx("td", { className: cell, children: t(`mushak.bands.${r.category}`) })] }, i))) })] }) }));
46
+ }
47
+ function Summary({ data }) {
48
+ const heading = (key) => (_jsx("tr", { className: "bg-muted font-semibold", children: _jsx("th", { colSpan: 4, scope: "colgroup", className: `${cell} text-start`, children: t(key) }) }));
49
+ const bands = (rows) => BANDS.map((b) => (_jsxs("tr", { className: "border-b", children: [_jsx("th", { scope: "row", className: `${cell} text-start font-normal`, children: t(`mushak.bands.${b}`) }), _jsx("td", { className: amount, children: format.decimal(rows[b].value, 2) }), _jsx("td", { className: amount, children: format.decimal(rows[b].sd, 2) }), _jsx("td", { className: amount, children: format.decimal(rows[b].vat, 2) })] }, b)));
50
+ const line = (key, value, column = 'vat') => (_jsxs("tr", { className: "border-b", children: [_jsx("th", { scope: "row", className: `${cell} text-start font-normal`, children: t(key) }), _jsx("td", { className: amount }), _jsx("td", { className: amount, children: column === 'sd' ? format.decimal(value, 2) : '' }), _jsx("td", { className: amount, children: column === 'vat' ? format.decimal(value, 2) : '' })] }));
51
+ return (_jsxs("table", { className: "w-full max-w-3xl text-sm", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { className: `${cell} text-start`, children: _jsx("span", { className: "sr-only", children: t('mushak.form') }) }), _jsx("th", { className: `${cell} text-end`, children: t('mushak.value') }), _jsx("th", { className: `${cell} text-end`, children: t('mushak.sd') }), _jsx("th", { className: `${cell} text-end`, children: t('mushak.vat') })] }) }), _jsxs("tbody", { children: [heading('mushak.r91.supplies'), bands(data.supplies), heading('mushak.r91.purchases'), bands(data.purchases), heading('mushak.r91.increasing'), line('mushak.r91.vdsDeducted', data.vdsDeducted), heading('mushak.r91.decreasing'), line('mushak.r91.vdsByCustomers', data.vdsByCustomers), line('mushak.r91.advanceTax', data.advanceTax), heading('mushak.r91.net'), line('mushak.r91.outputVat', data.outputVat), line('mushak.r91.inputVat', data.inputVat), line('mushak.r91.sdPayable', data.sdPayable, 'sd')] }), _jsx("tfoot", { className: "font-semibold", children: _jsxs("tr", { children: [_jsx("th", { scope: "row", className: `${cell} text-start`, children: t('mushak.r91.netVat') }), _jsx("td", { className: amount }), _jsx("td", { className: amount }), _jsx("td", { className: amount, children: format.decimal(data.netVat, 2) })] }) })] }));
52
+ }
53
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @packageDocumentation
3
+ * The web contribution of `shutro-erp/l10n_bd`: the Mushak returns page (the sales and purchase registers and the
4
+ * monthly return).
5
+ */
6
+ import { type Registry } from '../../ui/index.js';
7
+ import type { i18n } from 'i18next';
8
+ import { messages } from './messages.js';
9
+ export { MushakPage } from './MushakPage.js';
10
+ /** Registers the page (and the messages in `i18n`, when given). */
11
+ export declare function registerL10nBdWeb(registry: Registry, i18n?: i18n): Registry;
12
+ /** The messages, named as the starter project's back-office imports them (`<app>Messages`). */
13
+ export { messages as l10n_bdMessages };