diff --git a/accounting/gst.js b/accounting/gst.js index db99ca0a..aa82533a 100644 --- a/accounting/gst.js +++ b/accounting/gst.js @@ -5,6 +5,45 @@ import { DateTime } from 'luxon'; import { saveExportData } from '../reports/commonExporter'; import { getSavePath } from '../src/utils'; +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, @@ -75,9 +114,9 @@ export async function generateGstr1Json(getReportData) { if (transferType === 'B2B') { gstData.b2b = await generateB2bData(rows); - } else if (transferType === 'B2CL') { + } else if (transferType === 'B2C-Large') { gstData.b2cl = await generateB2clData(rows); - } else if (transferType === 'B2CS') { + } else if (transferType === 'B2C-Small') { gstData.b2cs = await generateB2csData(rows); } @@ -112,7 +151,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 +179,79 @@ async function generateB2bData(rows) { } async function generateB2clData(invoices) { - return []; + const b2cl = []; + + invoices.forEach(async (invoice) => { + + 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 = []; + + invoices.forEach(async (invoice) => { + + const pos = invoice.place.toUpperCase(); + + const invRecord = { + "sply_ty": invoice.inState ? "INTRA" : "INTER", + "pos": stateCodeMap[pos], + // "OE" - Abbreviation for errors and omissions excepted. + // https://specialties.bayt.com/en/specialties/q/53093/what-is-meant-by-e-amp-oe-on-bill-or-invoice-or-any-document/#:~:text=E%26OE%20on,not%20purposely%20written + "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..d1cd5429 100644 --- a/models/doctype/Address/Address.js +++ b/models/doctype/Address/Address.js @@ -1,12 +1,12 @@ export default { name: 'Address', doctype: 'DocType', + regional: 1, isSingle: 0, keywordFields: [ 'addressLine1', 'addressLine2', 'city', - 'state', 'country', 'postalCode' ], @@ -31,12 +31,6 @@ export default { fieldtype: 'Data', required: 1 }, - { - fieldname: 'state', - label: 'State', - placeholder: 'State', - fieldtype: 'Data' - }, { fieldname: 'country', label: 'Country', @@ -90,7 +84,6 @@ export default { 'addressLine1', 'addressLine2', 'city', - 'state', 'country', 'postalCode' ], diff --git a/models/doctype/Address/RegionalChanges.js b/models/doctype/Address/RegionalChanges.js new file mode 100644 index 00000000..68ba7125 --- /dev/null +++ b/models/doctype/Address/RegionalChanges.js @@ -0,0 +1,52 @@ +import { cloneDeep, capitalize } from 'lodash'; +import AddressOriginal from './Address'; +import { stateCodeMap } from '../../../accounting/gst'; + +export default function getAugmentedAddress({ country }) { + const Address = cloneDeep(AddressOriginal); + if (!country) { + return Address; + } + + const cityFieldIndex = Address.fields.findIndex( + (i) => i.fieldname === 'city' + ); + if (country === 'India') { + Address.fields = [ + ...Address.fields.slice(0, cityFieldIndex + 1), + { + fieldname: 'state', + label: 'State', + fieldtype: 'Select', + placeholder: 'State', + options: Object.keys(stateCodeMap).map((key) => capitalize(key)), + }, + ...Address.fields.slice(cityFieldIndex + 1, Address.fields.length), + ]; + // Setting country as India for customers as default for SMBs in India + Address.fields.forEach((field) => { + if (field.fieldname === 'country') { + field.default = 'India'; + } + }); + } else { + Address.fields = [ + ...Address.fields.slice(0, cityFieldIndex + 1), + { + fieldname: 'state', + label: 'State', + placeholder: 'State', + fieldtype: 'Data', + }, + ...Address.fields.slice(cityFieldIndex + 1, Address.fields.length), + ]; + } + + Address.quickEditFields = [ + ...Address.quickEditFields.slice(0, cityFieldIndex + 1), + 'state', + ...Address.quickEditFields.slice(cityFieldIndex + 1, Address.fields.length), + ]; + + return Address; +} \ No newline at end of file diff --git a/reports/GoodsAndServiceTax/BaseGSTR.js b/reports/GoodsAndServiceTax/BaseGSTR.js index 8c14ab30..5a766a3f 100644 --- a/reports/GoodsAndServiceTax/BaseGSTR.js +++ b/reports/GoodsAndServiceTax/BaseGSTR.js @@ -1,4 +1,5 @@ import frappe from 'frappejs'; +import { stateCodeMap } from '../../accounting/gst' class BaseGSTR { async getCompleteReport(gstrType, filters) { @@ -33,6 +34,7 @@ class BaseGSTR { async getRow(ledgerEntry) { let row = {}; ledgerEntry = await frappe.getDoc(ledgerEntry.doctype, ledgerEntry.name); + const { gstin } = frappe.AccountingSettings; let party = await frappe.getDoc( 'Party', ledgerEntry.customer || ledgerEntry.supplier @@ -46,7 +48,7 @@ class BaseGSTR { row.invNo = ledgerEntry.name; row.invDate = ledgerEntry.date; row.rate = 0; - row.inState = true; + row.inState = gstin.substring(0, 2) === stateCodeMap[row.place]; row.reverseCharge = !party.gstin ? 'Y' : 'N'; ledgerEntry.taxes?.forEach(tax => { row.rate += tax.rate; diff --git a/reports/GoodsAndServiceTax/GSTR1.js b/reports/GoodsAndServiceTax/GSTR1.js index 57e8e8de..e0a8e7d3 100644 --- a/reports/GoodsAndServiceTax/GSTR1.js +++ b/reports/GoodsAndServiceTax/GSTR1.js @@ -18,7 +18,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 };