mirror of
https://github.com/frappe/books.git
synced 2025-01-31 02:58:31 +00:00
incr: add gstr exports
This commit is contained in:
parent
e20d7fc6d2
commit
5a633b9d07
@ -56,10 +56,6 @@ export class Converter {
|
||||
}
|
||||
|
||||
static toDocValue(value: RawValue, field: Field, fyo: Fyo): DocValue {
|
||||
if (!field?.fieldtype) {
|
||||
console.log(value, field);
|
||||
console.trace();
|
||||
}
|
||||
switch (field.fieldtype) {
|
||||
case FieldTypeEnum.Currency:
|
||||
return toDocCurrency(value, field, fyo);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { t } from 'fyo';
|
||||
import { Action } from 'fyo/model/types';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Invoice } from 'models/baseModels/Invoice/Invoice';
|
||||
import { Party } from 'models/regionalModels/in/Party';
|
||||
@ -8,6 +9,7 @@ import { Report } from 'reports/Report';
|
||||
import { ColumnField, ReportData, ReportRow } from 'reports/types';
|
||||
import { Field, OptionField } from 'schemas/types';
|
||||
import { isNumeric } from 'src/utils';
|
||||
import getGSTRExportActions from './gstExporter';
|
||||
import { GSTRRow, GSTRType, TransferType, TransferTypeEnum } from './types';
|
||||
|
||||
export abstract class BaseGSTR extends Report {
|
||||
@ -16,6 +18,7 @@ export abstract class BaseGSTR extends Report {
|
||||
fromDate?: string;
|
||||
transferType?: TransferType;
|
||||
usePagination: boolean = true;
|
||||
gstrRows?: GSTRRow[];
|
||||
|
||||
abstract gstrType: GSTRType;
|
||||
|
||||
@ -45,6 +48,7 @@ export abstract class BaseGSTR extends Report {
|
||||
async setReportData(): Promise<void> {
|
||||
const gstrRows = await this.getGstrRows();
|
||||
const filteredRows = this.filterGstrRows(gstrRows);
|
||||
this.gstrRows = filteredRows;
|
||||
this.reportData = this.getReportDataFromGSTRRows(filteredRows);
|
||||
}
|
||||
|
||||
@ -337,4 +341,8 @@ export abstract class BaseGSTR extends Report {
|
||||
|
||||
return columns;
|
||||
}
|
||||
|
||||
getActions(): Action[] {
|
||||
return getGSTRExportActions(this);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Action } from 'fyo/model/types';
|
||||
import { BaseGSTR } from './BaseGSTR';
|
||||
import { GSTRType } from './types';
|
||||
|
||||
@ -7,7 +6,4 @@ export class GSTR1 extends BaseGSTR {
|
||||
static reportName = 'gstr-1';
|
||||
|
||||
gstrType: GSTRType = 'GSTR-1';
|
||||
getActions(): Action[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { Action } from 'fyo/model/types';
|
||||
import { BaseGSTR } from './BaseGSTR';
|
||||
import { GSTRType } from './types';
|
||||
|
||||
@ -7,7 +6,4 @@ export class GSTR2 extends BaseGSTR {
|
||||
static reportName = 'gstr-2';
|
||||
|
||||
gstrType: GSTRType = 'GSTR-2';
|
||||
getActions(): Action[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
367
reports/GoodsAndServiceTax/gstExporter.ts
Normal file
367
reports/GoodsAndServiceTax/gstExporter.ts
Normal file
@ -0,0 +1,367 @@
|
||||
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;
|
||||
|
||||
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 items = await fyo.db.getAllRaw(schemaName, {
|
||||
fields: ['baseAmount', 'tax', 'hsnCode'],
|
||||
filters: { parent: invRecord.inum as string },
|
||||
});
|
||||
|
||||
items.forEach((item) => {
|
||||
const hsnCode = item.hsnCode as number;
|
||||
const tax = item.tax as string;
|
||||
const baseAmount = (item.baseAmount ?? 0) as string;
|
||||
|
||||
const itemRecord: B2BItmRecord = {
|
||||
num: hsnCode,
|
||||
itm_det: {
|
||||
txval: fyo.pesa(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;
|
||||
|
||||
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 items = await fyo.db.getAllRaw(schemaName, {
|
||||
fields: ['hsnCode', 'tax', 'baseAmount'],
|
||||
filters: { parent: invRecord.inum },
|
||||
});
|
||||
|
||||
items.forEach((item) => {
|
||||
const hsnCode = item.hsnCode as number;
|
||||
const tax = item.tax as string;
|
||||
const baseAmount = (item.baseAmount ?? 0) as string;
|
||||
|
||||
const itemRecord: B2CLItmRecord = {
|
||||
num: hsnCode,
|
||||
itm_det: {
|
||||
txval: fyo.pesa(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;
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
|
||||
export enum TransferTypeEnum {
|
||||
'B2B' = 'B2B',
|
||||
'B2CL' = 'B2C',
|
||||
'B2CS' = 'B2C',
|
||||
'B2CL' = 'B2CL',
|
||||
'B2CS' = 'B2CS',
|
||||
'NR' = 'NR',
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,7 @@ import { getSavePath, saveData, showExportInFolder } from 'src/utils/ipcCalls';
|
||||
import { getIsNullOrUndef } from 'utils';
|
||||
import { generateCSV } from 'utils/csvParser';
|
||||
import { Report } from './Report';
|
||||
import { ReportCell } from './types';
|
||||
|
||||
type ExportExtention = 'csv' | 'json';
|
||||
import { ExportExtention, ReportCell } from './types';
|
||||
|
||||
interface JSONExport {
|
||||
columns: { fieldname: string; label: string }[];
|
||||
@ -42,21 +40,23 @@ async function exportReport(extention: ExportExtention, report: Report) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (extention) {
|
||||
case 'csv':
|
||||
await exportCsv(report, filePath);
|
||||
break;
|
||||
case 'json':
|
||||
await exportJson(report, filePath);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
let data = '';
|
||||
|
||||
if (extention === 'csv') {
|
||||
data = getCsvData(report);
|
||||
} else if (extention === 'json') {
|
||||
data = getJsonData(report);
|
||||
}
|
||||
|
||||
if (!data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
await saveExportData(data, filePath, report.fyo);
|
||||
report.fyo.telemetry.log(Verb.Exported, report.reportName, { extention });
|
||||
}
|
||||
|
||||
async function exportJson(report: Report, filePath: string) {
|
||||
function getJsonData(report: Report): string {
|
||||
const exportObject: JSONExport = {
|
||||
columns: [],
|
||||
rows: [],
|
||||
@ -117,13 +117,12 @@ async function exportJson(report: Report, filePath: string) {
|
||||
exportObject.softwareName = 'Frappe Books';
|
||||
exportObject.softwareVersion = report.fyo.store.appVersion;
|
||||
|
||||
await saveExportData(JSON.stringify(exportObject), filePath, report.fyo);
|
||||
return JSON.stringify(exportObject);
|
||||
}
|
||||
|
||||
export async function exportCsv(report: Report, filePath: string) {
|
||||
export function getCsvData(report: Report): string {
|
||||
const csvMatrix = convertReportToCSVMatrix(report);
|
||||
const csvString = generateCSV(csvMatrix);
|
||||
saveExportData(csvString, filePath, report.fyo);
|
||||
return generateCSV(csvMatrix);
|
||||
}
|
||||
|
||||
function convertReportToCSVMatrix(report: Report): unknown[][] {
|
||||
|
@ -2,7 +2,7 @@ import { DateTime } from 'luxon';
|
||||
import { AccountRootType } from 'models/baseModels/Account/types';
|
||||
import { BaseField, RawValue } from 'schemas/types';
|
||||
|
||||
export type ExportExtension = 'csv' | 'json';
|
||||
export type ExportExtention = 'csv' | 'json';
|
||||
|
||||
export interface ReportCell {
|
||||
bold?: boolean;
|
||||
|
Loading…
x
Reference in New Issue
Block a user