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.
- package/README.md +17 -10
- 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 +1350 -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 +142 -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/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/init.d.ts +5 -0
- package/dist/cli/init.js +195 -53
- package/dist/cli/launch.d.ts +16 -0
- package/dist/cli/launch.js +138 -0
- package/dist/cli/prompt.d.ts +35 -0
- package/dist/cli/prompt.js +46 -0
- package/dist/cli/templates.d.ts +11 -3
- package/dist/cli/templates.js +32 -7
- package/dist/cli/tui.d.ts +7 -0
- package/dist/cli/tui.js +254 -0
- package/dist/cli/ui.d.ts +25 -5
- package/dist/cli/ui.js +49 -9
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +3 -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 +1281 -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 +1343 -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/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/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 +4 -0
- package/dist/notify/models.d.ts +1 -1
- package/dist/notify/models.js +2 -0
- package/dist/notify/web/messages.js +4 -0
- package/dist/portal/.kosh/bindings.d.ts +22 -0
- package/dist/portal/.kosh/bindings.js +15 -0
- package/dist/portal/.kosh/schema.d.ts +346 -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 +12 -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 +3 -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 +671 -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 +15 -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 +144 -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 +822 -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 +144 -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 +736 -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 +15 -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 +15 -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/auth/base.js +2 -0
- package/dist/server/auth/service.d.ts +2 -0
- package/dist/server/auth/service.js +7 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/server.js +26 -1
- package/dist/ui/api.d.ts +2 -0
- package/dist/ui/app.js +1 -1
- package/dist/ui/context.d.ts +3 -1
- package/dist/ui/context.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/views/FormView.js +19 -6
- package/package.json +50 -2
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E-invoicing and e-way bills (RFC 0016 step 5, part 4). A posted customer invoice, credit or debit note to a
|
|
3
|
+
* registered recipient or abroad is registered with the Invoice Registration Portal (IRP) through a provider: the
|
|
4
|
+
* IRP schema's payload (version 1.1) is built from the document and checked first (what the IRP would refuse is
|
|
5
|
+
* said here, in words); the IRN, the acknowledgement and the signed QR code come back onto the document. An IRN is
|
|
6
|
+
* cancelled within 24 hours of its acknowledgement. An e-way bill is generated from an invoice with the transport
|
|
7
|
+
* (part B), and cancelled within 24 hours.
|
|
8
|
+
*
|
|
9
|
+
* Providers sit behind {@link GstProvider} (NIC's APIs directly, or a GST Suvidha Provider), like the messengers of
|
|
10
|
+
* notifications; credentials come from the environment, never from records. {@link createFakeGstProvider} answers
|
|
11
|
+
* as the IRP does, for tests and demonstrations: the IRN is the SHA-256 of the seller's GSTIN, the financial year,
|
|
12
|
+
* the document type and number, and a second registration of the same document is refused.
|
|
13
|
+
*/
|
|
14
|
+
import { createHash, randomInt } from 'node:crypto';
|
|
15
|
+
import { HttpProblem } from '../server/index.js';
|
|
16
|
+
import { orm } from '../server/orm.js';
|
|
17
|
+
import { GST_STATE_CODES } from './module.js';
|
|
18
|
+
const ZERO = orm.toDecimal(0);
|
|
19
|
+
/** A refusal of the IRP or the e-way bill system: its code and message, kept on the document. */
|
|
20
|
+
export class GstProviderError extends Error {
|
|
21
|
+
code;
|
|
22
|
+
constructor(code, message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.code = code;
|
|
25
|
+
this.name = 'GstProviderError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** The financial year (April–March) of `iso` as the IRN uses it: `2026-27`. */
|
|
29
|
+
export function financialYear(iso) {
|
|
30
|
+
const [y, m] = iso.split('-').map(Number);
|
|
31
|
+
const start = m >= 4 ? y : y - 1;
|
|
32
|
+
return `${start}-${String(start + 1).slice(2)}`;
|
|
33
|
+
}
|
|
34
|
+
/** The IRN of a document: SHA-256 of the seller's GSTIN, the financial year, the document type and number. */
|
|
35
|
+
export function irnOf(gstin, docType, docNo, iso) {
|
|
36
|
+
return createHash('sha256')
|
|
37
|
+
.update(`${gstin}${financialYear(iso)}${docType}${docNo.toUpperCase()}`)
|
|
38
|
+
.digest('hex');
|
|
39
|
+
}
|
|
40
|
+
const toIso = (dmy) => `${dmy.slice(6, 10)}-${dmy.slice(3, 5)}-${dmy.slice(0, 2)}`;
|
|
41
|
+
const base64url = (value) => Buffer.from(JSON.stringify(value)).toString('base64url');
|
|
42
|
+
/** A provider answering like the IRP and the e-way bill system, in memory (tests, demonstrations). */
|
|
43
|
+
export function createFakeGstProvider(now = () => new Date()) {
|
|
44
|
+
const invoices = new Map();
|
|
45
|
+
const bills = new Map();
|
|
46
|
+
let ack = 112610000000000;
|
|
47
|
+
// The IRP answers in Indian time (UTC+05:30), as a wall clock.
|
|
48
|
+
const ist = () => new Date(now().getTime() + 330 * 60_000);
|
|
49
|
+
const stamp = () => ist().toISOString().slice(0, 19).replace('T', ' ');
|
|
50
|
+
return {
|
|
51
|
+
name: 'fake',
|
|
52
|
+
invoices,
|
|
53
|
+
async registerInvoice(payload) {
|
|
54
|
+
const irn = irnOf(payload.SellerDtls.Gstin, payload.DocDtls.Typ, payload.DocDtls.No, toIso(payload.DocDtls.Dt));
|
|
55
|
+
if (invoices.has(irn))
|
|
56
|
+
throw new GstProviderError('2150', 'Duplicate IRN');
|
|
57
|
+
if (payload.ItemList.length === 0)
|
|
58
|
+
throw new GstProviderError('2172', 'The invoice has no items');
|
|
59
|
+
ack += 1;
|
|
60
|
+
const ackDate = stamp();
|
|
61
|
+
invoices.set(irn, { cancelled: false, ackDate });
|
|
62
|
+
const data = {
|
|
63
|
+
SellerGstin: payload.SellerDtls.Gstin,
|
|
64
|
+
BuyerGstin: payload.BuyerDtls.Gstin,
|
|
65
|
+
DocNo: payload.DocDtls.No,
|
|
66
|
+
DocTyp: payload.DocDtls.Typ,
|
|
67
|
+
DocDt: payload.DocDtls.Dt,
|
|
68
|
+
TotInvVal: payload.ValDtls.TotInvVal,
|
|
69
|
+
ItemCnt: payload.ItemList.length,
|
|
70
|
+
MainHsnCode: payload.ItemList[0]?.HsnCd,
|
|
71
|
+
Irn: irn,
|
|
72
|
+
IrnDt: ackDate,
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
irn,
|
|
76
|
+
ackNo: String(ack),
|
|
77
|
+
ackDate,
|
|
78
|
+
signedQr: `${base64url({ alg: 'none', typ: 'JWT' })}.${base64url({ data: JSON.stringify(data) })}.fake`,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
async cancelInvoice(_seller, irn) {
|
|
82
|
+
const known = invoices.get(irn);
|
|
83
|
+
if (known === undefined)
|
|
84
|
+
throw new GstProviderError('2163', 'The IRN is not known');
|
|
85
|
+
if (known.cancelled)
|
|
86
|
+
throw new GstProviderError('9999', 'The IRN is already cancelled');
|
|
87
|
+
known.cancelled = true;
|
|
88
|
+
return { cancelledAt: stamp() };
|
|
89
|
+
},
|
|
90
|
+
async generateEwayBill(payload) {
|
|
91
|
+
const ewbNo = String(randomInt(100000000000, 999999999999));
|
|
92
|
+
bills.set(ewbNo, { cancelled: false });
|
|
93
|
+
const start = ist();
|
|
94
|
+
// Valid one day per 200 km (road), to the end of the last day.
|
|
95
|
+
const days = Math.max(1, Math.ceil(payload.transDistance / 200));
|
|
96
|
+
const until = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate() + days, 23, 59, 0));
|
|
97
|
+
return {
|
|
98
|
+
ewbNo,
|
|
99
|
+
ewbDate: stamp(),
|
|
100
|
+
validUntil: until.toISOString().slice(0, 19).replace('T', ' '),
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
async cancelEwayBill(_seller, ewbNo) {
|
|
104
|
+
const known = bills.get(ewbNo);
|
|
105
|
+
if (known === undefined || known.cancelled)
|
|
106
|
+
throw new GstProviderError('312', 'The e-way bill cannot be cancelled');
|
|
107
|
+
known.cancelled = true;
|
|
108
|
+
return { cancelledAt: stamp() };
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** The provider the environment names (`KOSH_GST_PROVIDER=fake`), or none: e-invoicing is then unavailable. */
|
|
113
|
+
export function gstProviderFromEnv(env = process.env) {
|
|
114
|
+
return env['KOSH_GST_PROVIDER'] === 'fake' ? createFakeGstProvider() : undefined;
|
|
115
|
+
}
|
|
116
|
+
const money = (d) => Number(d.toFixed(2));
|
|
117
|
+
const dmy = (iso) => `${iso.slice(8, 10)}/${iso.slice(5, 7)}/${iso.slice(0, 4)}`;
|
|
118
|
+
const DOC_TYPES = {
|
|
119
|
+
out_invoice: 'INV',
|
|
120
|
+
out_refund: 'CRN',
|
|
121
|
+
out_debit: 'DBN',
|
|
122
|
+
};
|
|
123
|
+
/** A document's payload for the IRP, or the reasons the IRP would refuse it. */
|
|
124
|
+
export async function einvoicePayload(env, id) {
|
|
125
|
+
const [m] = await env
|
|
126
|
+
.model('account.move')
|
|
127
|
+
.search({ where: { id: orm.asId(id, 'account.move') }, limit: 1 })
|
|
128
|
+
.then((found) => found.load({
|
|
129
|
+
company: true,
|
|
130
|
+
partner: ['name', 'gstin', 'country', 'state', 'street', 'city', 'zip'],
|
|
131
|
+
invoiceLines: { product: ['hsnCode', 'kind'], taxes: ['reverseCharge'] },
|
|
132
|
+
}));
|
|
133
|
+
if (m === undefined)
|
|
134
|
+
throw new HttpProblem(404, 'missing-record', 'Not found', `There is no document ${id}.`);
|
|
135
|
+
const problems = [];
|
|
136
|
+
const typ = DOC_TYPES[m.kind];
|
|
137
|
+
if (typ === undefined)
|
|
138
|
+
problems.push('Only customer invoices, credit notes and debit notes are e-invoiced.');
|
|
139
|
+
if (m.state !== 'posted')
|
|
140
|
+
problems.push('Only a posted document is e-invoiced.');
|
|
141
|
+
const company = m.company;
|
|
142
|
+
const seller = company.gstin ?? '';
|
|
143
|
+
if (company.gstin === null)
|
|
144
|
+
problems.push('The company has no GSTIN (Accounting › Reporting › GST returns › Settings).');
|
|
145
|
+
if (company.state === null)
|
|
146
|
+
problems.push('The company has no state (Accounting › Configuration › Settings).');
|
|
147
|
+
if (company.zip === null || !/^\d{6}$/.test(company.zip))
|
|
148
|
+
problems.push('The company has no 6-digit PIN code.');
|
|
149
|
+
const partner = m.partner;
|
|
150
|
+
const exported = partner?.country !== null &&
|
|
151
|
+
partner?.country !== undefined &&
|
|
152
|
+
partner.country !== company.country;
|
|
153
|
+
if (partner === null)
|
|
154
|
+
problems.push('The document has no customer.');
|
|
155
|
+
else if (!exported && partner.gstin === null)
|
|
156
|
+
problems.push(`${partner.name} has no GSTIN: invoices to unregistered customers are not e-invoiced.`);
|
|
157
|
+
if (partner !== null && !exported && (partner.zip === null || !/^\d{6}$/.test(partner.zip)))
|
|
158
|
+
problems.push(`${partner.name} has no 6-digit PIN code.`);
|
|
159
|
+
const pos = exported ? '96' : (GST_STATE_CODES[m.placeOfSupply ?? partner?.state ?? ''] ?? '');
|
|
160
|
+
if (pos === '')
|
|
161
|
+
problems.push('The place of supply is not a state of India.');
|
|
162
|
+
const lines = [...m.invoiceLines].filter((l) => l.lineKind === 'product');
|
|
163
|
+
if (lines.length === 0)
|
|
164
|
+
problems.push('The document has no lines.');
|
|
165
|
+
const items = lines.map((l, i) => {
|
|
166
|
+
if (l.product?.hsnCode === null || l.product?.hsnCode === undefined)
|
|
167
|
+
problems.push(`Line ${i + 1} (${l.name ?? ''}) has no HSN / SAC code: set it on the product.`);
|
|
168
|
+
const qty = l.quantity ?? orm.toDecimal(1);
|
|
169
|
+
const price = l.priceUnit ?? ZERO;
|
|
170
|
+
const gross = qty.times(price);
|
|
171
|
+
const assessable = l.subtotal?.amount ?? ZERO;
|
|
172
|
+
const heads = [l.igstAmount, l.cgstAmount, l.sgstAmount, l.cessAmount].map((x) => x?.amount ?? ZERO);
|
|
173
|
+
return {
|
|
174
|
+
SlNo: String(i + 1),
|
|
175
|
+
PrdDesc: l.name ?? '',
|
|
176
|
+
IsServc: l.product?.hsnCode?.startsWith('99') === true ? 'Y' : 'N',
|
|
177
|
+
HsnCd: l.product?.hsnCode ?? '',
|
|
178
|
+
Qty: Number(qty.toString()),
|
|
179
|
+
Unit: 'NOS',
|
|
180
|
+
UnitPrice: money(price),
|
|
181
|
+
TotAmt: money(gross),
|
|
182
|
+
Discount: money(gross.minus(assessable)),
|
|
183
|
+
AssAmt: money(assessable),
|
|
184
|
+
GstRt: Number((l.gstRate ?? ZERO).toString()),
|
|
185
|
+
IgstAmt: money(heads[0]),
|
|
186
|
+
CgstAmt: money(heads[1]),
|
|
187
|
+
SgstAmt: money(heads[2]),
|
|
188
|
+
CesAmt: money(heads[3]),
|
|
189
|
+
TotItemVal: money(l.totalWithGst?.amount ?? assessable),
|
|
190
|
+
};
|
|
191
|
+
});
|
|
192
|
+
const sum = (key) => items.reduce((s, it) => s + it[key], 0);
|
|
193
|
+
const twoDecimals = (n) => Math.round(n * 100) / 100;
|
|
194
|
+
const igst = twoDecimals(sum('IgstAmt'));
|
|
195
|
+
const payload = {
|
|
196
|
+
Version: '1.1',
|
|
197
|
+
TranDtls: {
|
|
198
|
+
TaxSch: 'GST',
|
|
199
|
+
SupTyp: exported ? (igst > 0 ? 'EXPWP' : 'EXPWOP') : 'B2B',
|
|
200
|
+
RegRev: lines.some((l) => [...l.taxes].some((t) => t.reverseCharge)) ? 'Y' : 'N',
|
|
201
|
+
IgstOnIntra: 'N',
|
|
202
|
+
},
|
|
203
|
+
DocDtls: { Typ: typ ?? 'INV', No: m.name ?? '', Dt: dmy(String(m.date)) },
|
|
204
|
+
SellerDtls: {
|
|
205
|
+
Gstin: seller,
|
|
206
|
+
LglNm: company.name,
|
|
207
|
+
Addr1: company.street ?? company.name,
|
|
208
|
+
Loc: company.city ?? '',
|
|
209
|
+
Pin: Number(company.zip ?? 0),
|
|
210
|
+
Stcd: GST_STATE_CODES[company.state ?? ''] ?? '',
|
|
211
|
+
},
|
|
212
|
+
BuyerDtls: {
|
|
213
|
+
Gstin: exported ? 'URP' : (partner?.gstin ?? ''),
|
|
214
|
+
LglNm: partner?.name ?? '',
|
|
215
|
+
Pos: pos,
|
|
216
|
+
Addr1: partner?.street ?? partner?.name ?? '',
|
|
217
|
+
Loc: partner?.city ?? '',
|
|
218
|
+
Pin: exported ? 999999 : Number(partner?.zip ?? 0),
|
|
219
|
+
Stcd: exported ? '96' : (GST_STATE_CODES[partner?.state ?? ''] ?? ''),
|
|
220
|
+
},
|
|
221
|
+
ItemList: items,
|
|
222
|
+
ValDtls: {
|
|
223
|
+
AssVal: twoDecimals(sum('AssAmt')),
|
|
224
|
+
CgstVal: twoDecimals(sum('CgstAmt')),
|
|
225
|
+
SgstVal: twoDecimals(sum('SgstAmt')),
|
|
226
|
+
IgstVal: igst,
|
|
227
|
+
CesVal: twoDecimals(sum('CesAmt')),
|
|
228
|
+
TotInvVal: twoDecimals(sum('TotItemVal')),
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
return { payload: problems.length === 0 ? payload : null, problems, seller };
|
|
232
|
+
}
|
|
233
|
+
/** Writes the IRP's answer (or the refusal) onto a document, as the system: the fields are read-only to users. */
|
|
234
|
+
async function record(env, id, values) {
|
|
235
|
+
await env.asSuperuser({ reason: 'e-invoicing: the IRP answered' }, async (su) => {
|
|
236
|
+
// The document's e-invoice record (users only read it); the document shows it through computed fields.
|
|
237
|
+
const records = su.model('l10n_in.einvoice');
|
|
238
|
+
const move = orm.asId(id, 'account.move');
|
|
239
|
+
const found = await records.search({ where: { move }, limit: 1 });
|
|
240
|
+
if (found.length === 0)
|
|
241
|
+
await records.create([{ move, ...values }]);
|
|
242
|
+
else
|
|
243
|
+
await found.write(values);
|
|
244
|
+
await su.flush(); // checks in this scope (Shutro request #11)
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
/** A document's e-invoice record, or undefined (never registered). */
|
|
248
|
+
async function einvoiceOf(env, id) {
|
|
249
|
+
const [e] = await env
|
|
250
|
+
.model('l10n_in.einvoice')
|
|
251
|
+
.search({ where: { move: orm.asId(id, 'account.move') }, limit: 1 });
|
|
252
|
+
return e;
|
|
253
|
+
}
|
|
254
|
+
const fromProvider = (stamp) => new Date(`${stamp.replace(' ', 'T')}+05:30`);
|
|
255
|
+
/** The state of a document's e-invoice and e-way bill (what the screen shows). */
|
|
256
|
+
export async function einvoiceState(env, id) {
|
|
257
|
+
const [m] = await env
|
|
258
|
+
.model('account.move')
|
|
259
|
+
.search({ where: { id: orm.asId(id, 'account.move') }, limit: 1 });
|
|
260
|
+
if (m === undefined)
|
|
261
|
+
throw new HttpProblem(404, 'missing-record', 'Not found', `There is no document ${id}.`);
|
|
262
|
+
return {
|
|
263
|
+
status: m.einvoiceStatus,
|
|
264
|
+
irn: m.irn,
|
|
265
|
+
ackNo: m.irnAckNo,
|
|
266
|
+
ackDate: m.irnAckDate,
|
|
267
|
+
signedQr: m.irnSignedQr,
|
|
268
|
+
cancelledAt: m.irnCancelledAt,
|
|
269
|
+
error: m.einvoiceError,
|
|
270
|
+
ewayBill: {
|
|
271
|
+
status: m.ewbStatus,
|
|
272
|
+
number: m.ewbNo,
|
|
273
|
+
date: m.ewbDate,
|
|
274
|
+
validUntil: m.ewbValidUntil,
|
|
275
|
+
vehicleNo: m.ewbVehicleNo,
|
|
276
|
+
transporterId: m.ewbTransporterId,
|
|
277
|
+
distanceKm: m.ewbDistanceKm,
|
|
278
|
+
mode: m.ewbMode,
|
|
279
|
+
error: m.ewbError,
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const unavailable = () => new HttpProblem(409, 'no-gst-provider', 'E-invoicing is not set up', 'No IRP / e-way bill provider is configured for this installation (KOSH_GST_PROVIDER).');
|
|
284
|
+
/**
|
|
285
|
+
* Registers a document with the IRP. The document must be readable and the user may post (the right to e-invoice
|
|
286
|
+
* goes with it). A refusal, here or by the IRP, is kept on the document and answered with 422.
|
|
287
|
+
*/
|
|
288
|
+
export async function registerEInvoice(env, provider, id) {
|
|
289
|
+
env.access.check('account.move', 'write');
|
|
290
|
+
if (provider === undefined)
|
|
291
|
+
throw unavailable();
|
|
292
|
+
const { payload, problems } = await einvoicePayload(env, id);
|
|
293
|
+
const [current] = await env
|
|
294
|
+
.model('account.move')
|
|
295
|
+
.search({ where: { id: orm.asId(id, 'account.move') }, limit: 1 });
|
|
296
|
+
if (current?.einvoiceStatus === 'registered')
|
|
297
|
+
throw new HttpProblem(409, 'already-registered', 'Already registered', `The document has the IRN ${current.irn}.`);
|
|
298
|
+
if (payload === null) {
|
|
299
|
+
await record(env, id, { einvoiceStatus: 'failed', einvoiceError: problems.join('\n') });
|
|
300
|
+
throw new HttpProblem(422, 'einvoice-refused', 'The document cannot be e-invoiced', problems.join(' '));
|
|
301
|
+
}
|
|
302
|
+
try {
|
|
303
|
+
const answer = await provider.registerInvoice(payload);
|
|
304
|
+
await record(env, id, {
|
|
305
|
+
einvoiceStatus: 'registered',
|
|
306
|
+
irn: answer.irn,
|
|
307
|
+
irnAckNo: answer.ackNo,
|
|
308
|
+
irnAckDate: fromProvider(answer.ackDate),
|
|
309
|
+
irnSignedQr: answer.signedQr,
|
|
310
|
+
einvoiceError: null,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (!(error instanceof GstProviderError))
|
|
315
|
+
throw error;
|
|
316
|
+
await record(env, id, {
|
|
317
|
+
einvoiceStatus: 'failed',
|
|
318
|
+
einvoiceError: `${error.code}: ${error.message}`,
|
|
319
|
+
});
|
|
320
|
+
throw new HttpProblem(422, 'einvoice-refused', 'The IRP refused the document', `${error.code}: ${error.message}`);
|
|
321
|
+
}
|
|
322
|
+
return einvoiceState(env, id);
|
|
323
|
+
}
|
|
324
|
+
/** The IRP's reasons to cancel an IRN. */
|
|
325
|
+
export const CANCEL_REASONS = {
|
|
326
|
+
'1': 'Duplicate',
|
|
327
|
+
'2': 'Data entry mistake',
|
|
328
|
+
'3': 'Order cancelled',
|
|
329
|
+
'4': 'Others',
|
|
330
|
+
};
|
|
331
|
+
/** Cancels a document's IRN, within 24 hours of its acknowledgement. The document itself is reversed separately. */
|
|
332
|
+
export async function cancelEInvoice(env, provider, id, reason, remark) {
|
|
333
|
+
env.access.check('account.move', 'write');
|
|
334
|
+
if (provider === undefined)
|
|
335
|
+
throw unavailable();
|
|
336
|
+
if (CANCEL_REASONS[reason] === undefined)
|
|
337
|
+
throw new HttpProblem(400, 'invalid-body', 'Invalid request', 'reason is 1 (duplicate), 2 (mistake), 3 (cancelled) or 4 (others).');
|
|
338
|
+
const [m] = await env
|
|
339
|
+
.model('account.move')
|
|
340
|
+
.search({ where: { id: orm.asId(id, 'account.move') }, limit: 1 })
|
|
341
|
+
.then((f) => f.load({ company: true }));
|
|
342
|
+
if (m === undefined)
|
|
343
|
+
throw new HttpProblem(404, 'missing-record', 'Not found', `There is no document ${id}.`);
|
|
344
|
+
const e = await einvoiceOf(env, id);
|
|
345
|
+
if (e?.einvoiceStatus !== 'registered' || e.irn === null || e.irnAckDate === null)
|
|
346
|
+
throw new HttpProblem(409, 'not-registered', 'Not registered', 'The document has no IRN to cancel.');
|
|
347
|
+
const acknowledged = new Date(String(e.irnAckDate)).getTime();
|
|
348
|
+
if (env.now().getTime() - acknowledged > 24 * 3600 * 1000)
|
|
349
|
+
throw new HttpProblem(409, 'too-late', 'Too late to cancel', 'An IRN is cancelled within 24 hours. Issue a credit note instead.');
|
|
350
|
+
try {
|
|
351
|
+
const answer = await provider.cancelInvoice(m.company.gstin ?? '', e.irn, reason, remark);
|
|
352
|
+
await record(env, id, {
|
|
353
|
+
einvoiceStatus: 'cancelled',
|
|
354
|
+
irnCancelledAt: fromProvider(answer.cancelledAt),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
if (!(error instanceof GstProviderError))
|
|
359
|
+
throw error;
|
|
360
|
+
throw new HttpProblem(422, 'einvoice-refused', 'The IRP refused the cancellation', `${error.code}: ${error.message}`);
|
|
361
|
+
}
|
|
362
|
+
return einvoiceState(env, id);
|
|
363
|
+
}
|
|
364
|
+
const MODES = { road: '1', rail: '2', air: '3', ship: '4' };
|
|
365
|
+
/** Generates the e-way bill of a posted invoice with its transport (part B). */
|
|
366
|
+
export async function generateEwayBill(env, provider, id, transport) {
|
|
367
|
+
env.access.check('account.move', 'write');
|
|
368
|
+
if (provider === undefined)
|
|
369
|
+
throw unavailable();
|
|
370
|
+
const { payload: invoice, problems } = await einvoicePayload(env, id);
|
|
371
|
+
const [m] = await env
|
|
372
|
+
.model('account.move')
|
|
373
|
+
.search({ where: { id: orm.asId(id, 'account.move') }, limit: 1 });
|
|
374
|
+
if (m?.kind !== 'out_invoice')
|
|
375
|
+
problems.push('E-way bills are generated from customer invoices.');
|
|
376
|
+
if (m?.ewbStatus === 'generated')
|
|
377
|
+
throw new HttpProblem(409, 'already-generated', 'Already generated', `The invoice has the e-way bill ${m.ewbNo}.`);
|
|
378
|
+
if (transport.mode === 'road' && !/^[A-Z]{2}\d{1,2}[A-Z]{0,3}\d{4}$/.test(transport.vehicleNo))
|
|
379
|
+
problems.push('A vehicle number is like MH12AB1234.');
|
|
380
|
+
if (!(transport.distanceKm > 0 && transport.distanceKm <= 4000))
|
|
381
|
+
problems.push('The distance is 1 to 4000 km.');
|
|
382
|
+
const values = {
|
|
383
|
+
ewbMode: transport.mode,
|
|
384
|
+
ewbDistanceKm: transport.distanceKm,
|
|
385
|
+
ewbVehicleNo: transport.vehicleNo,
|
|
386
|
+
ewbTransporterId: transport.transporterId ?? null,
|
|
387
|
+
};
|
|
388
|
+
if (invoice === null || problems.length > 0) {
|
|
389
|
+
await record(env, id, { ...values, ewbStatus: 'failed', ewbError: problems.join('\n') });
|
|
390
|
+
throw new HttpProblem(422, 'ewaybill-refused', 'No e-way bill for this invoice', problems.join(' '));
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const answer = await provider.generateEwayBill({
|
|
394
|
+
supplyType: 'O',
|
|
395
|
+
docType: 'INV',
|
|
396
|
+
docNo: invoice.DocDtls.No,
|
|
397
|
+
docDate: invoice.DocDtls.Dt,
|
|
398
|
+
fromGstin: invoice.SellerDtls.Gstin,
|
|
399
|
+
fromPincode: invoice.SellerDtls.Pin,
|
|
400
|
+
fromStateCode: Number(invoice.SellerDtls.Stcd),
|
|
401
|
+
toGstin: invoice.BuyerDtls.Gstin,
|
|
402
|
+
toPincode: invoice.BuyerDtls.Pin,
|
|
403
|
+
toStateCode: Number(invoice.BuyerDtls.Stcd),
|
|
404
|
+
totInvValue: invoice.ValDtls.TotInvVal,
|
|
405
|
+
transMode: MODES[transport.mode],
|
|
406
|
+
transDistance: transport.distanceKm,
|
|
407
|
+
transporterId: transport.transporterId ?? '',
|
|
408
|
+
vehicleNo: transport.vehicleNo,
|
|
409
|
+
...(m?.irn === null || m?.irn === undefined ? {} : { irn: m.irn }),
|
|
410
|
+
});
|
|
411
|
+
await record(env, id, {
|
|
412
|
+
...values,
|
|
413
|
+
ewbStatus: 'generated',
|
|
414
|
+
ewbNo: answer.ewbNo,
|
|
415
|
+
ewbDate: fromProvider(answer.ewbDate),
|
|
416
|
+
ewbValidUntil: fromProvider(answer.validUntil),
|
|
417
|
+
ewbError: null,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
catch (error) {
|
|
421
|
+
if (!(error instanceof GstProviderError))
|
|
422
|
+
throw error;
|
|
423
|
+
await record(env, id, {
|
|
424
|
+
...values,
|
|
425
|
+
ewbStatus: 'failed',
|
|
426
|
+
ewbError: `${error.code}: ${error.message}`,
|
|
427
|
+
});
|
|
428
|
+
throw new HttpProblem(422, 'ewaybill-refused', 'The e-way bill system refused it', `${error.code}: ${error.message}`);
|
|
429
|
+
}
|
|
430
|
+
return einvoiceState(env, id);
|
|
431
|
+
}
|
|
432
|
+
/** Cancels an e-way bill within 24 hours of its generation. */
|
|
433
|
+
export async function cancelEwayBill(env, provider, id, reason, remark) {
|
|
434
|
+
env.access.check('account.move', 'write');
|
|
435
|
+
if (provider === undefined)
|
|
436
|
+
throw unavailable();
|
|
437
|
+
const [m] = await env
|
|
438
|
+
.model('account.move')
|
|
439
|
+
.search({ where: { id: orm.asId(id, 'account.move') }, limit: 1 })
|
|
440
|
+
.then((f) => f.load({ company: true }));
|
|
441
|
+
if (m === undefined)
|
|
442
|
+
throw new HttpProblem(404, 'missing-record', 'Not found', `There is no document ${id}.`);
|
|
443
|
+
const e = await einvoiceOf(env, id);
|
|
444
|
+
if (e?.ewbStatus !== 'generated' || e.ewbNo === null || e.ewbDate === null)
|
|
445
|
+
throw new HttpProblem(409, 'no-ewaybill', 'No e-way bill', 'The invoice has no e-way bill to cancel.');
|
|
446
|
+
if (env.now().getTime() - new Date(String(e.ewbDate)).getTime() > 24 * 3600 * 1000)
|
|
447
|
+
throw new HttpProblem(409, 'too-late', 'Too late to cancel', 'An e-way bill is cancelled within 24 hours.');
|
|
448
|
+
try {
|
|
449
|
+
await provider.cancelEwayBill(m.company.gstin ?? '', e.ewbNo, reason, remark);
|
|
450
|
+
await record(env, id, { ewbStatus: 'cancelled' });
|
|
451
|
+
}
|
|
452
|
+
catch (error) {
|
|
453
|
+
if (!(error instanceof GstProviderError))
|
|
454
|
+
throw error;
|
|
455
|
+
throw new HttpProblem(422, 'ewaybill-refused', 'The e-way bill system refused it', `${error.code}: ${error.message}`);
|
|
456
|
+
}
|
|
457
|
+
return einvoiceState(env, id);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* After posting (`account.move.posted`): customer documents of companies with e-invoicing on are registered, those
|
|
461
|
+
* to registered recipients and abroad; a refusal stays on the document for the user to fix and retry.
|
|
462
|
+
*/
|
|
463
|
+
export async function registerPosted(env, provider, id) {
|
|
464
|
+
const [m] = await env
|
|
465
|
+
.model('account.move')
|
|
466
|
+
.search({ where: { id: orm.asId(id, 'account.move') }, limit: 1 })
|
|
467
|
+
.then((f) => f.load({ company: true, partner: ['gstin', 'country'] }));
|
|
468
|
+
if (m === undefined || DOC_TYPES[m.kind] === undefined || !m.company.einvoiceEnabled)
|
|
469
|
+
return;
|
|
470
|
+
if (m.einvoiceStatus === 'registered')
|
|
471
|
+
return;
|
|
472
|
+
const exported = m.partner?.country !== null &&
|
|
473
|
+
m.partner?.country !== undefined &&
|
|
474
|
+
m.partner.country !== m.company.country;
|
|
475
|
+
if (m.partner?.gstin === null && !exported)
|
|
476
|
+
return; // unregistered: not e-invoiced
|
|
477
|
+
try {
|
|
478
|
+
await registerEInvoice(env, provider, id);
|
|
479
|
+
}
|
|
480
|
+
catch (error) {
|
|
481
|
+
if (error instanceof HttpProblem && error.problem.status === 422)
|
|
482
|
+
return; // kept on the document
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { orm } from '../server/orm.js';
|
|
2
|
+
import type { Env } from './.kosh/bindings.js';
|
|
3
|
+
type Decimal = ReturnType<typeof orm.toDecimal>;
|
|
4
|
+
/** B2C invoices between states above this total are reported one by one (B2CL), in rupees. */
|
|
5
|
+
export declare const B2CL_THRESHOLD: Decimal;
|
|
6
|
+
export declare class MonthError extends Error {
|
|
7
|
+
}
|
|
8
|
+
/** The first and last day of `month` (`2026-10`). */
|
|
9
|
+
export declare function monthRange(month: string): {
|
|
10
|
+
readonly from: string;
|
|
11
|
+
readonly to: string;
|
|
12
|
+
};
|
|
13
|
+
/** The taxable value and each head of tax. */
|
|
14
|
+
export interface Heads {
|
|
15
|
+
readonly txval: Decimal;
|
|
16
|
+
readonly iamt: Decimal;
|
|
17
|
+
readonly camt: Decimal;
|
|
18
|
+
readonly samt: Decimal;
|
|
19
|
+
readonly csamt: Decimal;
|
|
20
|
+
}
|
|
21
|
+
/** An invoice or note in a GSTR-1 section: its items summed by rate. */
|
|
22
|
+
export interface Gstr1Document {
|
|
23
|
+
readonly number: string;
|
|
24
|
+
readonly date: string;
|
|
25
|
+
readonly partner: string;
|
|
26
|
+
readonly gstin: string | null;
|
|
27
|
+
readonly pos: string;
|
|
28
|
+
readonly value: Decimal;
|
|
29
|
+
readonly reverseCharge: boolean;
|
|
30
|
+
/** For notes: credit (C) or debit (D), and the invoice they correct. */
|
|
31
|
+
readonly noteType?: 'C' | 'D';
|
|
32
|
+
readonly original?: {
|
|
33
|
+
readonly number: string;
|
|
34
|
+
readonly date: string;
|
|
35
|
+
} | null;
|
|
36
|
+
/** For exports: with IGST paid (WPAY) or under LUT (WOPAY). */
|
|
37
|
+
readonly exportType?: 'WPAY' | 'WOPAY';
|
|
38
|
+
readonly items: readonly (Heads & {
|
|
39
|
+
readonly rate: Decimal;
|
|
40
|
+
})[];
|
|
41
|
+
}
|
|
42
|
+
export interface Gstr1 {
|
|
43
|
+
readonly month: string;
|
|
44
|
+
readonly gstin: string | null;
|
|
45
|
+
readonly b2b: readonly Gstr1Document[];
|
|
46
|
+
readonly b2cl: readonly Gstr1Document[];
|
|
47
|
+
readonly b2cs: readonly (Heads & {
|
|
48
|
+
readonly supply: 'INTRA' | 'INTER';
|
|
49
|
+
readonly pos: string;
|
|
50
|
+
readonly rate: Decimal;
|
|
51
|
+
})[];
|
|
52
|
+
readonly exp: readonly Gstr1Document[];
|
|
53
|
+
readonly cdnr: readonly Gstr1Document[];
|
|
54
|
+
readonly cdnur: readonly Gstr1Document[];
|
|
55
|
+
readonly nil: readonly {
|
|
56
|
+
readonly supply: 'INTRB2B' | 'INTRAB2B' | 'INTRB2C' | 'INTRAB2C';
|
|
57
|
+
readonly nil: Decimal;
|
|
58
|
+
readonly exempt: Decimal;
|
|
59
|
+
readonly nonGst: Decimal;
|
|
60
|
+
}[];
|
|
61
|
+
readonly hsn: readonly (Heads & {
|
|
62
|
+
readonly hsn: string;
|
|
63
|
+
readonly description: string;
|
|
64
|
+
readonly quantity: Decimal;
|
|
65
|
+
readonly rate: Decimal;
|
|
66
|
+
})[];
|
|
67
|
+
/** Lines whose GST tax has no treatment: they are in no section. */
|
|
68
|
+
readonly unclassified: number;
|
|
69
|
+
}
|
|
70
|
+
/** GSTR-1 of `month`. */
|
|
71
|
+
export declare function gstr1(env: Env, month: string): Promise<Gstr1>;
|
|
72
|
+
/** GSTR-3B of `month`. */
|
|
73
|
+
export interface Gstr3b {
|
|
74
|
+
readonly month: string;
|
|
75
|
+
readonly gstin: string | null;
|
|
76
|
+
/** 3.1 (a) outward taxable supplies (other than zero-rated, nil-rated and exempt). */
|
|
77
|
+
readonly outwardTaxable: Heads;
|
|
78
|
+
/** 3.1 (b) outward zero-rated supplies (exports). */
|
|
79
|
+
readonly outwardZeroRated: Heads;
|
|
80
|
+
/** 3.1 (c) other outward supplies: nil-rated, exempt. */
|
|
81
|
+
readonly outwardNilExempt: Heads;
|
|
82
|
+
/** 3.1 (d) inward supplies liable to reverse charge. */
|
|
83
|
+
readonly inwardReverseCharge: Heads;
|
|
84
|
+
/** 3.1 (e) non-GST outward supplies. */
|
|
85
|
+
readonly outwardNonGst: Heads;
|
|
86
|
+
/** 4 (A)(3) input tax credit on inward supplies liable to reverse charge. */
|
|
87
|
+
readonly itcReverseCharge: Heads;
|
|
88
|
+
/** 4 (A)(5) all other input tax credit. */
|
|
89
|
+
readonly itcOther: Heads;
|
|
90
|
+
/** 5: exempt, nil-rated and non-GST inward supplies, from another state and within it. */
|
|
91
|
+
readonly inwardExempt: {
|
|
92
|
+
readonly inter: Decimal;
|
|
93
|
+
readonly intra: Decimal;
|
|
94
|
+
};
|
|
95
|
+
/** 6.1: by head, the tax payable, the credit and what is left to pay (negative: carried forward). */
|
|
96
|
+
readonly payable: Readonly<Record<'igst' | 'cgst' | 'sgst' | 'cess', {
|
|
97
|
+
readonly tax: Decimal;
|
|
98
|
+
readonly credit: Decimal;
|
|
99
|
+
readonly net: Decimal;
|
|
100
|
+
}>>;
|
|
101
|
+
}
|
|
102
|
+
/** GSTR-3B of `month`. */
|
|
103
|
+
export declare function gstr3b(env: Env, month: string): Promise<Gstr3b>;
|
|
104
|
+
/**
|
|
105
|
+
* GSTR-1 as the GST portal's offline JSON (the sections b2b, b2cl, b2cs, exp, cdnr, cdnur, nil, hsn). Check it against
|
|
106
|
+
* the current offline tool's schema before uploading: the portal changes it from time to time.
|
|
107
|
+
*/
|
|
108
|
+
export declare function gstr1PortalJson(r: Gstr1): Record<string, unknown>;
|
|
109
|
+
export {};
|