diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js index 8341456d9e7..c1571ee9f4e 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js @@ -585,6 +585,16 @@ frappe.ui.form.on('Sales Invoice', { }; }); + frm.set_query("adjustment_against", function() { + return { + filters: { + company: frm.doc.company, + customer: frm.doc.customer, + docstatus: 1 + } + }; + }); + frm.custom_make_buttons = { 'Delivery Note': 'Delivery', 'Sales Invoice': 'Return / Credit Note', @@ -870,6 +880,10 @@ frappe.ui.form.on('Sales Invoice', { }) } + if (frm.doc.is_debit_note) { + frm.set_df_property('return_against', 'label', 'Adjustment Against'); + } + if (frappe.boot.active_domains.includes("Healthcare")) { frm.set_df_property("patient", "hidden", 0); frm.set_df_property("patient_name", "hidden", 0); diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json index 018bc7e6419..932c8dcbb30 100644 --- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json +++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json @@ -17,6 +17,8 @@ "pos_profile", "offline_pos_name", "is_return", + "is_debit_note", + "update_billed_amount_in_sales_order", "column_break1", "company", "company_tax_id", @@ -405,15 +407,7 @@ "read_only": 1 }, { - "depends_on": "return_against", - "fieldname": "returns", - "fieldtype": "Section Break", - "hide_days": 1, - "hide_seconds": 1, - "label": "Returns" - }, - { - "depends_on": "return_against", + "depends_on": "eval:doc.return_against || doc.is_debit_note", "fieldname": "return_against", "fieldtype": "Link", "hide_days": 1, @@ -422,7 +416,7 @@ "no_copy": 1, "options": "Sales Invoice", "print_hide": 1, - "read_only": 1, + "read_only_depends_on": "eval:doc.is_return", "search_index": 1 }, { @@ -1982,6 +1976,12 @@ "fieldtype": "Link", "label": "Set Target Warehouse", "options": "Warehouse" + }, + { + "default": "0", + "fieldname": "is_debit_note", + "fieldtype": "Check", + "label": "Is Debit Note" } ], "icon": "fa fa-file-text", @@ -1995,7 +1995,7 @@ "link_fieldname": "consolidated_invoice" } ], - "modified": "2021-01-12 12:16:15.192520", + "modified": "2021-04-22 22:36:32.916354", "modified_by": "Administrator", "module": "Accounts", "name": "Sales Invoice", diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index 2776d8a9ef0..b474b04bad1 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -176,8 +176,6 @@ def get_regional_address_details(party_details, doctype, company): if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"): master_doctype = "Sales Taxes and Charges Template" - - get_tax_template_for_sez(party_details, master_doctype, company, 'Customer') get_tax_template_based_on_category(master_doctype, company, party_details) if party_details.get('taxes_and_charges'): @@ -188,7 +186,6 @@ def get_regional_address_details(party_details, doctype, company): elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"): master_doctype = "Purchase Taxes and Charges Template" - get_tax_template_for_sez(party_details, master_doctype, company, 'Supplier') get_tax_template_based_on_category(master_doctype, company, party_details) if party_details.get('taxes_and_charges'): @@ -260,20 +257,6 @@ def get_tax_template(master_doctype, company, is_inter_state, state_code): {'company': company, 'disabled': 0, 'tax_category': tax_category.name}, 'name') return default_tax -def get_tax_template_for_sez(party_details, master_doctype, company, party_type): - - gst_details = frappe.db.get_value(party_type, {'name': party_details.get(frappe.scrub(party_type))}, - ['gst_category', 'export_type'], as_dict=1) - - if gst_details: - if gst_details.gst_category == 'SEZ' and gst_details.export_type == 'With Payment of Tax': - default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0, - "gst_state": number_state_mapping[party_details.company_gstin[:2]]}) - - party_details["taxes_and_charges"] = default_tax - party_details.taxes = get_taxes_and_charges(master_doctype, default_tax) - - def calculate_annual_eligible_hra_exemption(doc): basic_component, hra_component = frappe.db.get_value('Company', doc.company, ["basic_component", "hra_component"]) if not (basic_component and hra_component): diff --git a/erpnext/regional/report/gstr_1/gstr_1.js b/erpnext/regional/report/gstr_1/gstr_1.js index 1a7ff2bf5a6..444f5dbb8ca 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.js +++ b/erpnext/regional/report/gstr_1/gstr_1.js @@ -46,7 +46,13 @@ frappe.query_reports["GSTR-1"] = { "label": __("Type of Business"), "fieldtype": "Select", "reqd": 1, - "options": ["B2B", "B2C Large", "B2C Small", "CDNR", "EXPORT"], + "options": [ + { "value": "B2B", "label": __("B2B Invoices - 4A, 4B, 4C, 6B, 6C") }, + { "value": "B2C Large", "label": __("B2C(Large) Invoices - 5A, 5B") }, + { "value": "B2C Small", "label": __("B2C(Small) Invoices - 7") }, + { "value": "CDNR-REG", "label": __("Credit/Debit Notes (Registered) - 9B") }, + { "value": "EXPORT", "label": __("Export Invoice - 6A") } + ], "default": "B2B" } ], diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py index 564b8d4497a..e1cbbe00649 100644 --- a/erpnext/regional/report/gstr_1/gstr_1.py +++ b/erpnext/regional/report/gstr_1/gstr_1.py @@ -32,6 +32,7 @@ class Gstr1Report(object): reverse_charge, return_against, is_return, + is_debit_note, gst_category, export_type, port_code, @@ -62,9 +63,9 @@ class Gstr1Report(object): for rate, items in items_based_on_rate.items(): row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items) - if self.filters.get("type_of_business") == "CDNR": + if self.filters.get("type_of_business") == "CDNR-REG": row.append("Y" if invoice_details.posting_date <= date(2017, 7, 1) else "N") - row.append("C" if invoice_details.return_against else "R") + row.append("C" if invoice_details.is_return else "D") if taxable_value: self.data.append(row) @@ -105,7 +106,7 @@ class Gstr1Report(object): def get_row_data_for_invoice(self, invoice, invoice_details, tax_rate, items): row = [] for fieldname in self.invoice_fields: - if self.filters.get("type_of_business") == "CDNR" and fieldname == "invoice_value": + if self.filters.get("type_of_business") == "CDNR-REG" and fieldname == "invoice_value": row.append(abs(invoice_details.base_rounded_total) or abs(invoice_details.base_grand_total)) elif fieldname == "invoice_value": row.append(invoice_details.base_rounded_total or invoice_details.base_grand_total) @@ -171,7 +172,7 @@ class Gstr1Report(object): if self.filters.get("type_of_business") == "B2B": - conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1" + conditions += "AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') AND is_return != 1" if self.filters.get("type_of_business") in ("B2C Large", "B2C Small"): b2c_limit = frappe.db.get_single_value('GST Settings', 'b2c_limit') @@ -179,19 +180,19 @@ class Gstr1Report(object): frappe.throw(_("Please set B2C Limit in GST Settings.")) if self.filters.get("type_of_business") == "B2C Large": - conditions += """ and ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') - and grand_total > {0} and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + conditions += """ AND ifnull(SUBSTR(place_of_supply, 1, 2),'') != ifnull(SUBSTR(company_gstin, 1, 2),'') + AND grand_total > {0} AND is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) elif self.filters.get("type_of_business") == "B2C Small": - conditions += """ and ( + conditions += """ AND ( SUBSTR(place_of_supply, 1, 2) = SUBSTR(company_gstin, 1, 2) - or grand_total <= {0}) and is_return != 1 and gst_category ='Unregistered' """.format(flt(b2c_limit)) + OR grand_total <= {0}) and is_return != 1 AND gst_category ='Unregistered' """.format(flt(b2c_limit)) - elif self.filters.get("type_of_business") == "CDNR": - conditions += """ and is_return = 1 """ + elif self.filters.get("type_of_business") == "CDNR-REG": + conditions += """ AND (is_return = 1 OR is_debit_note = 1) AND IFNULL(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ')""" elif self.filters.get("type_of_business") == "EXPORT": - conditions += """ and is_return !=1 and gst_category = 'Overseas' """ + conditions += """ AND is_return !=1 and gst_category = 'Overseas' """ return conditions def get_invoice_items(self): @@ -199,7 +200,7 @@ class Gstr1Report(object): self.item_tax_rate = frappe._dict() items = frappe.db.sql(""" - select item_code, parent, base_net_amount, item_tax_rate + select item_code, parent, taxable_value, base_net_amount, item_tax_rate from `tab%s Item` where parent in (%s) """ % (self.doctype, ', '.join(['%s']*len(self.invoices))), tuple(self.invoices), as_dict=1) @@ -207,7 +208,7 @@ class Gstr1Report(object): for d in items: if d.item_code not in self.invoice_items.get(d.parent, {}): self.invoice_items.setdefault(d.parent, {}).setdefault(d.item_code, - sum(i.get('base_net_amount', 0) for i in items + sum((i.get('taxable_value', 0) or i.get('base_net_amount', 0)) for i in items if i.item_code == d.item_code and i.parent == d.parent)) item_tax_rate = {} @@ -403,7 +404,7 @@ class Gstr1Report(object): "width": 100 } ] - elif self.filters.get("type_of_business") == "CDNR": + elif self.filters.get("type_of_business") == "CDNR-REG": self.invoice_columns = [ { "fieldname": "customer_gstin", @@ -437,6 +438,17 @@ class Gstr1Report(object): "options": "Sales Invoice", "width":120 }, + { + "fieldname": "reverse_charge", + "label": "Reverse Charge", + "fieldtype": "Data" + }, + { + "fieldname": "export_type", + "label": "Export Type", + "fieldtype": "Data", + "hidden": 1 + }, { "fieldname": "reason_for_issuing_document", "label": "Reason For Issuing document", @@ -449,6 +461,11 @@ class Gstr1Report(object): "fieldtype": "Data", "width": 120 }, + { + "fieldname": "gst_category", + "label": "GST Category", + "fieldtype": "Data" + }, { "fieldname": "invoice_value", "label": "Invoice Value", @@ -458,10 +475,10 @@ class Gstr1Report(object): ] self.other_columns = [ { - "fieldname": "cess_amount", - "label": "Cess Amount", - "fieldtype": "Currency", - "width": 100 + "fieldname": "cess_amount", + "label": "Cess Amount", + "fieldtype": "Currency", + "width": 100 }, { "fieldname": "pre_gst", @@ -589,6 +606,12 @@ def get_json(filters, report_name, data): out = get_export_json(res) gst_json["exp"] = out + elif filters["type_of_business"] == 'CDNR-REG': + for item in report_data[:-1]: + res.setdefault(item["customer_gstin"], {}).setdefault(item["invoice_number"],[]).append(item) + + out = get_cdnr_reg_json(res, gstin) + gst_json["cdnr"] = out return { 'report_name': report_name, @@ -628,7 +651,6 @@ def get_b2b_json(res, gstin): return out def get_b2cs_json(data, gstin): - company_state_number = gstin[0:2] out = [] @@ -713,6 +735,54 @@ def get_export_json(res): return out +def get_cdnr_reg_json(res, gstin): + out = [] + + for gst_in in res: + cdnr_item, inv = {"ctin": gst_in, "nt": []}, [] + if not gst_in: continue + + for number, invoice in iteritems(res[gst_in]): + if not invoice[0]["place_of_supply"]: + frappe.throw(_("""{0} not entered in Invoice {1}. + Please update and try again""").format(frappe.bold("Place Of Supply"), + frappe.bold(invoice[0]['invoice_number']))) + + inv_item = { + "nt_num": invoice[0]["invoice_number"], + "nt_dt": getdate(invoice[0]["posting_date"]).strftime('%d-%m-%Y'), + "val": abs(flt(invoice[0]["invoice_value"])), + "ntty": invoice[0]["document_type"], + "pos": "%02d" % int(invoice[0]["place_of_supply"].split('-')[0]), + "rchrg": invoice[0]["reverse_charge"], + "inv_type": get_invoice_type_for_cdnr(invoice[0]) + } + + inv_item["itms"] = [] + for item in invoice: + inv_item["itms"].append(get_rate_and_tax_details(item, gstin)) + + inv.append(inv_item) + + if not inv: continue + cdnr_item["nt"] = inv + out.append(cdnr_item) + + return out + +def get_invoice_type_for_cdnr(row): + if row.get('gst_category') == 'SEZ': + if row.get('export_type') == 'WPAY': + invoice_type = 'SEWP' + else: + invoice_type = 'SEWOP' + elif row.get('gst_category') == 'Deemed Export': + row.invoice_type = 'DE' + elif row.get('gst_category') == 'Registered Regular': + invoice_type = 'R' + + return invoice_type + def get_basic_invoice_detail(row): return { "inum": row["invoice_number"],