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)