mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-01 19:29:10 +00:00
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:
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user