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
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 92fc610..1db6ced 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 @@
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 @@
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 815e2eb..914aacc 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -273,7 +273,8 @@
'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 c80eef2..76bf9b9 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -542,4 +542,5 @@
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 0000000..fa7c444
--- /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 70960d7..0b75213 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -113,10 +113,10 @@
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 @@
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 @@
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 @@
],
'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 b878a1e..f887841 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -3,6 +3,7 @@
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 @@
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)