From 4fa600a8dd95e45dc781f3b723ac93fd654396e2 Mon Sep 17 00:00:00 2001 From: Shreya Shah Date: Tue, 5 Jun 2018 11:27:53 +0530 Subject: [PATCH] Apply GST based on Origin and Place of supply GST Code (#14288) * Add new gst field in Taxes and Charges template - is_inter_state * Add a patch * Add a regional function to fetch taxes on the basis of GSTin * Add regional function to hooks.py * Fetch taxes for Purchase Invoice on the basis of Supplier GSTIN * Fixes in the setup.py for India region * Set is_inter_state field For the existing Taxes and Charges templates, if an account_head with igst account (which is set in GST Settings) is found, set the checkbox and also check if it doesn't have a cgst account. * Fix as per review comment --- erpnext/accounts/party.py | 18 +++++- erpnext/hooks.py | 3 +- erpnext/patches.txt | 3 +- .../v11_0/inter_state_field_for_gst.py | 58 +++++++++++++++++++ erpnext/regional/india/setup.py | 23 +++++--- erpnext/regional/india/utils.py | 52 +++++++++++++---- 6 files changed, 133 insertions(+), 24 deletions(-) create mode 100644 erpnext/patches/v11_0/inter_state_field_for_gst.py diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py index 92fc61093b8..1db6ced2c5e 100644 --- a/erpnext/accounts/party.py +++ b/erpnext/accounts/party.py @@ -3,7 +3,7 @@ from __future__ import unicode_literals -import frappe +import frappe, erpnext from frappe import _, msgprint, scrub from frappe.defaults import get_user_permissions from frappe.model.utils import get_fetch_values @@ -43,12 +43,12 @@ def _get_party_details(party=None, account=None, party_type="Customer", company= party = frappe.get_doc(party_type, party) currency = party.default_currency if party.default_currency else get_company_currency(company) + out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_group) + out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) set_address_details(out, party, party_type, doctype, company) set_contact_details(out, party, party_type) set_other_values(out, party, party_type) set_price_list(out, party, party_type, price_list) - out["taxes_and_charges"] = set_taxes(party.name, party_type, posting_date, company, out.customer_group, out.supplier_group) - out["payment_terms_template"] = get_pyt_term_template(party.name, party_type, company) if not out.get("currency"): out["currency"] = currency @@ -83,6 +83,18 @@ def set_address_details(out, party, party_type, doctype=None, company=None): out.update(get_company_address(company)) if out.company_address: out.update(get_fetch_values(doctype, 'company_address', out.company_address)) + get_regional_address_details(out, doctype, company) + + elif doctype and doctype == "Purchase Invoice": + out.update(get_company_address(company)) + if out.company_address: + out["shipping_address"] = out["company_address"] + out.update(get_fetch_values(doctype, 'shipping_address', out.shipping_address)) + get_regional_address_details(out, doctype, company) + +@erpnext.allow_regional +def get_regional_address_details(out, doctype, company): + pass def set_contact_details(out, party, party_type): out.contact_person = get_default_contact(party_type, party.name) diff --git a/erpnext/hooks.py b/erpnext/hooks.py index 815e2ebc2ab..914aacc832d 100644 --- a/erpnext/hooks.py +++ b/erpnext/hooks.py @@ -273,7 +273,8 @@ regional_overrides = { 'India': { 'erpnext.tests.test_regional.test_method': 'erpnext.regional.india.utils.test_method', 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_header': 'erpnext.regional.india.utils.get_itemised_tax_breakup_header', - 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data' + 'erpnext.controllers.taxes_and_totals.get_itemised_tax_breakup_data': 'erpnext.regional.india.utils.get_itemised_tax_breakup_data', + 'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details' }, 'United Arab Emirates': { 'erpnext.controllers.taxes_and_totals.update_itemised_tax_data': 'erpnext.regional.united_arab_emirates.utils.update_itemised_tax_data' diff --git a/erpnext/patches.txt b/erpnext/patches.txt index c80eef22bb3..76bf9b9bd13 100644 --- a/erpnext/patches.txt +++ b/erpnext/patches.txt @@ -542,4 +542,5 @@ erpnext.patches.v11_0.refactor_erpnext_shopify erpnext.patches.v11_0.move_item_defaults_to_child_table_for_multicompany erpnext.patches.v11_0.rename_overproduction_percent_field erpnext.patches.v10_0.update_status_in_purchase_receipt -erpnext.patches.v11_0.rename_members_with_naming_series #04-06-2018 +erpnext.patches.v11_0.inter_state_field_for_gst +erpnext.patches.v11_0.rename_members_with_naming_series #04-06-2018 \ No newline at end of file diff --git a/erpnext/patches/v11_0/inter_state_field_for_gst.py b/erpnext/patches/v11_0/inter_state_field_for_gst.py new file mode 100644 index 00000000000..fa7c444103e --- /dev/null +++ b/erpnext/patches/v11_0/inter_state_field_for_gst.py @@ -0,0 +1,58 @@ +import frappe +from erpnext.regional.india.setup import make_custom_fields + +def execute(): + company = frappe.get_all('Company', filters = {'country': 'India'}) + if not company: + return + + make_custom_fields() + + frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges") + frappe.reload_doc("accounts", "doctype", "purchase_taxes_and_charges") + frappe.reload_doc("accounts", "doctype", "sales_taxes_and_charges_template") + frappe.reload_doc("accounts", "doctype", "purchase_taxes_and_charges_template") + + # set is_inter_state in Taxes And Charges Templates + if frappe.db.has_column("Sales Taxes And Charges Template", "is_inter_state") and\ + frappe.db.has_column("Purchase Taxes And Charges Template", "is_inter_state"): + + igst_accounts = set(frappe.db.sql_list('''SELECT igst_account from `tabGST Account` WHERE parent = "GST Settings"''')) + cgst_accounts = set(frappe.db.sql_list('''SELECT cgst_account FROM `tabGST Account` WHERE parenttype = "GST Settings"''')) + + when_then_sales = get_formatted_data("Sales Taxes and Charges", igst_accounts, cgst_accounts) + when_then_purchase = get_formatted_data("Purchase Taxes and Charges", igst_accounts, cgst_accounts) + + if when_then_sales: + frappe.db.sql('''update `tabSales Taxes and Charges Template` + set is_inter_state = Case {when_then} Else 0 End + '''.format(when_then=" ".join(when_then_sales))) + + if when_then_purchase: + frappe.db.sql('''update `tabPurchase Taxes and Charges Template` + set is_inter_state = Case {when_then} Else 0 End + '''.format(when_then=" ".join(when_then_purchase))) + +def get_formatted_data(doctype, igst_accounts, cgst_accounts): + # fetch all the rows data from child table + all_details = frappe.db.sql(''' + select parent, account_head from `tab{doctype}` + where parenttype="{doctype} Template"'''.format(doctype=doctype), as_dict=True) + + # group the data in the form "parent: [list of accounts]"" + group_detail = {} + for i in all_details: + if not i['parent'] in group_detail: group_detail[i['parent']] = [] + for j in all_details: + if i['parent']==j['parent']: + group_detail[i['parent']].append(j['account_head']) + + # form when_then condition based on - if list of accounts for a document + # matches any account in igst_accounts list and not matches any in cgst_accounts list + when_then = [] + for i in group_detail: + temp = set(group_detail[i]) + if not temp.isdisjoint(igst_accounts) and temp.isdisjoint(cgst_accounts): + when_then.append('''When name='{name}' Then 1'''.format(name=i)) + + return when_then diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py index 70960d714bd..0b7521398a7 100644 --- a/erpnext/regional/india/setup.py +++ b/erpnext/regional/india/setup.py @@ -113,10 +113,10 @@ def make_custom_fields(): purchase_invoice_gst_fields = [ dict(fieldname='supplier_gstin', label='Supplier GSTIN', fieldtype='Data', insert_after='supplier_address', - options='supplier_address.gstin', print_hide=1), + fetch_from='supplier_address.gstin', print_hide=1), dict(fieldname='company_gstin', label='Company GSTIN', - fieldtype='Data', insert_after='shipping_address', - options='shipping_address.gstin', print_hide=1), + fieldtype='Data', insert_after='shipping_address_display', + fetch_from='shipping_address.gstin', print_hide=1), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='shipping_address', print_hide=1, read_only=0), @@ -136,16 +136,16 @@ def make_custom_fields(): sales_invoice_gst_fields = [ dict(fieldname='billing_address_gstin', label='Billing Address GSTIN', fieldtype='Data', insert_after='customer_address', - options='customer_address.gstin', print_hide=1), + fetch_from='customer_address.gstin', print_hide=1), dict(fieldname='customer_gstin', label='Customer GSTIN', - fieldtype='Data', insert_after='shipping_address', - options='shipping_address_name.gstin', print_hide=1), + fieldtype='Data', insert_after='shipping_address_name', + fetch_from='shipping_address_name.gstin', print_hide=1), dict(fieldname='place_of_supply', label='Place of Supply', fieldtype='Data', insert_after='customer_gstin', print_hide=1, read_only=0), dict(fieldname='company_gstin', label='Company GSTIN', fieldtype='Data', insert_after='company_address', - options='company_address.gstin', print_hide=1), + fetch_from='company_address.gstin', print_hide=1), dict(fieldname='port_code', label='Port Code', fieldtype='Data', insert_after='reason_for_issuing_document', print_hide=1, depends_on="eval:doc.invoice_type=='Export' "), @@ -157,6 +157,11 @@ def make_custom_fields(): depends_on="eval:doc.invoice_type=='Export' ") ] + inter_state_gst_field = [ + dict(fieldname='is_inter_state', label='Is Inter State', + fieldtype='Check', insert_after='disabled', print_hide=1) + ] + custom_fields = { 'Address': [ dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data', @@ -168,7 +173,9 @@ def make_custom_fields(): ], 'Purchase Invoice': invoice_gst_fields + purchase_invoice_gst_fields, 'Sales Invoice': invoice_gst_fields + sales_invoice_gst_fields, - "Delivery Note": sales_invoice_gst_fields, + 'Delivery Note': sales_invoice_gst_fields, + 'Sales Taxes and Charges Template': inter_state_gst_field, + 'Purchase Taxes and Charges Template': inter_state_gst_field, 'Item': [ dict(fieldname='gst_hsn_code', label='HSN/SAC', fieldtype='Link', options='GST HSN Code', insert_after='item_group'), diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py index b878a1ea5d0..f887841cd0e 100644 --- a/erpnext/regional/india/utils.py +++ b/erpnext/regional/india/utils.py @@ -3,6 +3,7 @@ from frappe import _ from frappe.utils import cstr from erpnext.regional.india import states, state_numbers from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount +from erpnext.controllers.accounts_controller import get_taxes_and_charges def validate_gstin_for_india(doc, method): if not hasattr(doc, 'gstin'): @@ -61,19 +62,48 @@ def get_itemised_tax_breakup_data(doc): return hsn_tax, hsn_taxable_amount -def set_place_of_supply(doc, method): - if not frappe.get_meta('Address').has_field('gst_state'): return - - if doc.doctype in ("Sales Invoice", "Delivery Note"): - address_name = doc.shipping_address_name or doc.customer_address - elif doc.doctype == "Purchase Invoice": - address_name = doc.shipping_address or doc.supplier_address - - if address_name: - address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1) - doc.place_of_supply = cstr(address.gst_state_number) + "-" + cstr(address.gst_state) +def set_place_of_supply(doc, method=None): + doc.place_of_supply = get_place_of_supply(doc, doc.doctype) # don't remove this function it is used in tests def test_method(): '''test function''' return 'overridden' + +def get_place_of_supply(out, doctype): + if not frappe.get_meta('Address').has_field('gst_state'): return + + if doctype in ("Sales Invoice", "Delivery Note"): + address_name = out.shipping_address_name or out.customer_address + elif doctype == "Purchase Invoice": + address_name = out.shipping_address or out.supplier_address + + if address_name: + address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1) + return cstr(address.gst_state_number) + "-" + cstr(address.gst_state) + +def get_regional_address_details(out, doctype, company): + + out.place_of_supply = get_place_of_supply(out, doctype) + + if not out.place_of_supply: return + + if doctype in ("Sales Invoice", "Delivery Note"): + master_doctype = "Sales Taxes and Charges Template" + if not (out.company_gstin or out.place_of_supply): + return + else: + master_doctype = "Purchase Taxes and Charges Template" + if not (out.supplier_gstin or out.place_of_supply): + return + + if doctype in ("Sales Invoice", "Delivery Note") and out.company_gstin[:2] != out.place_of_supply[:2]\ + or (doctype == "Purchase Invoice" and out.supplier_gstin[:2] != out.place_of_supply[:2]): + default_tax = frappe.db.get_value(master_doctype, {"company": company, "is_inter_state":1, "disabled":0}) + else: + default_tax = frappe.db.get_value(master_doctype, {"company": company, "disabled":0, "is_default": 1}) + + if not default_tax: + return + out["taxes_and_charges"] = default_tax + out.taxes = get_taxes_and_charges(master_doctype, default_tax)