shutro-erp 0.2.1 → 0.3.1
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.
- package/README.md +74 -67
- package/dist/account_portal/.kosh/bindings.d.ts +22 -0
- package/dist/account_portal/.kosh/bindings.js +15 -0
- package/dist/account_portal/.kosh/schema.d.ts +1380 -0
- package/dist/account_portal/index.d.ts +3 -0
- package/dist/account_portal/index.js +73 -0
- package/dist/account_portal/module.d.ts +5983 -0
- package/dist/account_portal/module.js +18 -0
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +172 -9
- package/dist/accounting/chart.d.ts +40 -8
- package/dist/accounting/chart.js +124 -30
- package/dist/accounting/closing.d.ts +6 -1
- package/dist/accounting/closing.js +5 -1
- package/dist/accounting/demo.js +6 -5
- package/dist/accounting/index.d.ts +3 -3
- package/dist/accounting/index.js +200 -23
- package/dist/accounting/module.d.ts +3326 -1086
- package/dist/accounting/module.js +408 -66
- package/dist/accounting/payments.d.ts +25 -0
- package/dist/accounting/payments.js +96 -4
- package/dist/accounting/web/RegisterPayment.d.ts +3 -2
- package/dist/accounting/web/RegisterPayment.js +39 -6
- package/dist/accounting/web/SettingsPage.js +5 -1
- package/dist/accounting/web/messages.js +10 -0
- package/dist/assistant/routes.js +1 -1
- package/dist/automation/engine.js +1 -1
- package/dist/automation/web/messages.js +2 -2
- package/dist/cli/apps.d.ts +1 -1
- package/dist/cli/apps.js +14 -14
- package/dist/cli/bin.js +3 -0
- package/dist/cli/index.js +11 -11
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.js +224 -78
- package/dist/cli/launch.d.ts +16 -0
- package/dist/cli/launch.js +138 -0
- package/dist/cli/project.d.ts +7 -7
- package/dist/cli/project.js +25 -25
- package/dist/cli/prompt.d.ts +35 -0
- package/dist/cli/prompt.js +48 -2
- package/dist/cli/templates.d.ts +14 -6
- package/dist/cli/templates.js +61 -34
- package/dist/cli/tui.d.ts +7 -0
- package/dist/cli/tui.js +254 -0
- package/dist/cli/ui.d.ts +21 -8
- package/dist/cli/ui.js +65 -19
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +33 -1
- package/dist/contacts/index.js +24 -1
- package/dist/contacts/module.d.ts +42 -0
- package/dist/contacts/module.js +3 -1
- package/dist/import/convert.d.ts +55 -0
- package/dist/import/convert.js +224 -0
- package/dist/import/csv.d.ts +28 -0
- package/dist/import/csv.js +115 -0
- package/dist/import/index.d.ts +33 -0
- package/dist/import/index.js +60 -0
- package/dist/import/mapping.d.ts +15 -0
- package/dist/import/mapping.js +33 -0
- package/dist/import/match.d.ts +24 -0
- package/dist/import/match.js +60 -0
- package/dist/import/models.d.ts +2 -0
- package/dist/import/models.js +102 -0
- package/dist/import/routes.d.ts +13 -0
- package/dist/import/routes.js +392 -0
- package/dist/import/run.d.ts +131 -0
- package/dist/import/run.js +568 -0
- package/dist/import/service.d.ts +153 -0
- package/dist/import/service.js +367 -0
- package/dist/import/web/ImportButton.d.ts +7 -0
- package/dist/import/web/ImportButton.js +31 -0
- package/dist/import/web/ImportWizard.d.ts +75 -0
- package/dist/import/web/ImportWizard.js +217 -0
- package/dist/import/web/index.d.ts +13 -0
- package/dist/import/web/index.js +18 -0
- package/dist/import/web/messages.d.ts +2 -0
- package/dist/import/web/messages.js +139 -0
- package/dist/import/worker.d.ts +3 -0
- package/dist/import/worker.js +52 -0
- package/dist/import/xlsx.d.ts +20 -0
- package/dist/import/xlsx.js +192 -0
- package/dist/l10n_bd/.kosh/bindings.d.ts +22 -0
- package/dist/l10n_bd/.kosh/bindings.js +15 -0
- package/dist/l10n_bd/.kosh/schema.d.ts +1311 -0
- package/dist/l10n_bd/documents.d.ts +8 -0
- package/dist/l10n_bd/documents.js +183 -0
- package/dist/l10n_bd/index.d.ts +8 -0
- package/dist/l10n_bd/index.js +156 -0
- package/dist/l10n_bd/module.d.ts +5705 -0
- package/dist/l10n_bd/module.js +163 -0
- package/dist/l10n_bd/mushak.d.ts +67 -0
- package/dist/l10n_bd/mushak.js +139 -0
- package/dist/l10n_bd/routes.d.ts +3 -0
- package/dist/l10n_bd/routes.js +205 -0
- package/dist/l10n_bd/web/MushakPage.d.ts +7 -0
- package/dist/l10n_bd/web/MushakPage.js +53 -0
- package/dist/l10n_bd/web/index.d.ts +13 -0
- package/dist/l10n_bd/web/index.js +18 -0
- package/dist/l10n_bd/web/messages.d.ts +2 -0
- package/dist/l10n_bd/web/messages.js +101 -0
- package/dist/l10n_in/.kosh/bindings.d.ts +22 -0
- package/dist/l10n_in/.kosh/bindings.js +15 -0
- package/dist/l10n_in/.kosh/schema.d.ts +1373 -0
- package/dist/l10n_in/documents.d.ts +8 -0
- package/dist/l10n_in/documents.js +114 -0
- package/dist/l10n_in/einvoice.d.ts +253 -0
- package/dist/l10n_in/einvoice.js +485 -0
- package/dist/l10n_in/gstr.d.ts +109 -0
- package/dist/l10n_in/gstr.js +430 -0
- package/dist/l10n_in/index.d.ts +20 -0
- package/dist/l10n_in/index.js +232 -0
- package/dist/l10n_in/module.d.ts +7823 -0
- package/dist/l10n_in/module.js +427 -0
- package/dist/l10n_in/routes-einvoice.d.ts +15 -0
- package/dist/l10n_in/routes-einvoice.js +124 -0
- package/dist/l10n_in/routes.d.ts +17 -0
- package/dist/l10n_in/routes.js +282 -0
- package/dist/l10n_in/web/EInvoicePanel.d.ts +8 -0
- package/dist/l10n_in/web/EInvoicePanel.js +63 -0
- package/dist/l10n_in/web/GstPage.d.ts +7 -0
- package/dist/l10n_in/web/GstPage.js +57 -0
- package/dist/l10n_in/web/GstSettings.d.ts +10 -0
- package/dist/l10n_in/web/GstSettings.js +36 -0
- package/dist/l10n_in/web/index.d.ts +15 -0
- package/dist/l10n_in/web/index.js +22 -0
- package/dist/l10n_in/web/messages.d.ts +2 -0
- package/dist/l10n_in/web/messages.js +166 -0
- package/dist/mail/index.js +2 -2
- package/dist/modules-sdk/demo.d.ts +2 -0
- package/dist/modules-sdk/demo.js +7 -3
- package/dist/modules-sdk/index.d.ts +2 -2
- package/dist/modules-sdk/index.js +1 -1
- package/dist/modules-sdk/loader.js +2 -2
- package/dist/modules-sdk/manifest.d.ts +132 -0
- package/dist/modules-sdk/manifest.js +22 -0
- package/dist/notify/inbox.js +21 -0
- package/dist/notify/messages.js +7 -3
- package/dist/notify/models.d.ts +1 -1
- package/dist/notify/models.js +2 -0
- package/dist/notify/routes.js +1 -1
- package/dist/notify/web/messages.js +5 -1
- package/dist/portal/.kosh/bindings.d.ts +22 -0
- package/dist/portal/.kosh/bindings.js +15 -0
- package/dist/portal/.kosh/schema.d.ts +376 -0
- package/dist/portal/announce.d.ts +16 -0
- package/dist/portal/announce.js +117 -0
- package/dist/portal/documents.d.ts +17 -0
- package/dist/portal/documents.js +61 -0
- package/dist/portal/index.d.ts +20 -0
- package/dist/portal/index.js +65 -0
- package/dist/portal/module.d.ts +1405 -0
- package/dist/portal/module.js +84 -0
- package/dist/portal/routes-documents.d.ts +2 -0
- package/dist/portal/routes-documents.js +391 -0
- package/dist/portal/routes.d.ts +13 -0
- package/dist/portal/routes.js +313 -0
- package/dist/portal/rules.d.ts +11 -0
- package/dist/portal/rules.js +25 -0
- package/dist/portal/web/PortalAccess.d.ts +8 -0
- package/dist/portal/web/PortalAccess.js +66 -0
- package/dist/portal/web/index.d.ts +12 -0
- package/dist/portal/web/index.js +17 -0
- package/dist/portal/web/messages.d.ts +2 -0
- package/dist/portal/web/messages.js +33 -0
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +42 -3
- package/dist/product/demo.js +6 -5
- package/dist/product/index.d.ts +1 -0
- package/dist/product/index.js +17 -4
- package/dist/product/module.d.ts +346 -14
- package/dist/product/module.js +48 -18
- package/dist/product/taxes.d.ts +59 -0
- package/dist/product/taxes.js +61 -0
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +33 -1
- package/dist/project/module.d.ts +20 -0
- package/dist/project_portal/.kosh/bindings.d.ts +22 -0
- package/dist/project_portal/.kosh/bindings.js +15 -0
- package/dist/project_portal/.kosh/schema.d.ts +701 -0
- package/dist/project_portal/index.d.ts +9 -0
- package/dist/project_portal/index.js +42 -0
- package/dist/project_portal/module.d.ts +2142 -0
- package/dist/project_portal/module.js +22 -0
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +45 -4
- package/dist/purchase/demo.js +3 -3
- package/dist/purchase/index.js +1 -1
- package/dist/purchase/module.d.ts +232 -8
- package/dist/purchase/module.js +51 -9
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +174 -10
- package/dist/purchase_account/module.d.ts +2008 -641
- package/dist/purchase_account/module.js +2 -2
- package/dist/purchase_portal/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_portal/.kosh/bindings.js +15 -0
- package/dist/purchase_portal/.kosh/schema.d.ts +852 -0
- package/dist/purchase_portal/index.d.ts +9 -0
- package/dist/purchase_portal/index.js +53 -0
- package/dist/purchase_portal/module.d.ts +2758 -0
- package/dist/purchase_portal/module.js +18 -0
- package/dist/reporting/documents.js +20 -3
- package/dist/reporting/index.d.ts +1 -0
- package/dist/reporting/index.js +1 -0
- package/dist/reporting/routes-documents.js +9 -2
- package/dist/reporting/words.d.ts +11 -0
- package/dist/reporting/words.js +229 -0
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +174 -10
- package/dist/sale_account/module.d.ts +2008 -641
- package/dist/sale_account/module.js +3 -3
- package/dist/sale_portal/.kosh/bindings.d.ts +22 -0
- package/dist/sale_portal/.kosh/bindings.js +15 -0
- package/dist/sale_portal/.kosh/schema.d.ts +766 -0
- package/dist/sale_portal/index.d.ts +9 -0
- package/dist/sale_portal/index.js +71 -0
- package/dist/sale_portal/module.d.ts +2718 -0
- package/dist/sale_portal/module.js +19 -0
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +45 -4
- package/dist/sale_project/module.d.ts +390 -10
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +45 -4
- package/dist/sales/demo.js +3 -3
- package/dist/sales/index.js +67 -1
- package/dist/sales/module.d.ts +232 -8
- package/dist/sales/module.js +51 -12
- package/dist/server/api/client.js +1 -1
- package/dist/server/api/openapi.js +17 -1
- package/dist/server/apps.js +5 -5
- package/dist/server/auth/api-keys.d.ts +13 -0
- package/dist/server/auth/api-keys.js +65 -0
- package/dist/server/auth/base.js +26 -0
- package/dist/server/auth/crypto.js +1 -1
- package/dist/server/auth/scopes.d.ts +12 -0
- package/dist/server/auth/scopes.js +51 -0
- package/dist/server/auth/service.d.ts +59 -1
- package/dist/server/auth/service.js +236 -6
- package/dist/server/demo.d.ts +1 -1
- package/dist/server/demo.js +1 -1
- package/dist/server/env-names.d.ts +2 -0
- package/dist/server/env-names.js +16 -0
- package/dist/server/host.js +5 -1
- package/dist/server/identity.d.ts +7 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +2 -1
- package/dist/server/registry.js +1 -1
- package/dist/server/server.d.ts +5 -0
- package/dist/server/server.js +71 -6
- package/dist/settings/routes.js +1 -1
- package/dist/settings/web/ApiKeysPage.d.ts +8 -0
- package/dist/settings/web/ApiKeysPage.js +70 -0
- package/dist/settings/web/index.d.ts +1 -0
- package/dist/settings/web/index.js +4 -0
- package/dist/settings/web/messages.js +62 -0
- package/dist/storage/routes.js +1 -1
- package/dist/studio/build.js +1 -1
- package/dist/studio/generate.js +4 -4
- package/dist/studio/module.js +5 -5
- package/dist/studio/web/messages.js +2 -2
- package/dist/ui/api.d.ts +2 -0
- package/dist/ui/app.js +27 -4
- package/dist/ui/context.d.ts +3 -1
- package/dist/ui/context.js +2 -2
- package/dist/ui/i18n/messages.js +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/lib/format.d.ts +2 -0
- package/dist/ui/lib/format.js +3 -1
- package/dist/ui/pwa/worker.js +2 -2
- package/dist/ui/registry.d.ts +15 -0
- package/dist/ui/registry.js +20 -0
- package/dist/ui/views/FormView.js +19 -6
- package/package.json +52 -4
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GST returns of a month (RFC 0016 step 5, part 3), from the posted documents: GSTR-1 (outward supplies by the
|
|
3
|
+
* portal's sections — B2B by recipient GSTIN, B2C large and small, exports, credit and debit notes, nil-rated /
|
|
4
|
+
* exempt / non-GST, the HSN summary) and GSTR-3B (3.1 outward and reverse-charge inward supplies, 4 input tax credit,
|
|
5
|
+
* 5 exempt inward supplies, 6.1 the tax payable by head). Filing stays on the GST portal: GSTR-1 also comes as the
|
|
6
|
+
* portal's offline JSON. Amounts are in rupees: a foreign-currency line converts through its posted balance, as its
|
|
7
|
+
* taxes do. A line's band is the GST treatment of its GST tax; a line without one is listed as unclassified.
|
|
8
|
+
*/
|
|
9
|
+
import { supplyScope } from '../accounting/index.js';
|
|
10
|
+
import { orm } from '../server/orm.js';
|
|
11
|
+
import { GST_STATE_CODES } from './module.js';
|
|
12
|
+
const ZERO = orm.toDecimal(0);
|
|
13
|
+
const round = (d) => d.toDecimalPlaces(2, orm.Decimal.ROUND_HALF_UP);
|
|
14
|
+
/** B2C invoices between states above this total are reported one by one (B2CL), in rupees. */
|
|
15
|
+
export const B2CL_THRESHOLD = orm.toDecimal(100000);
|
|
16
|
+
export class MonthError extends Error {
|
|
17
|
+
}
|
|
18
|
+
/** The first and last day of `month` (`2026-10`). */
|
|
19
|
+
export function monthRange(month) {
|
|
20
|
+
const match = /^(\d{4})-(0[1-9]|1[0-2])$/.exec(month);
|
|
21
|
+
if (match === null)
|
|
22
|
+
throw new MonthError('A month is written 2026-10.');
|
|
23
|
+
const last = new Date(Date.UTC(Number(match[1]), Number(match[2]), 0)).getUTCDate();
|
|
24
|
+
return { from: `${month}-01`, to: `${month}-${String(last).padStart(2, '0')}` };
|
|
25
|
+
}
|
|
26
|
+
const NONE = { txval: ZERO, iamt: ZERO, camt: ZERO, samt: ZERO, csamt: ZERO };
|
|
27
|
+
const add = (a, b) => ({
|
|
28
|
+
txval: a.txval.plus(b.txval),
|
|
29
|
+
iamt: a.iamt.plus(b.iamt),
|
|
30
|
+
camt: a.camt.plus(b.camt),
|
|
31
|
+
samt: a.samt.plus(b.samt),
|
|
32
|
+
csamt: a.csamt.plus(b.csamt),
|
|
33
|
+
});
|
|
34
|
+
const neg = (h) => ({
|
|
35
|
+
txval: h.txval.neg(),
|
|
36
|
+
iamt: h.iamt.neg(),
|
|
37
|
+
camt: h.camt.neg(),
|
|
38
|
+
samt: h.samt.neg(),
|
|
39
|
+
csamt: h.csamt.neg(),
|
|
40
|
+
});
|
|
41
|
+
/** The company of `env`: its GSTIN, country and state. */
|
|
42
|
+
async function companyOf(env) {
|
|
43
|
+
const [c] = await env.model('res.company').search({ where: { id: env.companyRef }, limit: 1 });
|
|
44
|
+
return {
|
|
45
|
+
gstin: c?.gstin ?? null,
|
|
46
|
+
country: c?.country ?? null,
|
|
47
|
+
state: c?.state ?? null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function documents(env, kinds, month) {
|
|
51
|
+
const { from, to } = monthRange(month);
|
|
52
|
+
const company = await companyOf(env);
|
|
53
|
+
const moves = await env.model('account.move').search({
|
|
54
|
+
where: {
|
|
55
|
+
kind: { in: kinds },
|
|
56
|
+
state: 'posted',
|
|
57
|
+
date: { gte: from, lte: to },
|
|
58
|
+
},
|
|
59
|
+
order: ['date asc', 'name asc'],
|
|
60
|
+
});
|
|
61
|
+
const loaded = await moves.load({
|
|
62
|
+
partner: ['name', 'gstin', 'country', 'state'],
|
|
63
|
+
reversalOf: ['name', 'date'],
|
|
64
|
+
debitOf: ['name', 'date'],
|
|
65
|
+
invoiceLines: { taxes: ['gstTreatment', 'reverseCharge'], product: ['hsnCode'] },
|
|
66
|
+
});
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const m of loaded) {
|
|
69
|
+
const partnerCountry = m.partner?.country ?? null;
|
|
70
|
+
const pos = m.placeOfSupply ?? m.partner?.state ?? null;
|
|
71
|
+
const exported = partnerCountry !== null && company.country !== null && partnerCountry !== company.country;
|
|
72
|
+
const lines = [];
|
|
73
|
+
for (const l of m.invoiceLines) {
|
|
74
|
+
if (l.lineKind !== 'product')
|
|
75
|
+
continue;
|
|
76
|
+
const subtotal = l.subtotal?.amount ?? ZERO;
|
|
77
|
+
// Rupees: the posted balance, and the taxes at its rate.
|
|
78
|
+
const txval = (l.balance?.amount ?? ZERO).abs();
|
|
79
|
+
const rate = subtotal.isZero() ? orm.toDecimal(1) : txval.div(subtotal.abs());
|
|
80
|
+
const head = (d) => round((d ?? ZERO).times(rate));
|
|
81
|
+
const treatments = [...l.taxes]
|
|
82
|
+
.map((t) => t.gstTreatment)
|
|
83
|
+
.filter((t) => t !== null && t !== 'cess');
|
|
84
|
+
lines.push({
|
|
85
|
+
txval,
|
|
86
|
+
iamt: head(l.igstAmount?.amount),
|
|
87
|
+
camt: head(l.cgstAmount?.amount),
|
|
88
|
+
samt: head(l.sgstAmount?.amount),
|
|
89
|
+
csamt: head(l.cessAmount?.amount),
|
|
90
|
+
treatment: treatments[0] ?? 'unclassified',
|
|
91
|
+
rate: l.gstRate ?? ZERO,
|
|
92
|
+
reverseCharge: [...l.taxes].some((t) => t.reverseCharge),
|
|
93
|
+
hsn: l.product?.hsnCode ?? null,
|
|
94
|
+
description: l.name ?? '',
|
|
95
|
+
quantity: l.quantity ?? ZERO,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const original = m.reversalOf ?? m.debitOf;
|
|
99
|
+
out.push({
|
|
100
|
+
kind: m.kind,
|
|
101
|
+
number: m.name ?? '',
|
|
102
|
+
date: String(m.date),
|
|
103
|
+
partner: m.partner?.name ?? '',
|
|
104
|
+
gstin: m.partner?.gstin ?? null,
|
|
105
|
+
export: exported,
|
|
106
|
+
inter: supplyScope(company, partnerCountry, pos) === 'inter',
|
|
107
|
+
pos,
|
|
108
|
+
posCode: exported ? '96' : ((pos === null ? undefined : GST_STATE_CODES[pos]) ?? ''),
|
|
109
|
+
original: original === null ? null : { number: original.name ?? '', date: String(original.date) },
|
|
110
|
+
lines,
|
|
111
|
+
value: lines.reduce((s, l) => s.plus(l.txval).plus(l.iamt).plus(l.camt).plus(l.samt).plus(l.csamt), ZERO),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
function itemsByRate(lines) {
|
|
117
|
+
const byRate = new Map();
|
|
118
|
+
for (const l of lines) {
|
|
119
|
+
const key = l.rate.toString();
|
|
120
|
+
const prev = byRate.get(key) ?? { ...NONE, rate: l.rate };
|
|
121
|
+
byRate.set(key, { ...add(prev, l), rate: l.rate });
|
|
122
|
+
}
|
|
123
|
+
return [...byRate.values()];
|
|
124
|
+
}
|
|
125
|
+
/** GSTR-1 of `month`. */
|
|
126
|
+
export async function gstr1(env, month) {
|
|
127
|
+
const company = await companyOf(env);
|
|
128
|
+
const docs = await documents(env, ['out_invoice', 'out_debit', 'out_refund'], month);
|
|
129
|
+
const b2b = [];
|
|
130
|
+
const b2cl = [];
|
|
131
|
+
const exp = [];
|
|
132
|
+
const cdnr = [];
|
|
133
|
+
const cdnur = [];
|
|
134
|
+
const b2cs = new Map();
|
|
135
|
+
const nil = new Map();
|
|
136
|
+
const hsn = new Map();
|
|
137
|
+
let unclassified = 0;
|
|
138
|
+
for (const d of docs) {
|
|
139
|
+
const note = d.kind === 'out_refund' ? 'C' : d.kind === 'out_debit' ? 'D' : undefined;
|
|
140
|
+
const sign = note === 'C' ? -1 : 1;
|
|
141
|
+
const taxable = d.lines.filter((l) => l.treatment === 'taxable' || l.treatment === 'zero_rated');
|
|
142
|
+
unclassified += d.lines.filter((l) => l.treatment === 'unclassified').length;
|
|
143
|
+
// Nil-rated, exempt and non-GST supplies: summed by inter / intra and registered / not.
|
|
144
|
+
for (const l of d.lines) {
|
|
145
|
+
if (l.treatment !== 'nil' && l.treatment !== 'exempt' && l.treatment !== 'non_gst')
|
|
146
|
+
continue;
|
|
147
|
+
const supply = `${d.inter ? 'INTR' : 'INTRA'}${d.gstin === null ? 'B2C' : 'B2B'}`;
|
|
148
|
+
const row = nil.get(supply) ?? { supply, nil: ZERO, exempt: ZERO, nonGst: ZERO };
|
|
149
|
+
const v = l.txval.times(sign);
|
|
150
|
+
if (l.treatment === 'nil')
|
|
151
|
+
row.nil = row.nil.plus(v);
|
|
152
|
+
else if (l.treatment === 'exempt')
|
|
153
|
+
row.exempt = row.exempt.plus(v);
|
|
154
|
+
else
|
|
155
|
+
row.nonGst = row.nonGst.plus(v);
|
|
156
|
+
nil.set(supply, row);
|
|
157
|
+
}
|
|
158
|
+
// The HSN summary: every taxable line, notes against it.
|
|
159
|
+
for (const l of taxable) {
|
|
160
|
+
const code = l.hsn ?? '';
|
|
161
|
+
const key = `${code}|${l.rate.toString()}`;
|
|
162
|
+
const prev = hsn.get(key) ?? {
|
|
163
|
+
...NONE,
|
|
164
|
+
hsn: code,
|
|
165
|
+
description: l.description,
|
|
166
|
+
quantity: ZERO,
|
|
167
|
+
rate: l.rate,
|
|
168
|
+
};
|
|
169
|
+
const signed = sign === 1 ? l : neg(l);
|
|
170
|
+
hsn.set(key, {
|
|
171
|
+
...add(prev, signed),
|
|
172
|
+
hsn: code,
|
|
173
|
+
description: prev.description,
|
|
174
|
+
quantity: prev.quantity.plus(l.quantity.times(sign)),
|
|
175
|
+
rate: l.rate,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (taxable.length === 0)
|
|
179
|
+
continue;
|
|
180
|
+
const doc = {
|
|
181
|
+
number: d.number,
|
|
182
|
+
date: d.date,
|
|
183
|
+
partner: d.partner,
|
|
184
|
+
gstin: d.gstin,
|
|
185
|
+
pos: d.posCode,
|
|
186
|
+
value: d.value,
|
|
187
|
+
reverseCharge: taxable.some((l) => l.reverseCharge),
|
|
188
|
+
items: itemsByRate(taxable),
|
|
189
|
+
...(note === undefined ? {} : { noteType: note, original: d.original }),
|
|
190
|
+
...(d.export
|
|
191
|
+
? {
|
|
192
|
+
exportType: taxable.some((l) => l.treatment === 'taxable' && !l.iamt.isZero())
|
|
193
|
+
? 'WPAY'
|
|
194
|
+
: 'WOPAY',
|
|
195
|
+
}
|
|
196
|
+
: {}),
|
|
197
|
+
};
|
|
198
|
+
if (note !== undefined) {
|
|
199
|
+
if (d.gstin !== null)
|
|
200
|
+
cdnr.push(doc);
|
|
201
|
+
else if (d.export || (d.inter && d.value.gt(B2CL_THRESHOLD)))
|
|
202
|
+
cdnur.push(doc);
|
|
203
|
+
else
|
|
204
|
+
for (const item of doc.items)
|
|
205
|
+
addB2cs(b2cs, d, sign, item);
|
|
206
|
+
}
|
|
207
|
+
else if (d.gstin !== null)
|
|
208
|
+
b2b.push(doc);
|
|
209
|
+
else if (d.export)
|
|
210
|
+
exp.push(doc);
|
|
211
|
+
else if (d.inter && d.value.gt(B2CL_THRESHOLD))
|
|
212
|
+
b2cl.push(doc);
|
|
213
|
+
else
|
|
214
|
+
for (const item of doc.items)
|
|
215
|
+
addB2cs(b2cs, d, sign, item);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
month,
|
|
219
|
+
gstin: company.gstin,
|
|
220
|
+
b2b,
|
|
221
|
+
b2cl,
|
|
222
|
+
b2cs: [...b2cs.values()],
|
|
223
|
+
exp,
|
|
224
|
+
cdnr,
|
|
225
|
+
cdnur,
|
|
226
|
+
nil: [...nil.values()],
|
|
227
|
+
hsn: [...hsn.values()],
|
|
228
|
+
unclassified,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
function addB2cs(into, d, sign, item) {
|
|
232
|
+
const supply = d.inter ? 'INTER' : 'INTRA';
|
|
233
|
+
const key = `${supply}|${d.posCode}|${item.rate.toString()}`;
|
|
234
|
+
const prev = into.get(key) ?? { ...NONE, supply, pos: d.posCode, rate: item.rate };
|
|
235
|
+
into.set(key, {
|
|
236
|
+
...add(prev, sign === 1 ? item : neg(item)),
|
|
237
|
+
supply,
|
|
238
|
+
pos: d.posCode,
|
|
239
|
+
rate: item.rate,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
/** GSTR-3B of `month`. */
|
|
243
|
+
export async function gstr3b(env, month) {
|
|
244
|
+
const company = await companyOf(env);
|
|
245
|
+
let outwardTaxable = NONE;
|
|
246
|
+
let outwardZeroRated = NONE;
|
|
247
|
+
let outwardNilExempt = NONE;
|
|
248
|
+
let outwardNonGst = NONE;
|
|
249
|
+
let inwardReverseCharge = NONE;
|
|
250
|
+
let itcOther = NONE;
|
|
251
|
+
let inwardInter = ZERO;
|
|
252
|
+
let inwardIntra = ZERO;
|
|
253
|
+
for (const d of await documents(env, ['out_invoice', 'out_debit', 'out_refund'], month)) {
|
|
254
|
+
const sign = d.kind === 'out_refund' ? -1 : 1;
|
|
255
|
+
for (const l of d.lines) {
|
|
256
|
+
const signed = sign === 1 ? l : neg(l);
|
|
257
|
+
if (l.treatment === 'zero_rated' || (l.treatment === 'taxable' && d.export))
|
|
258
|
+
outwardZeroRated = add(outwardZeroRated, signed);
|
|
259
|
+
else if (l.treatment === 'taxable' && !l.reverseCharge)
|
|
260
|
+
outwardTaxable = add(outwardTaxable, signed);
|
|
261
|
+
else if (l.treatment === 'nil' || l.treatment === 'exempt')
|
|
262
|
+
outwardNilExempt = add(outwardNilExempt, { ...NONE, txval: signed.txval });
|
|
263
|
+
else if (l.treatment === 'non_gst')
|
|
264
|
+
outwardNonGst = add(outwardNonGst, { ...NONE, txval: signed.txval });
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const d of await documents(env, ['in_invoice', 'in_debit', 'in_refund'], month)) {
|
|
268
|
+
const sign = d.kind === 'in_refund' ? -1 : 1;
|
|
269
|
+
for (const l of d.lines) {
|
|
270
|
+
const signed = sign === 1 ? l : neg(l);
|
|
271
|
+
if (l.treatment === 'taxable' && l.reverseCharge)
|
|
272
|
+
inwardReverseCharge = add(inwardReverseCharge, signed);
|
|
273
|
+
else if (l.treatment === 'taxable')
|
|
274
|
+
itcOther = add(itcOther, signed);
|
|
275
|
+
else if (l.treatment === 'nil' || l.treatment === 'exempt' || l.treatment === 'non_gst') {
|
|
276
|
+
if (d.inter)
|
|
277
|
+
inwardInter = inwardInter.plus(signed.txval);
|
|
278
|
+
else
|
|
279
|
+
inwardIntra = inwardIntra.plus(signed.txval);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const itcReverseCharge = { ...inwardReverseCharge, txval: ZERO };
|
|
284
|
+
const head = (key) => {
|
|
285
|
+
const tax = outwardTaxable[key].plus(outwardZeroRated[key]).plus(inwardReverseCharge[key]);
|
|
286
|
+
const credit = itcReverseCharge[key].plus(itcOther[key]);
|
|
287
|
+
return { tax, credit, net: tax.minus(credit) };
|
|
288
|
+
};
|
|
289
|
+
return {
|
|
290
|
+
month,
|
|
291
|
+
gstin: company.gstin,
|
|
292
|
+
outwardTaxable,
|
|
293
|
+
outwardZeroRated,
|
|
294
|
+
outwardNilExempt,
|
|
295
|
+
inwardReverseCharge,
|
|
296
|
+
outwardNonGst,
|
|
297
|
+
itcReverseCharge,
|
|
298
|
+
itcOther: { ...itcOther, txval: ZERO },
|
|
299
|
+
inwardExempt: { inter: inwardInter, intra: inwardIntra },
|
|
300
|
+
payable: { igst: head('iamt'), cgst: head('camt'), sgst: head('samt'), cess: head('csamt') },
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const money = (d) => Number(d.toFixed(2));
|
|
304
|
+
const portalDate = (iso) => `${iso.slice(8, 10)}-${iso.slice(5, 7)}-${iso.slice(0, 4)}`;
|
|
305
|
+
const portalItems = (items, exportOnly = false) => items.map((it, i) => ({
|
|
306
|
+
num: i + 1,
|
|
307
|
+
itm_det: exportOnly
|
|
308
|
+
? {
|
|
309
|
+
txval: money(it.txval),
|
|
310
|
+
rt: Number(it.rate.toString()),
|
|
311
|
+
iamt: money(it.iamt),
|
|
312
|
+
csamt: money(it.csamt),
|
|
313
|
+
}
|
|
314
|
+
: {
|
|
315
|
+
txval: money(it.txval),
|
|
316
|
+
rt: Number(it.rate.toString()),
|
|
317
|
+
iamt: money(it.iamt),
|
|
318
|
+
camt: money(it.camt),
|
|
319
|
+
samt: money(it.samt),
|
|
320
|
+
csamt: money(it.csamt),
|
|
321
|
+
},
|
|
322
|
+
}));
|
|
323
|
+
/**
|
|
324
|
+
* GSTR-1 as the GST portal's offline JSON (the sections b2b, b2cl, b2cs, exp, cdnr, cdnur, nil, hsn). Check it against
|
|
325
|
+
* the current offline tool's schema before uploading: the portal changes it from time to time.
|
|
326
|
+
*/
|
|
327
|
+
export function gstr1PortalJson(r) {
|
|
328
|
+
const [y, m] = r.month.split('-');
|
|
329
|
+
const byGstin = (docs, key, note) => {
|
|
330
|
+
const out = new Map();
|
|
331
|
+
for (const d of docs) {
|
|
332
|
+
const list = out.get(d.gstin ?? '') ?? [];
|
|
333
|
+
list.push(note
|
|
334
|
+
? {
|
|
335
|
+
ntty: d.noteType,
|
|
336
|
+
nt_num: d.number,
|
|
337
|
+
nt_dt: portalDate(d.date),
|
|
338
|
+
val: money(d.value),
|
|
339
|
+
pos: d.pos,
|
|
340
|
+
rchrg: d.reverseCharge ? 'Y' : 'N',
|
|
341
|
+
inv_typ: 'R',
|
|
342
|
+
itms: portalItems(d.items),
|
|
343
|
+
}
|
|
344
|
+
: {
|
|
345
|
+
inum: d.number,
|
|
346
|
+
idt: portalDate(d.date),
|
|
347
|
+
val: money(d.value),
|
|
348
|
+
pos: d.pos,
|
|
349
|
+
rchrg: d.reverseCharge ? 'Y' : 'N',
|
|
350
|
+
inv_typ: 'R',
|
|
351
|
+
itms: portalItems(d.items),
|
|
352
|
+
});
|
|
353
|
+
out.set(d.gstin ?? '', list);
|
|
354
|
+
}
|
|
355
|
+
return [...out.entries()].map(([ctin, list]) => ({ ctin, [key]: list }));
|
|
356
|
+
};
|
|
357
|
+
const b2clByPos = new Map();
|
|
358
|
+
for (const d of r.b2cl) {
|
|
359
|
+
const list = b2clByPos.get(d.pos) ?? [];
|
|
360
|
+
list.push({
|
|
361
|
+
inum: d.number,
|
|
362
|
+
idt: portalDate(d.date),
|
|
363
|
+
val: money(d.value),
|
|
364
|
+
itms: portalItems(d.items),
|
|
365
|
+
});
|
|
366
|
+
b2clByPos.set(d.pos, list);
|
|
367
|
+
}
|
|
368
|
+
const expByType = new Map();
|
|
369
|
+
for (const d of r.exp) {
|
|
370
|
+
const list = expByType.get(d.exportType ?? 'WOPAY') ?? [];
|
|
371
|
+
list.push({
|
|
372
|
+
inum: d.number,
|
|
373
|
+
idt: portalDate(d.date),
|
|
374
|
+
val: money(d.value),
|
|
375
|
+
itms: portalItems(d.items, true),
|
|
376
|
+
});
|
|
377
|
+
expByType.set(d.exportType ?? 'WOPAY', list);
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
gstin: r.gstin ?? '',
|
|
381
|
+
fp: `${m}${y}`,
|
|
382
|
+
b2b: byGstin(r.b2b, 'inv', false),
|
|
383
|
+
b2cl: [...b2clByPos.entries()].map(([pos, inv]) => ({ pos, inv })),
|
|
384
|
+
b2cs: r.b2cs.map((s) => ({
|
|
385
|
+
sply_ty: s.supply,
|
|
386
|
+
pos: s.pos,
|
|
387
|
+
typ: 'OE',
|
|
388
|
+
rt: Number(s.rate.toString()),
|
|
389
|
+
txval: money(s.txval),
|
|
390
|
+
iamt: money(s.iamt),
|
|
391
|
+
camt: money(s.camt),
|
|
392
|
+
samt: money(s.samt),
|
|
393
|
+
csamt: money(s.csamt),
|
|
394
|
+
})),
|
|
395
|
+
exp: [...expByType.entries()].map(([exp_typ, inv]) => ({ exp_typ, inv })),
|
|
396
|
+
cdnr: byGstin(r.cdnr, 'nt', true),
|
|
397
|
+
cdnur: r.cdnur.map((d) => ({
|
|
398
|
+
typ: d.exportType === undefined ? 'B2CL' : d.exportType === 'WPAY' ? 'EXPWP' : 'EXPWOP',
|
|
399
|
+
ntty: d.noteType,
|
|
400
|
+
nt_num: d.number,
|
|
401
|
+
nt_dt: portalDate(d.date),
|
|
402
|
+
val: money(d.value),
|
|
403
|
+
pos: d.pos,
|
|
404
|
+
itms: portalItems(d.items),
|
|
405
|
+
})),
|
|
406
|
+
nil: {
|
|
407
|
+
inv: r.nil.map((n) => ({
|
|
408
|
+
sply_ty: n.supply,
|
|
409
|
+
nil_amt: money(n.nil),
|
|
410
|
+
expt_amt: money(n.exempt),
|
|
411
|
+
ngsup_amt: money(n.nonGst),
|
|
412
|
+
})),
|
|
413
|
+
},
|
|
414
|
+
hsn: {
|
|
415
|
+
data: r.hsn.map((h, i) => ({
|
|
416
|
+
num: i + 1,
|
|
417
|
+
hsn_sc: h.hsn,
|
|
418
|
+
desc: h.description,
|
|
419
|
+
uqc: 'NOS',
|
|
420
|
+
qty: Number(h.quantity.toString()),
|
|
421
|
+
rt: Number(h.rate.toString()),
|
|
422
|
+
txval: money(h.txval),
|
|
423
|
+
iamt: money(h.iamt),
|
|
424
|
+
camt: money(h.camt),
|
|
425
|
+
samt: money(h.samt),
|
|
426
|
+
csamt: money(h.csamt),
|
|
427
|
+
})),
|
|
428
|
+
},
|
|
429
|
+
};
|
|
430
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type KoshModule, type LocalizationSpec } from '../modules-sdk/index.js';
|
|
2
|
+
import { type GstProvider } from './einvoice.js';
|
|
3
|
+
export { GST_DOCUMENTS } from './documents.js';
|
|
4
|
+
export { CANCEL_REASONS, cancelEInvoice, cancelEwayBill, createFakeGstProvider, type EInvoicePayload, type EwayBillPayload, einvoicePayload, einvoiceState, financialYear, type GstProvider, GstProviderError, generateEwayBill, gstProviderFromEnv, irnOf, registerEInvoice, } from './einvoice.js';
|
|
5
|
+
export { B2CL_THRESHOLD, type Gstr1, type Gstr1Document, type Gstr3b, gstr1, gstr1PortalJson, gstr3b, type Heads, MonthError, monthRange, } from './gstr.js';
|
|
6
|
+
export { CESS_RATES, CompanyGst, GST_RATES, GST_STATE_CODES, GST_TREATMENTS, type GstAmounts, gstAmounts, gstinCheckDigit, gstinProblem, LineGst, l10nInShutro, MoveGst, PartnerGst, ProductHsn, TAXES, TaxGst, } from './module.js';
|
|
7
|
+
export { registerGstRoutes } from './routes.js';
|
|
8
|
+
export { einvoiceWorker, registerEInvoiceRoutes } from './routes-einvoice.js';
|
|
9
|
+
/** India, as Accounting installs it for a company. */
|
|
10
|
+
export declare const INDIA: LocalizationSpec;
|
|
11
|
+
/** What an installation may set instead of the environment (`KOSH_GST_PROVIDER`). */
|
|
12
|
+
export interface L10nInOptions {
|
|
13
|
+
/** The IRP / e-way bill provider; default: the environment's, or none (e-invoicing unavailable). */
|
|
14
|
+
readonly provider?: GstProvider | undefined;
|
|
15
|
+
/** Start the subscriber registering posted documents (default: unless `KOSH_SCHEDULES=off`). */
|
|
16
|
+
readonly workers?: boolean | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** The India localization with an explicit provider (tests, installations wiring their own). */
|
|
19
|
+
export declare function l10nInModule(options?: L10nInOptions): KoshModule;
|
|
20
|
+
export declare const l10nIn: KoshModule;
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
* `shutro-erp/l10n_in` (RFC 0016 step 5): Kosh for a company in India. Installing it sets each company without a chart up
|
|
4
|
+
* for the country: the chart of accounts (CGST, SGST, IGST and cess payable and input credit, GST under reverse
|
|
5
|
+
* charge, TDS), the accounts of the GST taxes and their components, the TDS withholdings, an April–March fiscal
|
|
6
|
+
* year and lakh / crore grouping. Contacts and companies get their GSTIN and PAN, products their HSN / SAC code.
|
|
7
|
+
* The company's state (Accounting › Settings) decides which invoices are within the state.
|
|
8
|
+
*
|
|
9
|
+
* Rates are those of the GST slabs and the income tax TDS sections as commonly applied; rates and thresholds change
|
|
10
|
+
* with GST Council decisions and each Finance Act: check them in Accounting › Configuration.
|
|
11
|
+
*/
|
|
12
|
+
import { accounting, COMMON_ACCOUNTS, DEFAULT_ACCOUNTS, installedChart, installLocalization, } from '../accounting/index.js';
|
|
13
|
+
import { contacts } from '../contacts/index.js';
|
|
14
|
+
import { defineModule } from '../modules-sdk/index.js';
|
|
15
|
+
import { field } from '../views/index.js';
|
|
16
|
+
import { GST_DOCUMENTS } from './documents.js';
|
|
17
|
+
import { gstProviderFromEnv } from './einvoice.js';
|
|
18
|
+
import { CESS_RATES, GST_RATES, l10nInShutro } from './module.js';
|
|
19
|
+
import { registerGstRoutes } from './routes.js';
|
|
20
|
+
import { einvoiceWorker, registerEInvoiceRoutes } from './routes-einvoice.js';
|
|
21
|
+
export { GST_DOCUMENTS } from './documents.js';
|
|
22
|
+
export { CANCEL_REASONS, cancelEInvoice, cancelEwayBill, createFakeGstProvider, einvoicePayload, einvoiceState, financialYear, GstProviderError, generateEwayBill, gstProviderFromEnv, irnOf, registerEInvoice, } from './einvoice.js';
|
|
23
|
+
export { B2CL_THRESHOLD, gstr1, gstr1PortalJson, gstr3b, MonthError, monthRange, } from './gstr.js';
|
|
24
|
+
export { CESS_RATES, CompanyGst, GST_RATES, GST_STATE_CODES, GST_TREATMENTS, gstAmounts, gstinCheckDigit, gstinProblem, LineGst, l10nInShutro, MoveGst, PartnerGst, ProductHsn, TAXES, TaxGst, } from './module.js';
|
|
25
|
+
export { registerGstRoutes } from './routes.js';
|
|
26
|
+
export { einvoiceWorker, registerEInvoiceRoutes } from './routes-einvoice.js';
|
|
27
|
+
const id = (rate) => rate.replace('.', '_');
|
|
28
|
+
/** The accounts of every GST component: output tax on sales, input credit on purchases, both under reverse charge. */
|
|
29
|
+
function componentAccounts() {
|
|
30
|
+
const out = {};
|
|
31
|
+
const heads = [
|
|
32
|
+
['cgst', '2310', '1310', '2320'],
|
|
33
|
+
['sgst', '2311', '1311', '2321'],
|
|
34
|
+
['igst', '2312', '1312', '2322'],
|
|
35
|
+
];
|
|
36
|
+
for (const rate of GST_RATES)
|
|
37
|
+
for (const [head, output, input] of heads) {
|
|
38
|
+
out[`l10n_in.gst_sale_${id(rate)}_${head}`] = output;
|
|
39
|
+
out[`l10n_in.gst_purchase_${id(rate)}_${head}`] = input;
|
|
40
|
+
}
|
|
41
|
+
for (const rate of ['5', '12', '18', '28'])
|
|
42
|
+
for (const [head, , input, payable] of heads)
|
|
43
|
+
out[`l10n_in.gst_rc_${id(rate)}_${head}`] = { account: input, reverseCharge: payable };
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/** India, as Accounting installs it for a company. */
|
|
47
|
+
export const INDIA = {
|
|
48
|
+
key: 'in',
|
|
49
|
+
country: 'IN',
|
|
50
|
+
label: 'India',
|
|
51
|
+
chart: {
|
|
52
|
+
accounts: [
|
|
53
|
+
...COMMON_ACCOUNTS,
|
|
54
|
+
{ code: '2310', name: 'CGST payable', type: 'liability_current' },
|
|
55
|
+
{ code: '2311', name: 'SGST / UTGST payable', type: 'liability_current' },
|
|
56
|
+
{ code: '2312', name: 'IGST payable', type: 'liability_current' },
|
|
57
|
+
{ code: '2313', name: 'Compensation cess payable', type: 'liability_current' },
|
|
58
|
+
{ code: '1310', name: 'CGST input credit', type: 'asset_current' },
|
|
59
|
+
{ code: '1311', name: 'SGST / UTGST input credit', type: 'asset_current' },
|
|
60
|
+
{ code: '1312', name: 'IGST input credit', type: 'asset_current' },
|
|
61
|
+
{ code: '1313', name: 'Compensation cess input credit', type: 'asset_current' },
|
|
62
|
+
{ code: '2320', name: 'CGST payable under reverse charge', type: 'liability_current' },
|
|
63
|
+
{ code: '2321', name: 'SGST payable under reverse charge', type: 'liability_current' },
|
|
64
|
+
{ code: '2322', name: 'IGST payable under reverse charge', type: 'liability_current' },
|
|
65
|
+
{ code: '2330', name: 'TDS payable', type: 'liability_current' },
|
|
66
|
+
{ code: '1330', name: 'TDS receivable', type: 'asset_current' },
|
|
67
|
+
],
|
|
68
|
+
defaults: DEFAULT_ACCOUNTS,
|
|
69
|
+
taxAccounts: { sale: '2312', purchase: '1312' },
|
|
70
|
+
},
|
|
71
|
+
taxes: Object.fromEntries(CESS_RATES.flatMap((r) => [
|
|
72
|
+
[`l10n_in.cess_sale_${id(r)}`, '2313'],
|
|
73
|
+
[`l10n_in.cess_purchase_${id(r)}`, '1313'],
|
|
74
|
+
])),
|
|
75
|
+
components: componentAccounts(),
|
|
76
|
+
withholdings: [
|
|
77
|
+
// Income tax deducted at source when paying vendors (sections of the Income-tax Act; thresholds are annual or
|
|
78
|
+
// per payment by section: check them, they are not applied per document here).
|
|
79
|
+
{
|
|
80
|
+
name: 'TDS 194C 1% (contractors, individuals)',
|
|
81
|
+
direction: 'vendor',
|
|
82
|
+
base: 'untaxed',
|
|
83
|
+
rate: '0.01',
|
|
84
|
+
account: '2330',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'TDS 194C 2% (contractors, others)',
|
|
88
|
+
direction: 'vendor',
|
|
89
|
+
base: 'untaxed',
|
|
90
|
+
rate: '0.02',
|
|
91
|
+
account: '2330',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'TDS 194J 10% (professional fees)',
|
|
95
|
+
direction: 'vendor',
|
|
96
|
+
base: 'untaxed',
|
|
97
|
+
rate: '0.10',
|
|
98
|
+
account: '2330',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'TDS 194J 2% (technical services)',
|
|
102
|
+
direction: 'vendor',
|
|
103
|
+
base: 'untaxed',
|
|
104
|
+
rate: '0.02',
|
|
105
|
+
account: '2330',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'TDS 194H 2% (commission)',
|
|
109
|
+
direction: 'vendor',
|
|
110
|
+
base: 'untaxed',
|
|
111
|
+
rate: '0.02',
|
|
112
|
+
account: '2330',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'TDS 194-I 10% (rent of buildings)',
|
|
116
|
+
direction: 'vendor',
|
|
117
|
+
base: 'untaxed',
|
|
118
|
+
rate: '0.10',
|
|
119
|
+
account: '2330',
|
|
120
|
+
},
|
|
121
|
+
// What customers deduct from our invoices (claimed against our income tax).
|
|
122
|
+
{
|
|
123
|
+
name: 'TDS deducted by customers 2%',
|
|
124
|
+
direction: 'customer',
|
|
125
|
+
base: 'untaxed',
|
|
126
|
+
rate: '0.02',
|
|
127
|
+
account: '1330',
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
fiscalYearStart: 4,
|
|
131
|
+
grouping: 'lakh',
|
|
132
|
+
};
|
|
133
|
+
/** Sets up the company of `env` for India when it has no chart yet, or completes an India setup. */
|
|
134
|
+
async function localize(raw) {
|
|
135
|
+
// Accounting's environment: the installer reads Accounting's models.
|
|
136
|
+
const env = raw;
|
|
137
|
+
const companies = await env
|
|
138
|
+
.model('res.company')
|
|
139
|
+
.search({ where: { id: env.companyRef }, limit: 1 });
|
|
140
|
+
if (companies.length === 0)
|
|
141
|
+
return undefined; // no company yet (before installBase)
|
|
142
|
+
const chart = await installedChart(env);
|
|
143
|
+
if (chart !== null && chart !== INDIA.key)
|
|
144
|
+
return undefined; // another chart: the company's choice
|
|
145
|
+
const done = await installLocalization(env, INDIA);
|
|
146
|
+
return done.chart === null
|
|
147
|
+
? undefined
|
|
148
|
+
: `the India chart (${done.chart.accounts} accounts), ${done.taxes} taxes, ${done.withholdings} withholdings`;
|
|
149
|
+
}
|
|
150
|
+
/** The India localization with an explicit provider (tests, installations wiring their own). */
|
|
151
|
+
export function l10nInModule(options = {}) {
|
|
152
|
+
return defineModule({
|
|
153
|
+
shutro: l10nInShutro,
|
|
154
|
+
kosh: '^0.1.0',
|
|
155
|
+
depends: [accounting, contacts],
|
|
156
|
+
accessLevels: {
|
|
157
|
+
manager: { label: 'India localization', groups: ['account.manager'] },
|
|
158
|
+
},
|
|
159
|
+
localization: INDIA,
|
|
160
|
+
hooks: { defaults: (env) => localize(env) },
|
|
161
|
+
routes: (context) => {
|
|
162
|
+
registerGstRoutes(context);
|
|
163
|
+
const provider = options.provider ?? gstProviderFromEnv();
|
|
164
|
+
registerEInvoiceRoutes(context, provider);
|
|
165
|
+
if (provider === undefined || !(options.workers ?? process.env['KOSH_SCHEDULES'] !== 'off'))
|
|
166
|
+
return;
|
|
167
|
+
const worker = einvoiceWorker(context, provider);
|
|
168
|
+
worker.start();
|
|
169
|
+
context.app.addHook('onClose', async () => {
|
|
170
|
+
await worker.stop();
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
// Accounting › Reporting › GST returns: GSTR-1 and GSTR-3B.
|
|
174
|
+
actions: {
|
|
175
|
+
'l10n_in.action_gst': {
|
|
176
|
+
type: 'client',
|
|
177
|
+
page: 'l10n_in.Gst',
|
|
178
|
+
label: 'GST returns',
|
|
179
|
+
visibleTo: ['account.user'],
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
menus: [
|
|
183
|
+
{
|
|
184
|
+
id: 'l10n_in.gst',
|
|
185
|
+
parent: 'accounting.reporting',
|
|
186
|
+
label: 'GST returns',
|
|
187
|
+
action: 'l10n_in.action_gst',
|
|
188
|
+
sequence: 8,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
// The tax invoice, the credit and debit notes (printed when Reporting is installed).
|
|
192
|
+
documents: GST_DOCUMENTS,
|
|
193
|
+
viewExtensions: [
|
|
194
|
+
// The GSTIN and PAN with a contact's identity, for contacts in India.
|
|
195
|
+
{
|
|
196
|
+
view: 'res.partner.form',
|
|
197
|
+
operations: [
|
|
198
|
+
{
|
|
199
|
+
append: 'identity',
|
|
200
|
+
nodes: [
|
|
201
|
+
field({ field: 'gstin', id: 'gstin', visibleWhen: { country: 'IN' } }),
|
|
202
|
+
field({ field: 'pan', id: 'pan', visibleWhen: { country: 'IN' } }),
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
},
|
|
207
|
+
// On posted customer invoices and notes: the e-invoice and the e-way bill.
|
|
208
|
+
{
|
|
209
|
+
view: 'account.move.invoice.form',
|
|
210
|
+
operations: [
|
|
211
|
+
{
|
|
212
|
+
insertAfter: 'register_payment',
|
|
213
|
+
nodes: [{ kind: 'component', id: 'einvoice', component: 'l10n_in.EInvoice' }],
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
// A product's HSN / SAC code.
|
|
218
|
+
{
|
|
219
|
+
view: 'product.product.form',
|
|
220
|
+
operations: [{ append: 'identity', nodes: [field({ field: 'hsnCode', id: 'hsn_code' })] }],
|
|
221
|
+
},
|
|
222
|
+
// Where a tax goes in the GST returns.
|
|
223
|
+
{
|
|
224
|
+
view: 'account.tax.form',
|
|
225
|
+
operations: [
|
|
226
|
+
{ append: 'main', nodes: [field({ field: 'gstTreatment', id: 'gst_treatment' })] },
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
export const l10nIn = l10nInModule();
|