diff --git a/accounting/gst.js b/accounting/gst.js
index db99ca0a..136110e6 100644
--- a/accounting/gst.js
+++ b/accounting/gst.js
@@ -5,6 +5,46 @@ import { DateTime } from 'luxon';
import { saveExportData } from '../reports/commonExporter';
import { getSavePath } from '../src/utils';
+// prettier-ignore
+export const stateCodeMap = {
+ 'JAMMU AND KASHMIR': '1',
+ 'HIMACHAL PRADESH': '2',
+ 'PUNJAB': '3',
+ 'CHANDIGARH': '4',
+ 'UTTARAKHAND': '5',
+ 'HARYANA': '6',
+ 'DELHI': '7',
+ 'RAJASTHAN': '8',
+ 'UTTAR PRADESH': '9',
+ 'BIHAR': '10',
+ 'SIKKIM': '11',
+ 'ARUNACHAL PRADESH': '12',
+ 'NAGALAND': '13',
+ 'MANIPUR': '14',
+ 'MIZORAM': '15',
+ 'TRIPURA': '16',
+ 'MEGHALAYA': '17',
+ 'ASSAM': '18',
+ 'WEST BENGAL': '19',
+ 'JHARKHAND': '20',
+ 'ODISHA': '21',
+ 'CHATTISGARH': '22',
+ 'MADHYA PRADESH': '23',
+ 'GUJARAT': '24',
+ 'DADRA AND NAGAR HAVELI AND DAMAN AND DIU': '26',
+ 'MAHARASHTRA': '27',
+ 'KARNATAKA': '29',
+ 'GOA': '30',
+ 'LAKSHADWEEP': '31',
+ 'KERALA': '32',
+ 'TAMIL NADU': '33',
+ 'PUDUCHERRY': '34',
+ 'ANDAMAN AND NICOBAR ISLANDS': '35',
+ 'TELANGANA': '36',
+ 'ANDHRA PRADESH': '37',
+ 'LADAKH': '38',
+};
+
const GST = {
'GST-0': 0,
'GST-0.25': 0.25,
@@ -112,7 +152,7 @@ async function generateB2bData(rows) {
items.forEach((item) => {
const itemRecord = {
- num: item.hsnCode || 0,
+ num: item.hsnCode,
itm_det: {
txval: item.baseAmount,
rt: GST[item.tax],
@@ -140,9 +180,75 @@ async function generateB2bData(rows) {
}
async function generateB2clData(invoices) {
- return [];
+ const b2cl = [];
+
+ for (let invoice of invoices) {
+ const stateInvoiceRecord = {
+ pos: stateCodeMap[invoice.place.toUpperCase()],
+ inv: [],
+ };
+
+ const invRecord = {
+ inum: invoice.invNo,
+ idt: DateTime.fromFormat(invoice.invDate, 'yyyy-MM-dd').toFormat(
+ 'dd-MM-yyyy'
+ ),
+ val: invoice.invAmt,
+ itms: [],
+ };
+
+ let items = await frappe.db
+ .knex('SalesInvoiceItem')
+ .where('parent', invRecord.inum);
+
+ items.forEach((item) => {
+ const itemRecord = {
+ num: item.hsnCode,
+ itm_det: {
+ txval: item.baseAmount,
+ rt: GST[item.tax],
+ csamt: 0,
+ iamt: ((invoice.rate || 0) * item.baseAmount) / 100,
+ },
+ };
+
+ invRecord.itms.push(itemRecord);
+ });
+
+ const stateRecord = b2cl.find((b) => b.pos === stateCodeMap[invoice.place]);
+
+ if (stateRecord) {
+ stateRecord.inv.push(invRecord);
+ } else {
+ stateInvoiceRecord.inv.push(invRecord);
+ b2cl.push(stateInvoiceRecord);
+ }
+ }
+
+ return b2cl;
}
async function generateB2csData(invoices) {
- return [];
+ const b2cs = [];
+
+ for (let invoice of invoices) {
+ const pos = invoice.place.toUpperCase();
+
+ const invRecord = {
+ sply_ty: invoice.inState ? 'INTRA' : 'INTER',
+ pos: stateCodeMap[pos],
+ // "OE" - Abbreviation for errors and omissions excepted.
+ typ: 'OE',
+ txval: invoice.taxVal,
+ rt: invoice.rate,
+ iamt: !invoice.inState ? (invoice.taxVal * invoice.rate) / 100 : 0,
+ camt: invoice.inState ? invoice.cgstAmt : 0,
+ samt: invoice.inState ? invoice.sgstAmt : 0,
+ csamt: 0,
+ };
+
+ b2cs.push(invRecord);
+ }
+
+ return b2cs;
}
diff --git a/models/doctype/Address/Address.js b/models/doctype/Address/Address.js
index 4767270a..585cf2a0 100644
--- a/models/doctype/Address/Address.js
+++ b/models/doctype/Address/Address.js
@@ -1,6 +1,20 @@
+import { stateCodeMap } from '../../../accounting/gst';
+import countryList from '../../../fixtures/countryInfo.json';
+import { titleCase } from '../../../src/utils';
+
+function getStates(doc) {
+ switch (doc.country) {
+ case 'India':
+ return Object.keys(stateCodeMap).map(titleCase).sort();
+ default:
+ return [];
+ }
+}
+
export default {
name: 'Address',
doctype: 'DocType',
+ regional: 1,
isSingle: 0,
keywordFields: [
'addressLine1',
@@ -8,7 +22,7 @@ export default {
'city',
'state',
'country',
- 'postalCode'
+ 'postalCode',
],
fields: [
{
@@ -22,69 +36,71 @@ export default {
fieldname: 'addressLine2',
label: 'Address Line 2',
placeholder: 'Address Line 2',
- fieldtype: 'Data'
+ fieldtype: 'Data',
},
{
fieldname: 'city',
label: 'City / Town',
placeholder: 'City / Town',
fieldtype: 'Data',
- required: 1
+ required: 1,
},
{
fieldname: 'state',
label: 'State',
placeholder: 'State',
- fieldtype: 'Data'
+ fieldtype: 'AutoComplete',
+ getList: getStates,
},
{
fieldname: 'country',
label: 'Country',
placeholder: 'Country',
- fieldtype: 'Data',
- required: 1
+ fieldtype: 'AutoComplete',
+ getList: () => Object.keys(countryList).sort(),
+ required: 1,
},
{
fieldname: 'postalCode',
label: 'Postal Code',
placeholder: 'Postal Code',
- fieldtype: 'Data'
+ fieldtype: 'Data',
},
{
fieldname: 'emailAddress',
label: 'Email Address',
placeholder: 'Email Address',
- fieldtype: 'Data'
+ fieldtype: 'Data',
},
{
fieldname: 'phone',
label: 'Phone',
placeholder: 'Phone',
- fieldtype: 'Data'
+ fieldtype: 'Data',
},
{
fieldname: 'fax',
label: 'Fax',
- fieldtype: 'Data'
+ fieldtype: 'Data',
},
{
fieldname: 'addressDisplay',
fieldtype: 'Text',
label: 'Address Display',
readOnly: true,
- formula: doc => {
+ formula: (doc) => {
return [
doc.addressLine1,
doc.addressLine2,
doc.city,
doc.state,
doc.country,
- doc.postalCode
+ doc.postalCode,
]
.filter(Boolean)
.join(', ');
- }
- }
+ },
+ },
],
quickEditFields: [
'addressLine1',
@@ -92,7 +108,7 @@ export default {
'city',
'state',
'country',
- 'postalCode'
+ 'postalCode',
],
- inlineEditDisplayField: 'addressDisplay'
+ inlineEditDisplayField: 'addressDisplay',
};
diff --git a/models/doctype/Address/RegionalChanges.js b/models/doctype/Address/RegionalChanges.js
new file mode 100644
index 00000000..26ca0bf7
--- /dev/null
+++ b/models/doctype/Address/RegionalChanges.js
@@ -0,0 +1,30 @@
+import { cloneDeep } from 'lodash';
+import { stateCodeMap } from '../../../accounting/gst';
+import { titleCase } from '../../../src/utils';
+import AddressOriginal from './Address';
+
+export default function getAugmentedAddress({ country }) {
+ const Address = cloneDeep(AddressOriginal);
+ if (!country) {
+ return Address;
+ }
+
+ const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
+ if (country === 'India') {
+ Address.fields = [
+ ...Address.fields,
+ {
+ fieldname: 'pos',
+ label: 'Place of Supply',
+ fieldtype: 'AutoComplete',
+ placeholder: 'Place of Supply',
+ formula: (doc) => (stateList.includes(doc.state) ? doc.state : ''),
+ getList: () => stateList,
+ },
+ ];
+
+ Address.quickEditFields = [...Address.quickEditFields, 'pos'];
+ }
+
+ return Address;
+}
diff --git a/package.json b/package.json
index b3c0df35..1b8e51a6 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,6 @@
"knex": "^0.95.12",
"lodash": "^4.17.21",
"luxon": "^2.0.2",
- "portal-vue": "^2.1.7",
"sqlite3": "^5.0.2",
"vue": "^2.6.14",
"vue-router": "^3.5.3"
diff --git a/reports/BalanceSheet/viewConfig.js b/reports/BalanceSheet/viewConfig.js
index 15f91ff8..922d172e 100644
--- a/reports/BalanceSheet/viewConfig.js
+++ b/reports/BalanceSheet/viewConfig.js
@@ -27,7 +27,7 @@ export default {
},
],
actions: getCommonExportActions('balance-sheet'),
- getColumns(data) {
+ getColumns({ data }) {
const columns = [
{ label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 2 },
];
diff --git a/reports/GoodsAndServiceTax/BaseGSTR.js b/reports/GoodsAndServiceTax/BaseGSTR.js
index 8c14ab30..118aff0a 100644
--- a/reports/GoodsAndServiceTax/BaseGSTR.js
+++ b/reports/GoodsAndServiceTax/BaseGSTR.js
@@ -1,22 +1,27 @@
import frappe from 'frappejs';
+import { stateCodeMap } from '../../accounting/gst';
class BaseGSTR {
async getCompleteReport(gstrType, filters) {
if (['GSTR-1', 'GSTR-2'].includes(gstrType)) {
+ const place = filters.place;
+ delete filters.place;
let entries = await frappe.db.getAll({
doctype: gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice',
- filters
+ filters,
});
+ filters.place = place;
let tableData = [];
for (let entry of entries) {
- entry.doctype = gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice';
+ entry.doctype =
+ gstrType === 'GSTR-1' ? 'SalesInvoice' : 'PurchaseInvoice';
const row = await this.getRow(entry);
tableData.push(row);
}
if (Object.keys(filters).length != 0) {
- tableData = tableData.filter(row => {
+ tableData = tableData.filter((row) => {
if (filters.account) return row.account === filters.account;
if (filters.transferType)
return row.transferType === filters.transferType;
@@ -24,6 +29,7 @@ class BaseGSTR {
return true;
});
}
+
return tableData;
} else {
return [];
@@ -31,36 +37,55 @@ class BaseGSTR {
}
async getRow(ledgerEntry) {
- let row = {};
ledgerEntry = await frappe.getDoc(ledgerEntry.doctype, ledgerEntry.name);
+
+ const row = {};
+ const { gstin } = frappe.AccountingSettings;
+
let party = await frappe.getDoc(
'Party',
ledgerEntry.customer || ledgerEntry.supplier
);
+
if (party.address) {
let addressDetails = await frappe.getDoc('Address', party.address);
- row.place = addressDetails.state || '';
+ row.place = addressDetails.pos || '';
}
+
row.gstin = party.gstin;
row.partyName = ledgerEntry.customer || ledgerEntry.supplier;
row.invNo = ledgerEntry.name;
row.invDate = ledgerEntry.date;
row.rate = 0;
- row.inState = true;
+ row.inState =
+ gstin && gstin.substring(0, 2) === stateCodeMap[row.place?.toUpperCase()];
row.reverseCharge = !party.gstin ? 'Y' : 'N';
- ledgerEntry.taxes?.forEach(tax => {
+
+ ledgerEntry.taxes?.forEach((tax) => {
row.rate += tax.rate;
const taxAmt = (tax.rate * ledgerEntry.netTotal) / 100;
- if (tax.account === 'IGST') row.igstAmt = taxAmt;
- if (tax.account === 'IGST') row.inState = false;
- if (tax.account === 'CGST') row.cgstAmt = taxAmt;
- if (tax.account === 'SGST') row.sgstAmt = taxAmt;
- if (tax.account === 'Nil Rated') row.nilRated = true;
- if (tax.account === 'Exempt') row.exempt = true;
- if (tax.account === 'Non GST') row.nonGST = true;
+
+ switch (tax.account) {
+ case 'IGST': {
+ row.igstAmt = taxAmt;
+ row.inState = false;
+ }
+ case 'CGST':
+ row.cgstAmt = taxAmt;
+ case 'SGST':
+ row.sgstAmt = taxAmt;
+ case 'Nil Rated':
+ row.nilRated = true;
+ case 'Exempt':
+ row.exempt = true;
+ case 'Non GST':
+ row.nonGST = true;
+ }
});
+
row.invAmt = ledgerEntry.grandTotal;
row.taxVal = ledgerEntry.netTotal;
+
return row;
}
}
diff --git a/reports/GoodsAndServiceTax/BaseViewConfig.js b/reports/GoodsAndServiceTax/BaseViewConfig.js
index 2fed4496..f1e84d9a 100644
--- a/reports/GoodsAndServiceTax/BaseViewConfig.js
+++ b/reports/GoodsAndServiceTax/BaseViewConfig.js
@@ -1,5 +1,6 @@
import { DateTime } from 'luxon';
-import { generateGstr1Json } from '../../accounting/gst';
+import { generateGstr1Json, stateCodeMap } from '../../accounting/gst';
+import { titleCase } from '../../src/utils';
const transferTypeMap = {
B2B: 'B2B',
@@ -7,6 +8,7 @@ const transferTypeMap = {
B2CS: 'B2C-Small',
NR: 'Nil Rated, Exempted and Non GST supplies',
};
+const stateList = Object.keys(stateCodeMap).map(titleCase).sort();
export default {
filterFields: [
@@ -21,11 +23,12 @@ export default {
size: 'small',
},
{
- fieldtype: 'Data',
+ fieldtype: 'AutoComplete',
label: 'Place',
size: 'small',
placeholder: 'Place',
fieldname: 'place',
+ getList: () => stateList,
},
{
fieldtype: 'Date',
@@ -55,14 +58,8 @@ export default {
},
],
- getColumns() {
- return [
- {
- label: 'GSTIN No.',
- fieldname: 'gstin',
- fieldtype: 'Data',
- width: 1.5,
- },
+ getColumns({ filters }) {
+ const columns = [
{
label: 'Party',
fieldtype: 'Data',
@@ -121,5 +118,17 @@ export default {
fieldtype: 'Currency',
},
];
+
+ const transferType = filters.transferType || 'B2B';
+ if (transferType === 'B2B') {
+ columns.unshift({
+ label: 'GSTIN No.',
+ fieldname: 'gstin',
+ fieldtype: 'Data',
+ width: 1.5,
+ });
+ }
+
+ return columns;
},
};
diff --git a/reports/GoodsAndServiceTax/GSTR1.js b/reports/GoodsAndServiceTax/GSTR1.js
index 57e8e8de..8759da5e 100644
--- a/reports/GoodsAndServiceTax/GSTR1.js
+++ b/reports/GoodsAndServiceTax/GSTR1.js
@@ -8,6 +8,8 @@ class GSTR1 extends BaseGSTR {
filters.cancelled = 0;
if (params.toDate || params.fromDate) {
filters.date = [];
+
+ if (params.place) filters.place = params.place;
if (params.toDate) filters.date.push('<=', params.toDate);
if (params.fromDate) filters.date.push('>=', params.fromDate);
}
@@ -18,7 +20,7 @@ class GSTR1 extends BaseGSTR {
const conditions = {
'B2B': row => row.gstin,
'B2CL': row => !row.gstin && !row.inState && row.invAmt >= 250000,
- 'B2CS': row => !row.gstin && (row.inState || (row.inState && row.invAmt < 250000)),
+ 'B2CS': row => !row.gstin && (row.inState || row.invAmt < 250000),
'NR': row => (row.rate === 0), // this takes care of both nil rated, exempted goods
};
diff --git a/reports/ProfitAndLoss/viewConfig.js b/reports/ProfitAndLoss/viewConfig.js
index 04878d51..fb34074f 100644
--- a/reports/ProfitAndLoss/viewConfig.js
+++ b/reports/ProfitAndLoss/viewConfig.js
@@ -41,7 +41,7 @@ export default {
},
],
actions: getCommonExportActions('profit-and-loss'),
- getColumns(data) {
+ getColumns({ data }) {
const columns = [
{ label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 2 },
];
diff --git a/reports/TrialBalance/viewConfig.js b/reports/TrialBalance/viewConfig.js
index 083f66a9..ff16dac3 100644
--- a/reports/TrialBalance/viewConfig.js
+++ b/reports/TrialBalance/viewConfig.js
@@ -32,7 +32,7 @@ export default {
},
],
actions: getCommonExportActions('trial-balance'),
- getColumns(data) {
+ getColumns() {
const columns = [
{ label: 'Account', fieldtype: 'Data', fieldname: 'account', width: 2 },
{
diff --git a/src/App.vue b/src/App.vue
index 25f1842b..67feed74 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -18,7 +18,6 @@
@setup-complete="showSetupWizardOrDesk(true)"
@setup-canceled="setupCanceled"
/>
-