2
0
mirror of https://github.com/frappe/books.git synced 2024-12-23 19:39:07 +00:00
books/reports/GoodsAndServiceTax/gstExporter.ts
18alantom cd09d31b9b incr: remove redundant fields
- complete the feature
2022-10-03 11:48:14 +05:30

396 lines
8.9 KiB
TypeScript

import { Action } from 'fyo/model/types';
import { Verb } from 'fyo/telemetry/types';
import { DateTime } from 'luxon';
import { ModelNameEnum } from 'models/types';
import { codeStateMap } from 'regional/in';
import { ExportExtention } from 'reports/types';
import { getSavePath } from 'src/utils/ipcCalls';
import { showMessageDialog } from 'src/utils/ui';
import { invertMap } from 'utils';
import { getCsvData, saveExportData } from '../commonExporter';
import { BaseGSTR } from './BaseGSTR';
import { TransferTypeEnum } from './types';
const GST = {
'GST-0': 0,
'GST-0.25': 0.25,
'GST-3': 3,
'GST-5': 5,
'GST-6': 6,
'GST-12': 12,
'GST-18': 18,
'GST-28': 28,
'IGST-0': 0,
'IGST-0.25': 0.25,
'IGST-3': 3,
'IGST-5': 5,
'IGST-6': 6,
'IGST-12': 12,
'IGST-18': 18,
'IGST-28': 28,
} as Record<string, number>;
const CSGST = {
'GST-0': 0,
'GST-0.25': 0.125,
'GST-3': 1.5,
'GST-5': 2.5,
'GST-6': 3,
'GST-12': 6,
'GST-18': 9,
'GST-28': 14,
} as Record<string, number>;
const IGST = {
'IGST-0.25': 0.25,
'IGST-3': 3,
'IGST-5': 5,
'IGST-6': 6,
'IGST-12': 12,
'IGST-18': 18,
'IGST-28': 28,
} as Record<string, number>;
interface GSTData {
version: string;
hash: string;
gstin: string;
fp: string;
b2b?: B2BCustomer[];
b2cl?: B2CLStateInvoiceRecord[];
b2cs?: B2CSInvRecord[];
}
interface B2BCustomer {
ctin: string;
inv: B2BInvRecord[];
}
interface B2BInvRecord {
inum: string;
idt: string;
val: number;
pos: string;
rchrg: 'Y' | 'N';
inv_typ: string;
itms: B2BItmRecord[];
}
interface B2BItmRecord {
num: number;
itm_det: {
txval: number;
rt: number;
csamt: number;
camt: number;
samt: number;
iamt: number;
};
}
interface B2CLInvRecord {
inum: string;
idt: string;
val: number;
itms: B2CLItmRecord[];
}
interface B2CLItmRecord {
num: number;
itm_det: {
txval: number;
rt: number;
csamt: 0;
iamt: number;
};
}
interface B2CLStateInvoiceRecord {
pos: string;
inv: B2CLInvRecord[];
}
interface B2CSInvRecord {
sply_ty: 'INTRA' | 'INTER';
pos: string;
typ: 'OE'; // "OE" - Errors and omissions excepted.
txval: number;
rt: number;
iamt: number;
camt: number;
samt: number;
csamt: number;
}
export default function getGSTRExportActions(report: BaseGSTR): Action[] {
const exportExtention = ['csv', 'json'] as ExportExtention[];
return exportExtention.map((ext) => ({
group: `Export`,
label: ext.toUpperCase(),
type: 'primary',
action: async () => {
await exportReport(ext, report);
},
}));
}
async function exportReport(extention: ExportExtention, report: BaseGSTR) {
const canExport = await getCanExport(report);
if (!canExport) {
return;
}
const { filePath, canceled } = await getSavePath(
report.reportName,
extention
);
if (canceled || !filePath) {
return;
}
let data = '';
if (extention === 'csv') {
data = getCsvData(report);
} else if (extention === 'json') {
data = await getGstrJsonData(report);
}
if (!data.length) {
return;
}
await saveExportData(data, filePath, report.fyo);
report.fyo.telemetry.log(Verb.Exported, report.reportName, { extention });
}
async function getCanExport(report: BaseGSTR) {
const gstin = await report.fyo.getValue(
ModelNameEnum.AccountingSettings,
'gstin'
);
if (gstin) {
return true;
}
showMessageDialog({
message: 'Cannot Export',
detail: 'Please set GSTIN in General Settings.',
});
return false;
}
export async function getGstrJsonData(report: BaseGSTR): Promise<string> {
const toDate = report.toDate!;
const transferType = report.transferType!;
const gstin = await report.fyo.getValue(
ModelNameEnum.AccountingSettings,
'gstin'
);
const gstData: GSTData = {
version: 'GST3.0.4',
hash: 'hash',
gstin: gstin as string,
fp: DateTime.fromISO(toDate).toFormat('MMyyyy'),
};
if (transferType === TransferTypeEnum.B2B) {
gstData.b2b = await generateB2bData(report);
} else if (transferType === TransferTypeEnum.B2CL) {
gstData.b2cl = await generateB2clData(report);
} else if (transferType === TransferTypeEnum.B2CS) {
gstData.b2cs = await generateB2csData(report);
}
return JSON.stringify(gstData);
}
async function generateB2bData(report: BaseGSTR): Promise<B2BCustomer[]> {
const fyo = report.fyo;
const b2b: B2BCustomer[] = [];
const schemaName =
report.gstrType === 'GSTR-1'
? ModelNameEnum.SalesInvoiceItem
: ModelNameEnum.PurchaseInvoiceItem;
const parentSchemaName =
report.gstrType === 'GSTR-1'
? ModelNameEnum.SalesInvoice
: ModelNameEnum.PurchaseInvoice;
for (const row of report.gstrRows ?? []) {
const invRecord: B2BInvRecord = {
inum: row.invNo,
idt: DateTime.fromJSDate(row.invDate).toFormat('dd-MM-yyyy'),
val: row.invAmt,
pos: row.gstin && row.gstin.substring(0, 2),
rchrg: row.reverseCharge,
inv_typ: 'R',
itms: [],
};
const exchangeRate = (
await fyo.db.getAllRaw(parentSchemaName, {
fields: ['exchangeRate'],
filters: { name: invRecord.inum },
})
)[0].exchangeRate as number;
const items = await fyo.db.getAllRaw(schemaName, {
fields: ['amount', 'tax', 'hsnCode'],
filters: { parent: invRecord.inum },
});
items.forEach((item) => {
const hsnCode = item.hsnCode as number;
const tax = item.tax as string;
const baseAmount = fyo
.pesa((item.amount as string) ?? 0)
.mul(exchangeRate);
const itemRecord: B2BItmRecord = {
num: hsnCode,
itm_det: {
txval: baseAmount.float,
rt: GST[tax],
csamt: 0,
camt: fyo
.pesa(CSGST[tax] ?? 0)
.mul(baseAmount)
.div(100).float,
samt: fyo
.pesa(CSGST[tax] ?? 0)
.mul(baseAmount)
.div(100).float,
iamt: fyo
.pesa(IGST[tax] ?? 0)
.mul(baseAmount)
.div(100).float,
},
};
invRecord.itms.push(itemRecord);
});
const customerRecord = b2b.find((b) => b.ctin === row.gstin);
const customer = {
ctin: row.gstin,
inv: [],
} as B2BCustomer;
if (customerRecord) {
customerRecord.inv.push(invRecord);
} else {
customer.inv.push(invRecord);
b2b.push(customer);
}
}
return b2b;
}
async function generateB2clData(
report: BaseGSTR
): Promise<B2CLStateInvoiceRecord[]> {
const fyo = report.fyo;
const b2cl: B2CLStateInvoiceRecord[] = [];
const stateCodeMap = invertMap(codeStateMap);
const schemaName =
report.gstrType === 'GSTR-1'
? ModelNameEnum.SalesInvoiceItem
: ModelNameEnum.PurchaseInvoiceItem;
const parentSchemaName =
report.gstrType === 'GSTR-1'
? ModelNameEnum.SalesInvoice
: ModelNameEnum.PurchaseInvoice;
for (const row of report.gstrRows ?? []) {
const invRecord: B2CLInvRecord = {
inum: row.invNo,
idt: DateTime.fromJSDate(row.invDate).toFormat('dd-MM-yyyy'),
val: row.invAmt,
itms: [],
};
const exchangeRate = (
await fyo.db.getAllRaw(parentSchemaName, {
fields: ['exchangeRate'],
filters: { name: invRecord.inum },
})
)[0].exchangeRate as number;
const items = await fyo.db.getAllRaw(schemaName, {
fields: ['amount', 'tax', 'hsnCode'],
filters: { parent: invRecord.inum },
});
items.forEach((item) => {
const hsnCode = item.hsnCode as number;
const tax = item.tax as string;
const baseAmount = fyo
.pesa((item.amount as string) ?? 0)
.mul(exchangeRate);
const itemRecord: B2CLItmRecord = {
num: hsnCode,
itm_det: {
txval: baseAmount.float,
rt: GST[tax] ?? 0,
csamt: 0,
iamt: fyo
.pesa(row.rate ?? 0)
.mul(baseAmount)
.div(100).float,
},
};
invRecord.itms.push(itemRecord);
});
const stateRecord = b2cl.find((b) => b.pos === stateCodeMap[row.place]);
const stateInvoiceRecord: B2CLStateInvoiceRecord = {
pos: stateCodeMap[row.place],
inv: [],
};
if (stateRecord) {
stateRecord.inv.push(invRecord);
} else {
stateInvoiceRecord.inv.push(invRecord);
b2cl.push(stateInvoiceRecord);
}
}
return b2cl;
}
function generateB2csData(report: BaseGSTR): B2CSInvRecord[] {
const stateCodeMap = invertMap(codeStateMap);
const b2cs: B2CSInvRecord[] = [];
for (const row of report.gstrRows ?? []) {
const invRecord: B2CSInvRecord = {
sply_ty: row.inState ? 'INTRA' : 'INTER',
pos: stateCodeMap[row.place],
typ: 'OE',
txval: row.taxVal,
rt: row.rate,
iamt: !row.inState ? (row.taxVal * row.rate) / 100 : 0,
camt: row.inState ? row.cgstAmt ?? 0 : 0,
samt: row.inState ? row.sgstAmt ?? 0 : 0,
csamt: 0,
};
b2cs.push(invRecord);
}
return b2cs;
}