mirror of
https://github.com/frappe/books.git
synced 2024-12-23 19:39:07 +00:00
352 lines
8.5 KiB
TypeScript
352 lines
8.5 KiB
TypeScript
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';
|
|
import { ModelNameEnum } from 'models/types';
|
|
import { codeStateMap } from 'regional/in';
|
|
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 {
|
|
place?: string;
|
|
toDate?: string;
|
|
fromDate?: string;
|
|
transferType?: TransferType;
|
|
usePagination: boolean = true;
|
|
gstrRows?: GSTRRow[];
|
|
loading: boolean = false;
|
|
|
|
abstract gstrType: GSTRType;
|
|
|
|
get transferTypeMap(): Record<string, string> {
|
|
if (this.gstrType === 'GSTR-2') {
|
|
return {
|
|
B2B: 'B2B',
|
|
};
|
|
}
|
|
|
|
return {
|
|
B2B: 'B2B',
|
|
B2CL: 'B2C-Large',
|
|
B2CS: 'B2C-Small',
|
|
NR: 'Nil Rated, Exempted and Non GST supplies',
|
|
};
|
|
}
|
|
|
|
get schemaName() {
|
|
if (this.gstrType === 'GSTR-1') {
|
|
return ModelNameEnum.SalesInvoice;
|
|
}
|
|
|
|
return ModelNameEnum.PurchaseInvoice;
|
|
}
|
|
|
|
async setReportData(): Promise<void> {
|
|
this.loading = true;
|
|
const gstrRows = await this.getGstrRows();
|
|
const filteredRows = this.filterGstrRows(gstrRows);
|
|
this.gstrRows = filteredRows;
|
|
this.reportData = this.getReportDataFromGSTRRows(filteredRows);
|
|
this.loading = false;
|
|
}
|
|
|
|
getReportDataFromGSTRRows(gstrRows: GSTRRow[]): ReportData {
|
|
const reportData: ReportData = [];
|
|
for (const row of gstrRows) {
|
|
const reportRow: ReportRow = { cells: [] };
|
|
|
|
for (const { fieldname, fieldtype, width } of this.columns) {
|
|
const align = isNumeric(fieldtype) ? 'right' : 'left';
|
|
|
|
const rawValue = row[fieldname as keyof GSTRRow];
|
|
let value = '';
|
|
if (rawValue !== undefined) {
|
|
value = this.fyo.format(rawValue, fieldtype);
|
|
}
|
|
|
|
reportRow.cells.push({
|
|
align,
|
|
rawValue,
|
|
value,
|
|
width: width ?? 1,
|
|
});
|
|
}
|
|
|
|
reportData.push(reportRow);
|
|
}
|
|
|
|
return reportData;
|
|
}
|
|
|
|
filterGstrRows(gstrRows: GSTRRow[]) {
|
|
return gstrRows.filter((row) => {
|
|
let allow = true;
|
|
if (this.place) {
|
|
allow &&= codeStateMap[this.place] === row.place;
|
|
}
|
|
this.place;
|
|
return (allow &&= this.transferFilterFunction(row));
|
|
});
|
|
}
|
|
|
|
get transferFilterFunction(): (row: GSTRRow) => boolean {
|
|
if (this.transferType === 'B2B') {
|
|
return (row) => !!row.gstin;
|
|
}
|
|
|
|
if (this.transferType === 'B2CL') {
|
|
return (row) => !row.gstin && !row.inState && row.invAmt >= 250000;
|
|
}
|
|
|
|
if (this.transferType === 'B2CS') {
|
|
return (row) => !row.gstin && (row.inState || row.invAmt < 250000);
|
|
}
|
|
|
|
if (this.transferType === 'NR') {
|
|
return (row) => row.rate === 0; // this takes care of both nil rated, exempted goods
|
|
}
|
|
|
|
return (_) => true;
|
|
}
|
|
|
|
async getEntries() {
|
|
const date: string[] = [];
|
|
if (this.toDate) {
|
|
date.push('<=', this.toDate);
|
|
}
|
|
|
|
if (this.fromDate) {
|
|
date.push('>=', this.fromDate);
|
|
}
|
|
|
|
return (await this.fyo.db.getAllRaw(this.schemaName, {
|
|
filters: { date, submitted: true, cancelled: false },
|
|
})) as { name: string }[];
|
|
}
|
|
|
|
async getGstrRows(): Promise<GSTRRow[]> {
|
|
const entries = await this.getEntries();
|
|
const gstrRows: GSTRRow[] = [];
|
|
for (const entry of entries) {
|
|
const gstrRow = await this.getGstrRow(entry.name as string);
|
|
gstrRows.push(gstrRow);
|
|
}
|
|
return gstrRows;
|
|
}
|
|
|
|
async getGstrRow(entryName: string): Promise<GSTRRow> {
|
|
const entry = (await this.fyo.doc.getDoc(
|
|
this.schemaName,
|
|
entryName
|
|
)) as Invoice;
|
|
const gstin = (await this.fyo.getValue(
|
|
ModelNameEnum.AccountingSettings,
|
|
'gstin'
|
|
)) as string | null;
|
|
|
|
const party = (await this.fyo.doc.getDoc('Party', entry.party!)) as Party;
|
|
|
|
let place = '';
|
|
if (party.address) {
|
|
const pos = (await this.fyo.getValue(
|
|
ModelNameEnum.Address,
|
|
party.address as string,
|
|
'pos'
|
|
)) as string | undefined;
|
|
|
|
place = pos ?? '';
|
|
} else if (party.gstin) {
|
|
const code = party.gstin.slice(0, 2);
|
|
place = codeStateMap[code] ?? '';
|
|
}
|
|
|
|
let inState = false;
|
|
if (gstin) {
|
|
inState = codeStateMap[gstin.slice(0, 2)] === place;
|
|
}
|
|
|
|
const gstrRow: GSTRRow = {
|
|
gstin: party.gstin ?? '',
|
|
partyName: entry.party!,
|
|
invNo: entry.name!,
|
|
invDate: entry.date!,
|
|
rate: 0,
|
|
reverseCharge: !party.gstin ? 'Y' : 'N',
|
|
inState,
|
|
place,
|
|
invAmt: entry.grandTotal?.float ?? 0,
|
|
taxVal: entry.netTotal?.float ?? 0,
|
|
};
|
|
|
|
for (const tax of entry.taxes ?? []) {
|
|
gstrRow.rate += tax.rate ?? 0;
|
|
}
|
|
|
|
this.setTaxValuesOnGSTRRow(entry, gstrRow);
|
|
return gstrRow;
|
|
}
|
|
|
|
setTaxValuesOnGSTRRow(entry: Invoice, gstrRow: GSTRRow) {
|
|
for (const tax of entry.taxes ?? []) {
|
|
const rate = tax.rate ?? 0;
|
|
gstrRow.rate += rate;
|
|
const taxAmt = entry.netTotal!.percent(rate).float;
|
|
|
|
switch (tax.account) {
|
|
case 'IGST': {
|
|
gstrRow.igstAmt = taxAmt;
|
|
gstrRow.inState = false;
|
|
}
|
|
case 'CGST':
|
|
gstrRow.cgstAmt = taxAmt;
|
|
case 'SGST':
|
|
gstrRow.sgstAmt = taxAmt;
|
|
case 'Nil Rated':
|
|
gstrRow.nilRated = true;
|
|
case 'Exempt':
|
|
gstrRow.exempt = true;
|
|
case 'Non GST':
|
|
gstrRow.nonGST = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
async setDefaultFilters() {
|
|
if (!this.toDate) {
|
|
this.toDate = DateTime.local().toISODate();
|
|
}
|
|
|
|
if (!this.fromDate) {
|
|
this.fromDate = DateTime.local().minus({ months: 3 }).toISODate();
|
|
}
|
|
|
|
if (!this.transferType) {
|
|
this.transferType = 'B2B';
|
|
}
|
|
}
|
|
|
|
getFilters(): Field[] {
|
|
const transferTypeMap = this.transferTypeMap;
|
|
const options = Object.keys(transferTypeMap).map((k) => ({
|
|
value: k,
|
|
label: transferTypeMap[k],
|
|
}));
|
|
|
|
return [
|
|
{
|
|
fieldtype: 'Select',
|
|
label: t`Transfer Type`,
|
|
placeholder: t`Transfer Type`,
|
|
fieldname: 'transferType',
|
|
options,
|
|
} as OptionField,
|
|
{
|
|
fieldtype: 'AutoComplete',
|
|
label: t`Place`,
|
|
placeholder: t`Place`,
|
|
fieldname: 'place',
|
|
options: Object.keys(codeStateMap).map((code) => {
|
|
return {
|
|
value: code,
|
|
label: codeStateMap[code],
|
|
};
|
|
}),
|
|
} as OptionField,
|
|
{
|
|
fieldtype: 'Date',
|
|
label: t`From Date`,
|
|
placeholder: t`From Date`,
|
|
fieldname: 'fromDate',
|
|
},
|
|
{
|
|
fieldtype: 'Date',
|
|
label: t`To Date`,
|
|
placeholder: t`To Date`,
|
|
fieldname: 'toDate',
|
|
},
|
|
];
|
|
}
|
|
|
|
getColumns(): ColumnField[] | Promise<ColumnField[]> {
|
|
const columns = [
|
|
{
|
|
label: t`Party`,
|
|
fieldtype: 'Data',
|
|
fieldname: 'partyName',
|
|
width: 1.5,
|
|
},
|
|
{
|
|
label: t`Invoice No.`,
|
|
fieldname: 'invNo',
|
|
fieldtype: 'Data',
|
|
},
|
|
{
|
|
label: t`Invoice Value`,
|
|
fieldname: 'invAmt',
|
|
fieldtype: 'Currency',
|
|
},
|
|
{
|
|
label: t`Invoice Date`,
|
|
fieldname: 'invDate',
|
|
fieldtype: 'Date',
|
|
},
|
|
{
|
|
label: t`Place of supply`,
|
|
fieldname: 'place',
|
|
fieldtype: 'Data',
|
|
},
|
|
{
|
|
label: t`Rate`,
|
|
fieldname: 'rate',
|
|
width: 0.5,
|
|
},
|
|
{
|
|
label: t`Taxable Value`,
|
|
fieldname: 'taxVal',
|
|
fieldtype: 'Currency',
|
|
},
|
|
{
|
|
label: t`Reverse Chrg.`,
|
|
fieldname: 'reverseCharge',
|
|
fieldtype: 'Data',
|
|
},
|
|
{
|
|
label: t`Intergrated Tax`,
|
|
fieldname: 'igstAmt',
|
|
fieldtype: 'Currency',
|
|
},
|
|
{
|
|
label: t`Central Tax`,
|
|
fieldname: 'cgstAmt',
|
|
fieldtype: 'Currency',
|
|
},
|
|
{
|
|
label: t`State Tax`,
|
|
fieldname: 'sgstAmt',
|
|
fieldtype: 'Currency',
|
|
},
|
|
] as ColumnField[];
|
|
|
|
const transferType = this.transferType ?? TransferTypeEnum.B2B;
|
|
if (transferType === TransferTypeEnum.B2B) {
|
|
columns.unshift({
|
|
label: t`GSTIN No.`,
|
|
fieldname: 'gstin',
|
|
fieldtype: 'Data',
|
|
width: 1.5,
|
|
});
|
|
}
|
|
|
|
return columns;
|
|
}
|
|
|
|
getActions(): Action[] {
|
|
return getGSTRExportActions(this);
|
|
}
|
|
}
|