feat(regional): Auto state wise taxation for GST India (#19877)

* feat(regional): Auto state wise taxation for GST India

* fix: Update gst category on addition of GSTIN

* fix: Codacy and travis fixes

* fix: Travis

* fix(test): Update GST category only if GSTIN field available

* fix: Test Cases

* fix: Do not skip accounts if place of supply is not present

* fix: Auto GST taxation for SEZ Party types

* fix: Automatic taxation for multi state

* fix: Codacy and travis fixes

* fix: Auto GST template selection in Sales Order

* fix: Move inter state check and source state to tax category

* fix: Remove unique check from tax template

* fix: Remove unique check from tax template

* fix: Address fetching logic in Sales

* fix: fecth tax template on company address change

* fix: fetch company gstin on address change

* fix: company_gstin set value fix

* fix: Mutiple fixes and code refactor

* fix: Add missing semicolon

* fix: Company address fetching in sales invoice

* fix: Remove print statement

* fix: Import functools

* fix: Naming fixes and code cleanup

* fix: Update patches

* fix: Remove changes in patches.txt

* fix: Iteritems compatibility for python 3
This commit is contained in:
Deepesh Garg
2019-12-10 15:54:29 +05:30
committed by Nabin Hait
parent d06b685fdf
commit 72649c207f
23 changed files with 602 additions and 671 deletions

View File

@@ -7,6 +7,8 @@ from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_
from erpnext.controllers.accounts_controller import get_taxes_and_charges
from erpnext.hr.utils import get_salary_assignment
from erpnext.hr.doctype.salary_structure.salary_structure import make_salary_slip
from erpnext.regional.india import number_state_mapping
from six import string_types
def validate_gstin_for_india(doc, method):
if hasattr(doc, 'gst_state') and doc.gst_state:
@@ -46,6 +48,14 @@ def validate_gstin_for_india(doc, method):
frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
.format(doc.gst_state_number))
def update_gst_category(doc, method):
for link in doc.links:
if link.link_doctype in ['Customer', 'Supplier']:
if doc.get('gstin'):
frappe.db.sql("""
UPDATE `tab{0}` SET gst_category = %s WHERE name = %s AND gst_category = 'Unregistered'
""".format(link.link_doctype), ("Registered Regular", link.link_name)) #nosec
def set_gst_state_and_state_number(doc):
if not doc.gst_state:
if not doc.state:
@@ -122,44 +132,106 @@ def test_method():
'''test function'''
return 'overridden'
def get_place_of_supply(out, doctype):
def get_place_of_supply(party_details, 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 doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
address_name = party_details.shipping_address_name or party_details.customer_address
elif doctype in ("Purchase Invoice", "Purchase Order", "Purchase Receipt"):
address_name = party_details.shipping_address or party_details.supplier_address
if address_name:
address = frappe.db.get_value("Address", address_name, ["gst_state", "gst_state_number"], as_dict=1)
if address and address.gst_state and address.gst_state_number:
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)
@frappe.whitelist()
def get_regional_address_details(party_details, doctype, company, return_taxes=None):
if not out.place_of_supply: return
if isinstance(party_details, string_types):
party_details = json.loads(party_details)
party_details = frappe._dict(party_details)
if doctype in ("Sales Invoice", "Delivery Note"):
party_details.place_of_supply = get_place_of_supply(party_details, doctype)
if doctype in ("Sales Invoice", "Delivery Note", "Sales Order"):
master_doctype = "Sales Taxes and Charges Template"
if not out.company_gstin:
return
elif doctype == "Purchase Invoice":
master_doctype = "Purchase Taxes and Charges Template"
if not out.supplier_gstin:
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') and return_taxes:
return party_details
if not party_details.company_gstin:
return
if ((doctype in ("Sales Invoice", "Delivery Note") and out.company_gstin
and out.company_gstin[:2] != out.place_of_supply[:2]) or (doctype == "Purchase Invoice"
and out.supplier_gstin 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})
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') and return_taxes:
return party_details
if not party_details.supplier_gstin:
return
if not party_details.place_of_supply: return
if ((doctype in ("Sales Invoice", "Delivery Note", "Sales Order") and party_details.company_gstin
and party_details.company_gstin[:2] != party_details.place_of_supply[:2]) or (doctype in ("Purchase Invoice",
"Purchase Order", "Purchase Receipt") and party_details.supplier_gstin and party_details.supplier_gstin[:2] != party_details.place_of_supply[:2])):
default_tax = get_tax_template(master_doctype, company, 1, party_details.company_gstin[:2])
else:
default_tax = frappe.db.get_value(master_doctype, {"company": company, "disabled":0, "is_default": 1})
default_tax = get_tax_template(master_doctype, company, 0, party_details.company_gstin[:2])
if not default_tax:
return
out["taxes_and_charges"] = default_tax
out.taxes = get_taxes_and_charges(master_doctype, default_tax)
party_details["taxes_and_charges"] = default_tax
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
if return_taxes:
return party_details
def get_tax_template_based_on_category(master_doctype, company, party_details):
if not party_details.get('tax_category'):
return
default_tax = frappe.db.get_value(master_doctype, {'company': company, 'tax_category': party_details.get('tax_category')},
'name')
if default_tax:
party_details["taxes_and_charges"] = default_tax
party_details.taxes = get_taxes_and_charges(master_doctype, default_tax)
def get_tax_template(master_doctype, company, is_inter_state, state_code):
tax_categories = frappe.get_all('Tax Category', fields = ['name', 'is_inter_state', 'gst_state'],
filters = {'is_inter_state': is_inter_state})
default_tax = ''
for tax_category in tax_categories:
if tax_category.gst_state == number_state_mapping[state_code] or \
(not default_tax and not tax_category.gst_state):
default_tax = frappe.db.get_value(master_doctype,
{'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 = frappe.get_cached_value('Company', doc.company, "basic_component")
@@ -555,7 +627,7 @@ def get_gst_accounts(company, account_wise=False):
filters={"parent": "GST Settings", "company": company},
fields=["cgst_account", "sgst_account", "igst_account", "cess_account"])
if not gst_settings_accounts:
if not gst_settings_accounts and not frappe.flags.in_test:
frappe.throw(_("Please set GST Accounts in GST Settings"))
for d in gst_settings_accounts: