Merge branch 'develop' into partially-submit-drop-ship-items-issue
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index cd71273..cb90f80 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -6,14 +6,18 @@
 from frappe.utils import cstr, flt, fmt_money, formatdate, getdate, nowdate, cint, get_link_to_form
 from frappe import msgprint, _, scrub
 from erpnext.controllers.accounts_controller import AccountsController
-from erpnext.accounts.utils import get_balance_on, get_account_currency
+from erpnext.accounts.utils import get_balance_on, get_stock_accounts, get_stock_and_account_balance, \
+	get_account_currency, check_if_stock_and_account_balance_synced
 from erpnext.accounts.party import get_party_account
 from erpnext.hr.doctype.expense_claim.expense_claim import update_reimbursed_amount
-from erpnext.accounts.doctype.invoice_discounting.invoice_discounting import get_party_account_based_on_invoice_discounting
+from erpnext.accounts.doctype.invoice_discounting.invoice_discounting \
+	import get_party_account_based_on_invoice_discounting
 from erpnext.accounts.deferred_revenue import get_deferred_booking_accounts
 
 from six import string_types, iteritems
 
+class StockAccountInvalidTransaction(frappe.ValidationError): pass
+
 class JournalEntry(AccountsController):
 	def __init__(self, *args, **kwargs):
 		super(JournalEntry, self).__init__(*args, **kwargs)
@@ -46,6 +50,7 @@
 		self.validate_empty_accounts_table()
 		self.set_account_and_party_balance()
 		self.validate_inter_company_accounts()
+		self.validate_stock_accounts()
 		if not self.title:
 			self.title = self.get_title()
 
@@ -57,6 +62,8 @@
 		self.update_expense_claim()
 		self.update_inter_company_jv()
 		self.update_invoice_discounting()
+		check_if_stock_and_account_balance_synced(self.posting_date,
+			self.company, self.doctype, self.name)
 
 	def on_cancel(self):
 		from erpnext.accounts.utils import unlink_ref_doc_from_payment_entries
@@ -95,6 +102,16 @@
 			if account_currency == previous_account_currency:
 				if self.total_credit != doc.total_debit or self.total_debit != doc.total_credit:
 					frappe.throw(_("Total Credit/ Debit Amount should be same as linked Journal Entry"))
+	
+	def validate_stock_accounts(self):
+		stock_accounts = get_stock_accounts(self.company, self.doctype, self.name)
+		for account in stock_accounts:
+			account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
+				self.posting_date, self.company)
+
+			if account_bal == stock_bal:
+				frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
+					.format(account), StockAccountInvalidTransaction)
 
 	def update_inter_company_jv(self):
 		if self.voucher_type == "Inter Company Journal Entry" and self.inter_company_journal_entry_reference:
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 1d2eacd..b56f8e5 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -6,7 +6,7 @@
 from frappe.utils import flt, nowdate
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
 from erpnext.exceptions import InvalidAccountCurrency
-from erpnext.accounts.general_ledger import StockAccountInvalidTransaction
+from erpnext.accounts.doctype.journal_entry.journal_entry import StockAccountInvalidTransaction
 
 class TestJournalEntry(unittest.TestCase):
 	def test_journal_entry_with_against_jv(self):
@@ -84,25 +84,31 @@
 		company = "_Test Company with perpetual inventory"
 		stock_account = get_inventory_account(company)
 
+		from erpnext.accounts.utils import get_stock_and_account_balance
+		account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
+		diff = flt(account_bal) - flt(stock_bal)
+
+		if not diff:
+			diff = 100
+
 		jv = frappe.new_doc("Journal Entry")
 		jv.company = company
 		jv.posting_date = nowdate()
 		jv.append("accounts", {
 			"account": stock_account,
 			"cost_center": "Main - TCP1",
-			"debit_in_account_currency": 100
+			"debit_in_account_currency": 0 if diff > 0 else abs(diff),
+			"credit_in_account_currency": diff if diff > 0 else 0
 		})
 		
 		jv.append("accounts", {
 			"account": "Stock Adjustment - TCP1",
-			"credit_in_account_currency": 100,
 			"cost_center": "Main - TCP1",
+			"debit_in_account_currency": diff if diff > 0 else 0,
+			"credit_in_account_currency": 0 if diff > 0 else abs(diff)
 		})
 		jv.insert()
 
-		from erpnext.accounts.utils import get_stock_and_account_balance
-		account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(stock_account, nowdate(), company)
-
 		if account_bal == stock_bal:
 			self.assertRaises(StockAccountInvalidTransaction, jv.submit)
 			frappe.db.rollback()
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index 55a5b0e..0565264 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -345,9 +345,13 @@
 	if ((pricing_rule.margin_type in ['Amount', 'Percentage'] and pricing_rule.currency == args.currency)
 			or (pricing_rule.margin_type == 'Percentage')):
 		item_details.margin_type = pricing_rule.margin_type
-		item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
 		item_details.has_margin = True
 
+		if pricing_rule.apply_multiple_pricing_rules and item_details.margin_rate_or_amount is not None:
+			item_details.margin_rate_or_amount += pricing_rule.margin_rate_or_amount
+		else:
+			item_details.margin_rate_or_amount = pricing_rule.margin_rate_or_amount
+
 	if pricing_rule.rate_or_discount == 'Rate':
 		pricing_rule_rate = 0.0
 		if pricing_rule.currency == args.currency:
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index 2c7cd14..fb1fbe4 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -164,7 +164,15 @@
 			frappe.throw(_("Invalid {0}").format(args.get(field)))
 
 		parent_groups = frappe.db.sql_list("""select name from `tab%s`
-			where lft<=%s and rgt>=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+			where lft>=%s and rgt<=%s""" % (parenttype, '%s', '%s'), (lft, rgt))
+
+		if parenttype in ["Customer Group", "Item Group", "Territory"]:
+			parent_field = "parent_{0}".format(frappe.scrub(parenttype))
+			root_name = frappe.db.get_list(parenttype,
+				{"is_group": 1, parent_field: ("is", "not set")}, "name", as_list=1)
+
+			if root_name and root_name[0][0]:
+				parent_groups.append(root_name[0][0])
 
 		if parent_groups:
 			if allow_blank: parent_groups.append('')
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india.js b/erpnext/accounts/doctype/sales_invoice/regional/india.js
index 6336db1..f54bce8 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india.js
@@ -1,6 +1,8 @@
 {% include "erpnext/regional/india/taxes.js" %}
+{% include "erpnext/regional/india/e_invoice/einvoice.js" %}
 
 erpnext.setup_auto_gst_taxation('Sales Invoice');
+erpnext.setup_einvoice_actions('Sales Invoice')
 
 frappe.ui.form.on("Sales Invoice", {
 	setup: function(frm) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 50734c8..40009ac 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -232,9 +232,9 @@
 			frappe.throw(_("At least one mode of payment is required for POS invoice."))
 
 	def before_cancel(self):
+		super(SalesInvoice, self).before_cancel()
 		self.update_time_sheet(None)
 
-
 	def on_cancel(self):
 		super(SalesInvoice, self).on_cancel()
 
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index ceb7907..3c681ee 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1825,93 +1825,7 @@
 	# 	check_gl_entries(self, target_doc.name, pi_gl_entries, add_days(nowdate(), -1))
 
 	def test_eway_bill_json(self):
-		if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
-			address = frappe.get_doc({
-				"address_line1": "_Test Address Line 1",
-				"address_title": "_Test Address for Eway bill",
-				"address_type": "Billing",
-				"city": "_Test City",
-				"state": "Test State",
-				"country": "India",
-				"doctype": "Address",
-				"is_primary_address": 1,
-				"phone": "+91 0000000000",
-				"gstin": "27AAECE4835E1ZR",
-				"gst_state": "Maharashtra",
-				"gst_state_number": "27",
-				"pincode": "401108"
-			}).insert()
-
-			address.append("links", {
-				"link_doctype": "Company",
-				"link_name": "_Test Company"
-			})
-
-			address.save()
-
-		if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
-			address = frappe.get_doc({
-				"address_line1": "_Test Address Line 1",
-				"address_title": "_Test Customer-Address for Eway bill",
-				"address_type": "Shipping",
-				"city": "_Test City",
-				"state": "Test State",
-				"country": "India",
-				"doctype": "Address",
-				"is_primary_address": 1,
-				"phone": "+91 0000000000",
-				"gst_state": "Maharashtra",
-				"gst_state_number": "27",
-				"pincode": "410038"
-			}).insert()
-
-			address.append("links", {
-				"link_doctype": "Customer",
-				"link_name": "_Test Customer"
-			})
-
-			address.save()
-
-		gst_settings = frappe.get_doc("GST Settings")
-
-		gst_account = frappe.get_all(
-			"GST Account",
-			fields=["cgst_account", "sgst_account", "igst_account"],
-			filters = {"company": "_Test Company"})
-
-		if not gst_account:
-			gst_settings.append("gst_accounts", {
-				"company": "_Test Company",
-				"cgst_account": "CGST - _TC",
-				"sgst_account": "SGST - _TC",
-				"igst_account": "IGST - _TC",
-			})
-
-		gst_settings.save()
-
-		si = create_sales_invoice(do_not_save =1, rate = '60000')
-
-		si.distance = 2000
-		si.company_address = "_Test Address for Eway bill-Billing"
-		si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
-		si.vehicle_no = "KA12KA1234"
-		si.gst_category = "Registered Regular"
-
-		si.append("taxes", {
-			"charge_type": "On Net Total",
-			"account_head": "CGST - _TC",
-			"cost_center": "Main - _TC",
-			"description": "CGST @ 9.0",
-			"rate": 9
-		})
-
-		si.append("taxes", {
-			"charge_type": "On Net Total",
-			"account_head": "SGST - _TC",
-			"cost_center": "Main - _TC",
-			"description": "SGST @ 9.0",
-			"rate": 9
-		})
+		si = make_sales_invoice_for_ewaybill()
 
 		si.submit()
 
@@ -1927,6 +1841,187 @@
 		self.assertEqual(data['billLists'][0]['sgstValue'], 5400)
 		self.assertEqual(data['billLists'][0]['vehicleNo'], 'KA12KA1234')
 		self.assertEqual(data['billLists'][0]['itemList'][0]['taxableAmount'], 60000)
+	
+	def test_einvoice_submission_without_irn(self):
+		# init
+		frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 1)
+		country = frappe.flags.country
+		frappe.flags.country = 'India'
+
+		si = make_sales_invoice_for_ewaybill()
+		self.assertRaises(frappe.ValidationError, si.submit)
+
+		si.irn = 'test_irn'
+		si.submit()
+
+		# reset
+		frappe.db.set_value('E Invoice Settings', 'E Invoice Settings', 'enable', 0)
+		frappe.flags.country = country
+	
+	def test_einvoice_json(self):
+		from erpnext.regional.india.e_invoice.utils import make_einvoice
+
+		customer_gstin = '27AACCM7806M1Z3'
+		customer_gstin_dtls = {
+			'LegalName': '_Test Customer', 'TradeName': '_Test Customer', 'AddrLoc': '_Test City',
+			'StateCode': '27', 'AddrPncd': '410038', 'AddrBno': '_Test Bldg',
+			'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
+		}
+		company_gstin = '27AAECE4835E1ZR'
+		company_gstin_dtls = {
+			'LegalName': '_Test Company', 'TradeName': '_Test Company', 'AddrLoc': '_Test City',
+			'StateCode': '27', 'AddrPncd': '401108', 'AddrBno': '_Test Bldg',
+			'AddrBnm': '100', 'AddrFlno': '200', 'AddrSt': '_Test Street'
+		}
+		# set cache gstin details to avoid fetching details which will require connection to GSP servers
+		frappe.local.gstin_cache = {}
+		frappe.local.gstin_cache[customer_gstin] = customer_gstin_dtls
+		frappe.local.gstin_cache[company_gstin] = company_gstin_dtls
+
+		si = make_sales_invoice_for_ewaybill()
+		si.naming_series = 'INV-2020-.#####'
+		si.items = []
+		si.append("items", {
+			"item_code": "_Test Item",
+			"uom": "Nos",
+			"warehouse": "_Test Warehouse - _TC",
+			"qty": 2,
+			"rate": 100,
+			"income_account": "Sales - _TC",
+			"expense_account": "Cost of Goods Sold - _TC",
+			"cost_center": "_Test Cost Center - _TC",
+		})
+		si.append("items", {
+			"item_code": "_Test Item 2",
+			"uom": "Nos",
+			"warehouse": "_Test Warehouse - _TC",
+			"qty": 4,
+			"rate": 150,
+			"income_account": "Sales - _TC",
+			"expense_account": "Cost of Goods Sold - _TC",
+			"cost_center": "_Test Cost Center - _TC",
+		})
+		si.save()
+
+		einvoice = make_einvoice(si)
+
+		total_item_ass_value = sum([d['AssAmt'] for d in einvoice['ItemList']])
+		total_item_cgst_value = sum([d['CgstAmt'] for d in einvoice['ItemList']])
+		total_item_sgst_value = sum([d['SgstAmt'] for d in einvoice['ItemList']])
+		total_item_igst_value = sum([d['IgstAmt'] for d in einvoice['ItemList']])
+		total_item_value = sum([d['TotItemVal'] for d in einvoice['ItemList']])
+
+		self.assertEqual(einvoice['Version'], '1.1')
+		self.assertEqual(einvoice['ValDtls']['AssVal'], total_item_ass_value)
+		self.assertEqual(einvoice['ValDtls']['CgstVal'], total_item_cgst_value)
+		self.assertEqual(einvoice['ValDtls']['SgstVal'], total_item_sgst_value)
+		self.assertEqual(einvoice['ValDtls']['IgstVal'], total_item_igst_value)
+		self.assertEqual(einvoice['ValDtls']['TotInvVal'], total_item_value)
+		self.assertTrue(einvoice['EwbDtls'])
+
+def make_sales_invoice_for_ewaybill():
+	if not frappe.db.exists('Address', '_Test Address for Eway bill-Billing'):
+		address = frappe.get_doc({
+			"address_line1": "_Test Address Line 1",
+			"address_title": "_Test Address for Eway bill",
+			"address_type": "Billing",
+			"city": "_Test City",
+			"state": "Test State",
+			"country": "India",
+			"doctype": "Address",
+			"is_primary_address": 1,
+			"phone": "+910000000000",
+			"gstin": "27AAECE4835E1ZR",
+			"gst_state": "Maharashtra",
+			"gst_state_number": "27",
+			"pincode": "401108"
+		}).insert()
+
+		address.append("links", {
+			"link_doctype": "Company",
+			"link_name": "_Test Company"
+		})
+
+		address.save()
+
+	if not frappe.db.exists('Address', '_Test Customer-Address for Eway bill-Shipping'):
+		address = frappe.get_doc({
+			"address_line1": "_Test Address Line 1",
+			"address_title": "_Test Customer-Address for Eway bill",
+			"address_type": "Shipping",
+			"city": "_Test City",
+			"state": "Test State",
+			"country": "India",
+			"doctype": "Address",
+			"is_primary_address": 1,
+			"phone": "+910000000000",
+			"gstin": "27AACCM7806M1Z3",
+			"gst_state": "Maharashtra",
+			"gst_state_number": "27",
+			"pincode": "410038"
+		}).insert()
+
+		address.append("links", {
+			"link_doctype": "Customer",
+			"link_name": "_Test Customer"
+		})
+
+		address.save()
+	
+	if not frappe.db.exists('Supplier', '_Test Transporter'):
+		frappe.get_doc({
+			"doctype": "Supplier",
+			"supplier_name": "_Test Transporter",
+			"country": "India",
+			"supplier_group": "_Test Supplier Group",
+			"supplier_type": "Company",
+			"is_transporter": 1
+		}).insert()
+
+	gst_settings = frappe.get_doc("GST Settings")
+
+	gst_account = frappe.get_all(
+		"GST Account",
+		fields=["cgst_account", "sgst_account", "igst_account"],
+		filters = {"company": "_Test Company"})
+
+	if not gst_account:
+		gst_settings.append("gst_accounts", {
+			"company": "_Test Company",
+			"cgst_account": "CGST - _TC",
+			"sgst_account": "SGST - _TC",
+			"igst_account": "IGST - _TC",
+		})
+
+	gst_settings.save()
+
+	si = create_sales_invoice(do_not_save =1, rate = '60000')
+
+	si.distance = 2000
+	si.company_address = "_Test Address for Eway bill-Billing"
+	si.customer_address = "_Test Customer-Address for Eway bill-Shipping"
+	si.vehicle_no = "KA12KA1234"
+	si.gst_category = "Registered Regular"
+	si.mode_of_transport = 'Road'
+	si.transporter = '_Test Transporter'
+
+	si.append("taxes", {
+		"charge_type": "On Net Total",
+		"account_head": "CGST - _TC",
+		"cost_center": "Main - _TC",
+		"description": "CGST @ 9.0",
+		"rate": 9
+	})
+
+	si.append("taxes", {
+		"charge_type": "On Net Total",
+		"account_head": "SGST - _TC",
+		"cost_center": "Main - _TC",
+		"description": "SGST @ 9.0",
+		"rate": 9
+	})
+
+	return si
 
 def check_gl_entries(doc, voucher_no, expected_gle, posting_date):
 	gl_entries = frappe.db.sql("""select account, debit, credit, posting_date
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index c7f0c87..287c79f 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -5,15 +5,11 @@
 import frappe, erpnext
 from frappe.utils import flt, cstr, cint, comma_and, today, getdate, formatdate, now
 from frappe import _
-from erpnext.accounts.utils import get_stock_and_account_balance
 from frappe.model.meta import get_field_precision
 from erpnext.accounts.doctype.budget.budget import validate_expense_against_budget
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import get_accounting_dimensions
 
-
 class ClosedAccountingPeriod(frappe.ValidationError): pass
-class StockAccountInvalidTransaction(frappe.ValidationError): pass
-class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
 
 def make_gl_entries(gl_map, cancel=False, adv_adj=False, merge_entries=True, update_outstanding='Yes', from_repost=False):
 	if gl_map:
@@ -131,10 +127,6 @@
 	for entry in gl_map:
 		make_entry(entry, adv_adj, update_outstanding, from_repost)
 
-	if not from_repost:
-		validate_account_for_perpetual_inventory(gl_map)
-
-
 def make_entry(args, adv_adj, update_outstanding, from_repost=False):
 	gle = frappe.new_doc("GL Entry")
 	gle.update(args)
@@ -144,63 +136,9 @@
 	gle.run_method("on_update_with_args", adv_adj, update_outstanding, from_repost)
 	gle.submit()
 
-	# check against budget
 	if not from_repost:
 		validate_expense_against_budget(args)
 
-def validate_account_for_perpetual_inventory(gl_map):
-	if cint(erpnext.is_perpetual_inventory_enabled(gl_map[0].company)):
-		account_list = [gl_entries.account for gl_entries in gl_map]
-
-		aii_accounts = [d.name for d in frappe.get_all("Account",
-			filters={'account_type': 'Stock', 'is_group': 0, 'company': gl_map[0].company})]
-
-		for account in account_list:
-			if account not in aii_accounts:
-				continue
-
-			# Always use current date to get stock and account balance as there can future entries for
-			# other items
-			account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
-				gl_map[0].posting_date, gl_map[0].company)
-
-			if gl_map[0].voucher_type=="Journal Entry":
-				# In case of Journal Entry, there are no corresponding SL entries,
-				# hence deducting currency amount
-				account_bal -= flt(gl_map[0].debit) - flt(gl_map[0].credit)
-				if account_bal == stock_bal:
-					frappe.throw(_("Account: {0} can only be updated via Stock Transactions")
-						.format(account), StockAccountInvalidTransaction)
-
-			elif abs(account_bal - stock_bal) > 0.1:
-				precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
-					currency=frappe.get_cached_value('Company',  gl_map[0].company,  "default_currency"))
-
-				diff = flt(stock_bal - account_bal, precision)
-				error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses on {3}.").format(
-					stock_bal, account_bal, frappe.bold(account), gl_map[0].posting_date)
-				error_resolution = _("Please create adjustment Journal Entry for amount {0} ").format(frappe.bold(diff))
-				stock_adjustment_account = frappe.db.get_value("Company",gl_map[0].company,"stock_adjustment_account")
-
-				db_or_cr_warehouse_account =('credit_in_account_currency' if diff < 0 else 'debit_in_account_currency')
-				db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if diff < 0 else 'credit_in_account_currency')
-
-				journal_entry_args = {
-					'accounts':[
-						{'account': account, db_or_cr_warehouse_account : abs(diff)},
-						{'account': stock_adjustment_account, db_or_cr_stock_adjustment_account : abs(diff)}
-					]
-				}
-
-				frappe.msgprint(msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
-					raise_exception=StockValueAndAccountBalanceOutOfSync,
-					title=_('Values Out Of Sync'),
-					primary_action={
-						'label': _('Make Journal Entry'),
-						'client_action': 'erpnext.route_to_adjustment_jv',
-						'args': journal_entry_args
-					})
-
 def validate_cwip_accounts(gl_map):
 	cwip_enabled = any([cint(ac.enable_cwip_accounting) for ac in frappe.db.get_all("Asset Category","enable_cwip_accounting")])
 
diff --git a/erpnext/accounts/print_format/gst_e_invoice/__init__.py b/erpnext/accounts/print_format/gst_e_invoice/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/__init__.py
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
new file mode 100644
index 0000000..9827e00
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.html
@@ -0,0 +1,162 @@
+{%- from "templates/print_formats/standard_macros.html" import add_header, render_field, print_value -%}
+{%- set einvoice = json.loads(doc.signed_einvoice) -%}
+
+<div class="page-break">
+	<div {% if print_settings.repeat_header_footer %} id="header-html" class="hidden-pdf" {% endif %}>
+		{% if letter_head and not no_letterhead %}
+			<div class="letter-head">{{ letter_head }}</div>
+		{% endif %}
+		<div class="print-heading">
+			<h2>E Invoice<br><small>{{ doc.name }}</small></h2>
+		</div>
+	</div>
+	{% if print_settings.repeat_header_footer %}
+	<div id="footer-html" class="visible-pdf">
+		{% if not no_letterhead and footer %}
+		<div class="letter-head-footer">
+			{{ footer }}
+		</div>
+		{% endif %}
+		<p class="text-center small page-number visible-pdf">
+			{{ _("Page {0} of {1}").format('<span class="page"></span>', '<span class="topage"></span>') }}
+		</p>
+	</div>
+	{% endif %}
+	<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+		<h5 class="font-bold" style="margin-left: 15px; margin-top: 0px;">1. Transaction Details</h5>
+		<div class="col-xs-8 column-break">
+			<div class="row data-field">
+				<div class="col-xs-4"><label>IRN</label></div>
+				<div class="col-xs-8 value">{{ einvoice.Irn }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Ack. No</label></div>
+				<div class="col-xs-8 value">{{ einvoice.AckNo }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Ack. Date</label></div>
+				<div class="col-xs-8 value">{{ frappe.utils.format_datetime(einvoice.AckDt, "dd/MM/yyyy hh:mm:ss") }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Category</label></div>
+				<div class="col-xs-8 value">{{ einvoice.TranDtls.SupTyp }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Document Type</label></div>
+				<div class="col-xs-8 value">{{ einvoice.DocDtls.Typ }}</div>
+			</div>
+			<div class="row data-field">
+				<div class="col-xs-4"><label>Document No</label></div>
+				<div class="col-xs-8 value">{{ einvoice.DocDtls.No }}</div>
+			</div>
+		</div>
+		<div class="col-xs-4 column-break">
+			<img src="{{ doc.qrcode_image }}" width="175px" style="float: right;">
+		</div>
+	</div>
+	<div class="row section-break" style="border-bottom: 1px solid #d1d8dd; padding-bottom: 10px;">
+		<h5 class="font-bold" style="margin-left: 15px; margin-bottom: 0px;">2. Party Details</h5>
+		{%- set seller = einvoice.SellerDtls -%}
+		<div class="col-xs-6 column-break">
+			<h5 style="margin-bottom: 5px;">Seller</h5>
+			<p>{{ seller.Gstin }}</p>
+			<p>{{ seller.LglNm }}</p>
+			<p>{{ seller.Addr1 }}</p>
+			{%- if seller.Addr2 -%} <p>{{ seller.Addr2 }}</p> {% endif %}
+			<p>{{ seller.Loc }}</p>
+			<p>{{ frappe.db.get_value("Address", doc.company_address, "gst_state") }} - {{ seller.Pin }}</p>
+
+			{%- if einvoice.ShipDtls -%}
+				{%- set shipping = einvoice.ShipDtls -%}
+				<h5 style="margin-bottom: 5px;">Shipping</h5>
+				<p>{{ shipping.Gstin }}</p>
+				<p>{{ shipping.LglNm }}</p>
+				<p>{{ shipping.Addr1 }}</p>
+				{%- if shipping.Addr2 -%} <p>{{ shipping.Addr2 }}</p> {% endif %}
+				<p>{{ shipping.Loc }}</p>
+				<p>{{ frappe.db.get_value("Address", doc.shipping_address_name, "gst_state") }} - {{ shipping.Pin }}</p>
+			{% endif %}
+		</div>
+		{%- set buyer = einvoice.BuyerDtls -%}
+		<div class="col-xs-6 column-break">
+			<h5 style="margin-bottom: 5px;">Buyer</h5>
+			<p>{{ buyer.Gstin }}</p>
+			<p>{{ buyer.LglNm }}</p>
+			<p>{{ buyer.Addr1 }}</p>
+			{%- if buyer.Addr2 -%} <p>{{ buyer.Addr2 }}</p> {% endif %}
+			<p>{{ buyer.Loc }}</p>
+			<p>{{ frappe.db.get_value("Address", doc.customer_address, "gst_state") }} - {{ buyer.Pin }}</p>
+		</div>
+	</div>
+	<div style="overflow-x: auto;">
+		<h5 class="font-bold" style="margin-bottom: 0px;">3. Item Details</h5>
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th class="text-left" style="width: 3%;">Sr. No.</th>
+					<th class="text-left">Item</th>
+					<th class="text-left" style="width: 10%;">HSN Code</th>
+					<th class="text-left" style="width: 5%;">Qty</th>
+					<th class="text-left" style="width: 5%;">UOM</th>
+					<th class="text-left">Rate</th>
+					<th class="text-left" style="width: 5%;">Discount</th>
+					<th class="text-left">Taxable Amount</th>
+					<th class="text-left" style="width: 7%;">Tax Rate</th>
+					<th class="text-left" style="width: 5%;">Other Charges</th>
+					<th class="text-left">Total</th>
+				</tr>
+			</thead>
+			<tbody>
+				{% for item in einvoice.ItemList %}
+					<tr>
+						<td class="text-left" style="width: 3%;">{{ item.SlNo }}</td>
+						<td class="text-left">{{ item.PrdDesc }}</td>
+						<td class="text-left" style="width: 10%;">{{ item.HsnCd }}</td>
+						<td class="text-right" style="width: 5%;">{{ item.Qty }}</td>
+						<td class="text-left" style="width: 5%;">{{ item.Unit }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.UnitPrice, None, "INR") }}</td>
+						<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(item.Discount, None, "INR") }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.AssAmt, None, "INR") }}</td>
+						<td class="text-right" style="width: 7%;">{{ item.GstRt + item.CesRt }} %</td>
+						<td class="text-right" style="width: 5%;">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+						<td class="text-right">{{ frappe.utils.fmt_money(item.TotItemVal, None, "INR") }}</td>
+					</tr>
+				{% endfor %}
+			</tbody>
+		</table>
+	</div>
+	<div style="overflow-x: auto;">
+		<h5 class="font-bold" style="margin-bottom: 0px;">4. Value Details</h5>
+		<table class="table table-bordered">
+			<thead>
+				<tr>
+					<th class="text-left">Taxable Amount</th>
+					<th class="text-left">CGST</th>
+					<th class="text-left"">SGST</th>
+					<th class="text-left">IGST</th>
+					<th class="text-left">CESS</th>
+					<th class="text-left" style="width: 10%;">State CESS</th>
+					<th class="text-left">Discount</th>
+					<th class="text-left" style="width: 10%;">Other Charges</th>
+					<th class="text-left" style="width: 10%;">Round Off</th>
+					<th class="text-left">Total Value</th>
+				</tr>
+			</thead>
+			<tbody>
+				{%- set value_details = einvoice.ValDtls -%}
+				<tr>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.AssVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.CgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.SgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.IgstVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.CesVal, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.Discount, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(0, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.RndOffAmt, None, "INR") }}</td>
+					<td class="text-right">{{ frappe.utils.fmt_money(value_details.TotInvVal, None, "INR") }}</td>
+				</tr>
+			</tbody>
+		</table>
+	</div>
+</div>
\ No newline at end of file
diff --git a/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
new file mode 100644
index 0000000..1001199
--- /dev/null
+++ b/erpnext/accounts/print_format/gst_e_invoice/gst_e_invoice.json
@@ -0,0 +1,24 @@
+{
+ "align_labels_right": 1,
+ "creation": "2020-10-10 18:01:21.032914",
+ "custom_format": 0,
+ "default_print_language": "en-US",
+ "disabled": 1,
+ "doc_type": "Sales Invoice",
+ "docstatus": 0,
+ "doctype": "Print Format",
+ "font": "Default",
+ "html": "",
+ "idx": 0,
+ "line_breaks": 1,
+ "modified": "2020-10-23 19:54:40.634936",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "GST E-Invoice",
+ "owner": "Administrator",
+ "print_format_builder": 0,
+ "print_format_type": "Jinja",
+ "raw_printing": 0,
+ "show_section_headings": 1,
+ "standard": "Yes"
+}
\ No newline at end of file
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 540ac84..67c7fd2 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -12,11 +12,12 @@
 from six import iteritems
 # imported to enable erpnext.accounts.utils.get_account_currency
 from erpnext.accounts.doctype.account.account import get_account_currency
+from frappe.model.meta import get_field_precision
 
 from erpnext.stock.utils import get_stock_value_on
 from erpnext.stock import get_warehouse_account_map
 
-
+class StockValueAndAccountBalanceOutOfSync(frappe.ValidationError): pass
 class FiscalYearError(frappe.ValidationError): pass
 
 @frappe.whitelist()
@@ -585,24 +586,6 @@
 				(dr_or_cr, dr_or_cr, '%s', '%s', '%s', dr_or_cr),
 				(d.diff, d.voucher_type, d.voucher_no))
 
-def get_stock_and_account_balance(account=None, posting_date=None, company=None):
-	if not posting_date: posting_date = nowdate()
-
-	warehouse_account = get_warehouse_account_map(company)
-
-	account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
-
-	related_warehouses = [wh for wh, wh_details in warehouse_account.items()
-		if wh_details.account == account and not wh_details.is_group]
-
-	total_stock_value = 0.0
-	for warehouse in related_warehouses:
-		value = get_stock_value_on(warehouse, posting_date)
-		total_stock_value += value
-
-	precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
-	return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
-
 def get_currency_precision():
 	precision = cint(frappe.db.get_default("currency_precision"))
 	if not precision:
@@ -903,12 +886,6 @@
 
 	return accounts
 
-def get_stock_accounts(company):
-	return frappe.get_all("Account", filters = {
-		"account_type": "Stock",
-		"company": company
-	})
-
 def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
 		warehouse_account=None, company=None):
 	def _delete_gl_entries(voucher_type, voucher_no):
@@ -983,4 +960,90 @@
 		if not account_existed:
 			matched = False
 			break
-	return matched
\ No newline at end of file
+	return matched
+
+def check_if_stock_and_account_balance_synced(posting_date, company, voucher_type=None, voucher_no=None):
+	if not cint(erpnext.is_perpetual_inventory_enabled(company)):
+		return
+
+	accounts = get_stock_accounts(company, voucher_type, voucher_no)
+	stock_adjustment_account = frappe.db.get_value("Company", company, "stock_adjustment_account")
+
+	for account in accounts:
+		account_bal, stock_bal, warehouse_list = get_stock_and_account_balance(account,
+			posting_date, company)
+
+		if abs(account_bal - stock_bal) > 0.1:
+			precision = get_field_precision(frappe.get_meta("GL Entry").get_field("debit"),
+				currency=frappe.get_cached_value('Company',  company,  "default_currency"))
+
+			diff = flt(stock_bal - account_bal, precision)
+
+			error_reason = _("Stock Value ({0}) and Account Balance ({1}) are out of sync for account {2} and it's linked warehouses as on {3}.").format(
+				stock_bal, account_bal, frappe.bold(account), posting_date)
+			error_resolution = _("Please create an adjustment Journal Entry for amount {0} on {1}")\
+				.format(frappe.bold(diff), frappe.bold(posting_date))			
+
+			frappe.msgprint(
+				msg="""{0}<br></br>{1}<br></br>""".format(error_reason, error_resolution),
+				raise_exception=StockValueAndAccountBalanceOutOfSync,
+				title=_('Values Out Of Sync'),
+				primary_action={
+					'label': _('Make Journal Entry'),
+					'client_action': 'erpnext.route_to_adjustment_jv',
+					'args': get_journal_entry(account, stock_adjustment_account, diff)
+				})
+
+def get_stock_accounts(company, voucher_type=None, voucher_no=None):
+	stock_accounts = [d.name for d in frappe.db.get_all("Account", {
+		"account_type": "Stock",
+		"company": company,
+		"is_group": 0
+	})]
+	if voucher_type and voucher_no:
+		if voucher_type == "Journal Entry":
+			stock_accounts = [d.account for d in frappe.db.get_all("Journal Entry Account", {
+				"parent": voucher_no,
+				"account": ["in", stock_accounts]
+			}, "account")]
+
+		else:
+			stock_accounts = [d.account for d in frappe.db.get_all("GL Entry", {
+				"voucher_type": voucher_type,
+				"voucher_no": voucher_no,
+				"account": ["in", stock_accounts]
+			}, "account")]
+
+	return stock_accounts
+
+def get_stock_and_account_balance(account=None, posting_date=None, company=None):
+	if not posting_date: posting_date = nowdate()
+
+	warehouse_account = get_warehouse_account_map(company)
+
+	account_balance = get_balance_on(account, posting_date, in_account_currency=False, ignore_account_permission=True)
+
+	related_warehouses = [wh for wh, wh_details in warehouse_account.items()
+		if wh_details.account == account and not wh_details.is_group]
+
+	total_stock_value = 0.0
+	for warehouse in related_warehouses:
+		value = get_stock_value_on(warehouse, posting_date)
+		total_stock_value += value
+
+	precision = frappe.get_precision("Journal Entry Account", "debit_in_account_currency")
+	return flt(account_balance, precision), flt(total_stock_value, precision), related_warehouses
+
+def get_journal_entry(account, stock_adjustment_account, amount):
+	db_or_cr_warehouse_account =('credit_in_account_currency' if amount < 0 else 'debit_in_account_currency')
+	db_or_cr_stock_adjustment_account = ('debit_in_account_currency' if amount < 0 else 'credit_in_account_currency')
+
+	return {
+		'accounts':[{
+			'account': account,
+			db_or_cr_warehouse_account: abs(amount)
+		}, {
+			'account': stock_adjustment_account,
+			db_or_cr_stock_adjustment_account : abs(amount)
+		}]
+	}
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 74ca62f..1430827 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -21,9 +21,6 @@
 		self.reschedule_depreciations(self.new_asset_value)
 
 	def on_cancel(self):
-		if self.journal_entry:
-			frappe.throw(_("Cancel the journal entry {0} first").format(self.journal_entry))
-
 		self.reschedule_depreciations(self.current_asset_value)
 
 	def validate_date(self):
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 32c5d3a..0f1aa23 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -110,8 +110,14 @@
 			self.set_inter_company_account()
 
 		validate_regional(self)
+		
+		validate_einvoice_fields(self)
+
 		if self.doctype != 'Material Request':
 			apply_pricing_rule_on_transaction(self)
+	
+	def before_cancel(self):
+		validate_einvoice_fields(self)
 
 	def validate_deferred_start_and_end_date(self):
 		for d in self.items:
@@ -1518,3 +1524,7 @@
 @erpnext.allow_regional
 def validate_regional(doc):
 	pass
+
+@erpnext.allow_regional
+def validate_einvoice_fields(doc):
+	pass
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index dc61870..6edc020 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -241,7 +241,7 @@
 					if rate > 0:
 						d.rate = rate
 
-				d.amount = flt(d.consumed_qty) * flt(d.rate)
+				d.amount = flt(flt(d.consumed_qty) * flt(d.rate), d.precision("amount"))
 				supplied_items_cost += flt(d.amount)
 		
 		return supplied_items_cost
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 8f65c31..7979226 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -204,21 +204,25 @@
 	return items
 
 def get_returned_qty_map_for_row(row_name, doctype):
+	if doctype == "POS Invoice": return {}
+
 	child_doctype = doctype + " Item"
-	reference_field = frappe.scrub(child_doctype) if doctype == "Purchase Receipt" else "dn_detail"
+	reference_field = "dn_detail" if doctype == "Delivery Note" else frappe.scrub(child_doctype)
 
 	fields = [
 		"sum(abs(`tab{0}`.qty)) as qty".format(child_doctype),
 		"sum(abs(`tab{0}`.stock_qty)) as stock_qty".format(child_doctype)
 	]
 
-	if doctype == "Purchase Receipt":
+	if doctype in ("Purchase Receipt", "Purchase Invoice"):
 		fields += [
 			"sum(abs(`tab{0}`.rejected_qty)) as rejected_qty".format(child_doctype),
-			"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype),
-			"sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)
+			"sum(abs(`tab{0}`.received_qty)) as received_qty".format(child_doctype)
 		]
 
+		if doctype == "Purchase Receipt":
+			fields += ["sum(abs(`tab{0}`.received_stock_qty)) as received_stock_qty".format(child_doctype)]
+
 	data = frappe.db.get_list(doctype,
 		fields = fields,
 		filters = [
@@ -231,6 +235,7 @@
 
 def make_return_doc(doctype, source_name, target_doc=None):
 	from frappe.model.mapper import get_mapped_doc
+	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 	company = frappe.db.get_value("Delivery Note", source_name, "company")
 	default_warehouse_for_sales_return = frappe.db.get_value("Company", company, "default_warehouse_for_sales_return")
 
@@ -290,6 +295,12 @@
 	def update_item(source_doc, target_doc, source_parent):
 		target_doc.qty = -1 * source_doc.qty
 
+		if source_doc.serial_no:
+			returned_serial_nos = get_returned_serial_nos(source_doc, source_parent)
+			serial_nos = list(set(get_serial_nos(source_doc.serial_no)) - set(returned_serial_nos))
+			if serial_nos:
+				target_doc.serial_no = '\n'.join(serial_nos)
+
 		if doctype == "Purchase Receipt":
 			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
 			target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
@@ -305,10 +316,12 @@
 			target_doc.purchase_receipt_item = source_doc.name
 
 		elif doctype == "Purchase Invoice":
-			target_doc.received_qty = -1 * source_doc.received_qty
-			target_doc.rejected_qty = -1 * source_doc.rejected_qty
-			target_doc.qty = -1* source_doc.qty
-			target_doc.stock_qty = -1 * source_doc.stock_qty
+			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+			target_doc.received_qty = -1 * flt(source_doc.received_qty - (returned_qty_map.get('received_qty') or 0))
+			target_doc.rejected_qty = -1 * flt(source_doc.rejected_qty - (returned_qty_map.get('rejected_qty') or 0))
+			target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+
+			target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
 			target_doc.purchase_order = source_doc.purchase_order
 			target_doc.purchase_receipt = source_doc.purchase_receipt
 			target_doc.rejected_warehouse = source_doc.rejected_warehouse
@@ -330,6 +343,10 @@
 			if default_warehouse_for_sales_return:
 				target_doc.warehouse = default_warehouse_for_sales_return
 		elif doctype == "Sales Invoice" or doctype == "POS Invoice":
+			returned_qty_map = get_returned_qty_map_for_row(source_doc.name, doctype)
+			target_doc.qty = -1 * flt(source_doc.qty - (returned_qty_map.get('qty') or 0))
+			target_doc.stock_qty = -1 * flt(source_doc.stock_qty - (returned_qty_map.get('stock_qty') or 0))
+
 			target_doc.sales_order = source_doc.sales_order
 			target_doc.delivery_note = source_doc.delivery_note
 			target_doc.so_detail = source_doc.so_detail
@@ -406,4 +423,22 @@
 	if reference_voucher_detail_no:
 		filters["voucher_detail_no"] = reference_voucher_detail_no
 
-	return filters
\ No newline at end of file
+	return filters
+
+def get_returned_serial_nos(child_doc, parent_doc):
+	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+	return_ref_field = frappe.scrub(child_doc.doctype)
+	if child_doc.doctype == "Delivery Note Item":
+		return_ref_field = "dn_detail"
+
+	serial_nos = []
+
+	fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
+
+	filters = [[parent_doc.doctype, "return_against", "=", parent_doc.name], [parent_doc.doctype, "is_return", "=", 1],
+		[child_doc.doctype, return_ref_field, "=", child_doc.name], [parent_doc.doctype, "docstatus", "=", 1]]
+
+	for row in frappe.get_all(parent_doc.doctype, fields = fields, filters=filters):
+		serial_nos.extend(get_serial_nos(row.serial_no))
+
+	return serial_nos
\ No newline at end of file
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 51c063c..4399976 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -6,7 +6,7 @@
 from frappe.utils import cint, flt, cstr, get_link_to_form, today, getdate
 from frappe import _
 import frappe.defaults
-from erpnext.accounts.utils import get_fiscal_year
+from erpnext.accounts.utils import get_fiscal_year, check_if_stock_and_account_balance_synced
 from erpnext.accounts.general_ledger import make_gl_entries, make_reverse_gl_entries, process_gl_map
 from erpnext.controllers.accounts_controller import AccountsController
 from erpnext.stock.stock_ledger import get_valuation_rate
@@ -402,6 +402,14 @@
 
 		if check_if_future_sle_exists(args):
 			create_repost_item_valuation_entry(args)
+		elif not is_reposting_pending():
+			check_if_stock_and_account_balance_synced(self.posting_date,
+				self.company, self.doctype, self.name)
+
+def is_reposting_pending():
+	return frappe.db.exists("Repost Item Valuation",
+		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
+
 
 def check_if_future_sle_exists(args):
 	sl_entries = frappe.db.get_all("Stock Ledger Entry",
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 1e3bb6a..a2d9d86 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -397,7 +397,8 @@
 		'erpnext.accounts.party.get_regional_address_details': 'erpnext.regional.india.utils.get_regional_address_details',
 		'erpnext.hr.utils.calculate_annual_eligible_hra_exemption': 'erpnext.regional.india.utils.calculate_annual_eligible_hra_exemption',
 		'erpnext.hr.utils.calculate_hra_exemption_for_period': 'erpnext.regional.india.utils.calculate_hra_exemption_for_period',
-		'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries'
+		'erpnext.accounts.doctype.purchase_invoice.purchase_invoice.make_regional_gl_entries': 'erpnext.regional.india.utils.make_regional_gl_entries',
+		'erpnext.controllers.accounts_controller.validate_einvoice_fields': 'erpnext.regional.india.e_invoice.utils.validate_einvoice_fields'
 	},
 	'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/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index dfc600c..0fde3a1 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -135,7 +135,7 @@
 				try:
 					frappe.get_doc({
 						"doctype": "File",
-						"file_name": self.image,
+						"file_url": self.image,
 						"attached_to_doctype": "User",
 						"attached_to_name": self.user_id
 					}).insert()
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index d15d81e..ec28eb7 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -17,6 +17,7 @@
 
 class OperationMismatchError(frappe.ValidationError): pass
 class OperationSequenceError(frappe.ValidationError): pass
+class JobCardCancelError(frappe.ValidationError): pass
 
 class JobCard(Document):
 	def validate(self):
@@ -217,33 +218,49 @@
 		field = "operation_id"
 		data = self.get_current_operation_data()
 		if data and len(data) > 0:
-			for_quantity = data[0].completed_qty
-			time_in_mins = data[0].time_in_mins
+			for_quantity = flt(data[0].completed_qty)
+			time_in_mins = flt(data[0].time_in_mins)
 
-		if self.get(field):
-			time_data = frappe.db.sql("""
+		wo = frappe.get_doc('Work Order', self.work_order)
+		if self.operation_id:
+			self.validate_produced_quantity(for_quantity, wo)
+			self.update_work_order_data(for_quantity, time_in_mins, wo)
+
+	def validate_produced_quantity(self, for_quantity, wo):
+		if self.docstatus < 2: return
+
+		if wo.produced_qty > for_quantity:
+			first_part_msg = (_("The {0} {1} is used to calculate the valuation cost for the finished good {2}.")
+				.format(frappe.bold(_("Job Card")), frappe.bold(self.name), frappe.bold(self.production_item)))
+
+			second_part_msg = (_("Kindly cancel the Manufacturing Entries first against the work order {0}.")
+				.format(frappe.bold(get_link_to_form("Work Order", self.work_order))))
+
+			frappe.throw(_("{0} {1}").format(first_part_msg, second_part_msg),
+				JobCardCancelError, title = _("Error"))
+
+	def update_work_order_data(self, for_quantity, time_in_mins, wo):
+		time_data = frappe.db.sql("""
 				SELECT
 					min(from_time) as start_time, max(to_time) as end_time
 				FROM `tabJob Card` jc, `tabJob Card Time Log` jctl
 				WHERE
 					jctl.parent = jc.name and jc.work_order = %s
-					and jc.{0} = %s and jc.docstatus = 1
-			""".format(field), (self.work_order, self.get(field)), as_dict=1)
+					and jc.operation_id = %s and jc.docstatus = 1
+			""", (self.work_order, self.operation_id), as_dict=1)
 
-			wo = frappe.get_doc('Work Order', self.work_order)
+		for data in wo.operations:
+			if data.get("name") == self.operation_id:
+				data.completed_qty = for_quantity
+				data.actual_operation_time = time_in_mins
+				data.actual_start_time = time_data[0].start_time if time_data else None
+				data.actual_end_time = time_data[0].end_time if time_data else None
 
-			for data in wo.operations:
-				if data.get("name") == self.get(field):
-					data.completed_qty = for_quantity
-					data.actual_operation_time = time_in_mins
-					data.actual_start_time = time_data[0].start_time if time_data else None
-					data.actual_end_time = time_data[0].end_time if time_data else None
-
-			wo.flags.ignore_validate_update_after_submit = True
-			wo.update_operation_status()
-			wo.calculate_operating_cost()
-			wo.set_actual_dates()
-			wo.save()
+		wo.flags.ignore_validate_update_after_submit = True
+		wo.update_operation_status()
+		wo.calculate_operating_cost()
+		wo.set_actual_dates()
+		wo.save()
 
 	def get_current_operation_data(self):
 		return frappe.get_all('Job Card',
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index ce9699e..a77bd15 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import unittest
 import frappe
-from frappe.utils import flt, time_diff_in_hours, now, add_months, cint, today
+from frappe.utils import flt, now, add_months, cint, today, add_to_date
 from erpnext.manufacturing.doctype.work_order.work_order import (make_stock_entry,
 	ItemHasVariantError, stop_unstop, StockOverProductionError, OverProductionError, CapacityError)
 from erpnext.stock.doctype.stock_entry import test_stock_entry
@@ -14,6 +14,7 @@
 from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
 from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
+from erpnext.manufacturing.doctype.job_card.job_card import JobCardCancelError
 
 class TestWorkOrder(unittest.TestCase):
 	def setUp(self):
@@ -369,21 +370,49 @@
 		self.assertEqual(ste.total_additional_costs, 1000)
 
 	def test_job_card(self):
+		stock_entries = []
 		data = frappe.get_cached_value('BOM',
 			{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
 
-		if data:
-			frappe.db.set_value("Manufacturing Settings",
-				None, "disable_capacity_planning", 0)
+		bom, bom_item = data
 
-			bom, bom_item = data
+		bom_doc = frappe.get_doc('BOM', bom)
+		work_order = make_wo_order_test_record(item=bom_item, qty=1,
+			bom_no=bom, source_warehouse="_Test Warehouse - _TC")
 
-			bom_doc = frappe.get_doc('BOM', bom)
-			work_order = make_wo_order_test_record(item=bom_item, qty=1, bom_no=bom)
-			self.assertTrue(work_order.planned_end_date)
+		for row in work_order.required_items:
+			stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
+				target="_Test Warehouse - _TC", qty=row.required_qty, basic_rate=100)
+			stock_entries.append(stock_entry_doc)
 
-			job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
-			self.assertEqual(len(job_cards), len(bom_doc.operations))
+		ste = frappe.get_doc(make_stock_entry(work_order.name, "Material Transfer for Manufacture", 1))
+		ste.submit()
+		stock_entries.append(ste)
+
+		job_cards = frappe.get_all('Job Card', filters = {'work_order': work_order.name})
+		self.assertEqual(len(job_cards), len(bom_doc.operations))
+
+		for i, job_card in enumerate(job_cards):
+			doc = frappe.get_doc("Job Card", job_card)
+			doc.append("time_logs", {
+				"from_time": now(),
+				"hours": i,
+				"to_time": add_to_date(now(), i),
+				"completed_qty": doc.for_quantity
+			})
+			doc.submit()
+
+		ste1 = frappe.get_doc(make_stock_entry(work_order.name, "Manufacture", 1))
+		ste1.submit()
+		stock_entries.append(ste1)
+
+		for job_card in job_cards:
+			doc = frappe.get_doc("Job Card", job_card)
+			self.assertRaises(JobCardCancelError, doc.cancel)
+
+		stock_entries.reverse()
+		for stock_entry in stock_entries:
+			stock_entry.cancel()
 
 	def test_capcity_planning(self):
 		frappe.db.set_value("Manufacturing Settings", None, {
@@ -509,7 +538,6 @@
 		ste1.submit()
 		ste_cancel_list.append(ste1)
 
-		print(wo_order.name)
 		ste3 = frappe.get_doc(make_stock_entry(wo_order.name, "Material Consumption for Manufacture", 2))
 		self.assertEquals(ste3.fg_completed_qty, 2)
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 9e33014..d69dabf 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -732,6 +732,7 @@
 erpnext.patches.v13_0.print_uom_after_quantity_patch
 erpnext.patches.v13_0.set_payment_channel_in_payment_gateway_account
 erpnext.patches.v13_0.create_healthcare_custom_fields_in_stock_entry_detail
+erpnext.patches.v12_0.setup_einvoice_fields #2020-12-02
 erpnext.patches.v13_0.updates_for_multi_currency_payroll
 erpnext.patches.v13_0.update_reason_for_resignation_in_employee
 erpnext.patches.v13_0.update_custom_fields_for_shopify
diff --git a/erpnext/patches/v12_0/setup_einvoice_fields.py b/erpnext/patches/v12_0/setup_einvoice_fields.py
new file mode 100644
index 0000000..d078276
--- /dev/null
+++ b/erpnext/patches/v12_0/setup_einvoice_fields.py
@@ -0,0 +1,55 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from erpnext.regional.india.setup import add_permissions, add_print_formats
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	frappe.reload_doc("regional", "doctype", "e_invoice_settings")
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+				depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+			
+			dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
+		
+			dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+			dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+			dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+				depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+			dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
+	add_permissions()
+	add_print_formats()
+
+	einvoice_cond = 'in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category)'
+	t = {
+		'mode_of_transport': [{'default': None}],
+		'distance': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.transporter'}],
+		'gst_vehicle_type': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+		'lr_date': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+		'lr_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && in_list(["Air", "Ship", "Rail"], doc.mode_of_transport)'}],
+		'vehicle_no': [{'mandatory_depends_on': f'eval:{einvoice_cond} && doc.mode_of_transport == "Road"'}],
+		'ewaybill': [
+			{'read_only_depends_on': 'eval:doc.irn && doc.ewaybill'},
+			{'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)'}
+		]
+	}
+
+	for field, conditions in t.items():
+		for c in conditions:
+			[(prop, value)] = c.items()
+			frappe.db.set_value('Custom Field', { 'fieldname': field }, prop, value)
diff --git a/erpnext/regional/doctype/e_invoice_request_log/__init__.py b/erpnext/regional/doctype/e_invoice_request_log/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
new file mode 100644
index 0000000..7b7ba96
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Request Log', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
new file mode 100644
index 0000000..5c1c79d
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.json
@@ -0,0 +1,103 @@
+{
+ "actions": [],
+ "autoname": "EINV-REQ-.#####",
+ "creation": "2020-12-08 12:54:08.175992",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "user",
+  "url",
+  "headers",
+  "response",
+  "column_break_7",
+  "timestamp",
+  "reference_invoice",
+  "data"
+ ],
+ "fields": [
+  {
+   "fieldname": "user",
+   "fieldtype": "Link",
+   "label": "User",
+   "options": "User"
+  },
+  {
+   "fieldname": "reference_invoice",
+   "fieldtype": "Link",
+   "label": "Reference Invoice",
+   "options": "Sales Invoice"
+  },
+  {
+   "fieldname": "headers",
+   "fieldtype": "Code",
+   "label": "Headers",
+   "options": "JSON"
+  },
+  {
+   "fieldname": "data",
+   "fieldtype": "Code",
+   "label": "Data",
+   "options": "JSON"
+  },
+  {
+   "default": "Now",
+   "fieldname": "timestamp",
+   "fieldtype": "Datetime",
+   "label": "Timestamp"
+  },
+  {
+   "fieldname": "response",
+   "fieldtype": "Code",
+   "label": "Response",
+   "options": "JSON"
+  },
+  {
+   "fieldname": "url",
+   "fieldtype": "Data",
+   "label": "URL"
+  },
+  {
+   "fieldname": "column_break_7",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2020-12-24 21:09:38.882866",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Request Log",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
new file mode 100644
index 0000000..9150bdd
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/e_invoice_request_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class EInvoiceRequestLog(Document):
+	pass
diff --git a/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
new file mode 100644
index 0000000..c84e9a2
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_request_log/test_e_invoice_request_log.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestEInvoiceRequestLog(unittest.TestCase):
+	pass
diff --git a/erpnext/regional/doctype/e_invoice_settings/__init__.py b/erpnext/regional/doctype/e_invoice_settings/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
new file mode 100644
index 0000000..cc2d9f0
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.js
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('E Invoice Settings', {
+	refresh(frm) {
+		const docs_link = 'https://docs.erpnext.com/docs/user/manual/en/regional/india/setup-e-invoicing';
+		frm.dashboard.set_headline(
+			__("Read {0} for more information on E Invoicing features.", [`<a href='${docs_link}'>documentation</a>`])
+		);
+	}
+});
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
new file mode 100644
index 0000000..4dcb22a
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -0,0 +1,58 @@
+{
+ "actions": [],
+ "creation": "2020-09-24 16:23:16.235722",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "enable",
+  "section_break_2",
+  "credentials",
+  "auth_token",
+  "token_expiry"
+ ],
+ "fields": [
+  {
+   "default": "0",
+   "fieldname": "enable",
+   "fieldtype": "Check",
+   "label": "Enable"
+  },
+  {
+   "depends_on": "enable",
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "auth_token",
+   "fieldtype": "Data",
+   "hidden": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "token_expiry",
+   "fieldtype": "Datetime",
+   "hidden": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "credentials",
+   "fieldtype": "Table",
+   "label": "Credentials",
+   "mandatory_depends_on": "enable",
+   "options": "E Invoice User"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2020-12-22 15:34:57.280044",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice Settings",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
new file mode 100644
index 0000000..c24ad88
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.py
@@ -0,0 +1,14 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+from __future__ import unicode_literals
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+
+class EInvoiceSettings(Document):
+	def validate(self):
+		if self.enable and not self.credentials:
+			frappe.throw(_('You must add atleast one credentials to be able to use E Invoicing.'))
+
diff --git a/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
new file mode 100644
index 0000000..a11ce63
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_settings/test_e_invoice_settings.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestEInvoiceSettings(unittest.TestCase):
+	pass
diff --git a/erpnext/regional/doctype/e_invoice_user/__init__.py b/erpnext/regional/doctype/e_invoice_user/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/__init__.py
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
new file mode 100644
index 0000000..dd9d997
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
@@ -0,0 +1,48 @@
+{
+ "actions": [],
+ "creation": "2020-12-22 15:02:46.229474",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "gstin",
+  "username",
+  "password"
+ ],
+ "fields": [
+  {
+   "fieldname": "gstin",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "GSTIN",
+   "reqd": 1
+  },
+  {
+   "fieldname": "username",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Username",
+   "reqd": 1
+  },
+  {
+   "fieldname": "password",
+   "fieldtype": "Password",
+   "in_list_view": 1,
+   "label": "Password",
+   "reqd": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2020-12-22 15:10:53.466205",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E Invoice User",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
new file mode 100644
index 0000000..056c54f
--- /dev/null
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+# import frappe
+from frappe.model.document import Document
+
+class EInvoiceUser(Document):
+	pass
diff --git a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
index ce2c1d4..1ff5680 100644
--- a/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
+++ b/erpnext/regional/doctype/uae_vat_settings/uae_vat_settings.json
@@ -29,25 +29,12 @@
  ],
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-09-30 20:08:18.764798",
+ "modified": "2020-12-25 20:20:22.342426",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "UAE VAT Settings",
  "owner": "Administrator",
- "permissions": [
-  {
-   "create": 1,
-   "delete": 1,
-   "email": 1,
-   "export": 1,
-   "print": 1,
-   "read": 1,
-   "report": 1,
-   "role": "System Manager",
-   "share": 1,
-   "write": 1
-  }
- ],
+ "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
diff --git a/erpnext/regional/india/e_invoice/__init__.py b/erpnext/regional/india/e_invoice/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/__init__.py
diff --git a/erpnext/regional/india/e_invoice/einv_item_template.json b/erpnext/regional/india/e_invoice/einv_item_template.json
new file mode 100644
index 0000000..78e5651
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_item_template.json
@@ -0,0 +1,31 @@
+{{
+    "SlNo": "{item.sr_no}",
+    "PrdDesc": "{item.description}",
+    "IsServc": "{item.is_service_item}",
+    "HsnCd": "{item.gst_hsn_code}",
+    "Barcde": "{item.barcode}",
+    "Unit": "{item.uom}",
+    "Qty": "{item.qty}",
+    "FreeQty": "{item.free_qty}",
+    "UnitPrice": "{item.unit_rate}",
+    "TotAmt": "{item.gross_amount}",
+    "Discount": "{item.discount_amount}",
+    "AssAmt": "{item.taxable_value}",
+    "PrdSlNo": "{item.serial_no}",
+    "GstRt": "{item.tax_rate}",
+    "IgstAmt": "{item.igst_amount}",
+    "CgstAmt": "{item.cgst_amount}",
+    "SgstAmt": "{item.sgst_amount}",
+    "CesRt": "{item.cess_rate}",
+    "CesAmt": "{item.cess_amount}",
+    "CesNonAdvlAmt": "{item.cess_nadv_amount}",
+    "StateCesRt": "{item.state_cess_rate}",
+    "StateCesAmt": "{item.state_cess_amount}",
+    "StateCesNonAdvlAmt": "{item.state_cess_nadv_amount}",
+    "OthChrg": "{item.other_charges}",
+    "TotItemVal": "{item.total_value}",
+    "BchDtls": {{
+        "Nm": "{item.batch_no}",
+        "ExpDt": "{item.batch_expiry_date}"
+    }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_template.json b/erpnext/regional/india/e_invoice/einv_template.json
new file mode 100644
index 0000000..e5751da
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_template.json
@@ -0,0 +1,110 @@
+{{
+    "Version": "1.1",
+    "TranDtls": {{
+        "TaxSch": "{transaction_details.tax_scheme}",
+        "SupTyp": "{transaction_details.supply_type}",
+        "RegRev": "{transaction_details.reverse_charge}",
+        "EcmGstin": "{transaction_details.ecom_gstin}",
+        "IgstOnIntra": "{transaction_details.igst_on_intra}"
+    }},
+    "DocDtls": {{
+        "Typ": "{doc_details.invoice_type}",
+        "No": "{doc_details.invoice_name}",
+        "Dt": "{doc_details.invoice_date}"
+    }},
+    "SellerDtls": {{
+        "Gstin": "{seller_details.gstin}",
+        "LglNm": "{seller_details.legal_name}",
+        "TrdNm": "{seller_details.trade_name}",
+        "Loc": "{seller_details.location}",
+        "Pin": "{seller_details.pincode}",
+        "Stcd": "{seller_details.state_code}",
+        "Addr1": "{seller_details.address_line1}",
+        "Addr2": "{seller_details.address_line2}",
+        "Ph": "{seller_details.phone}",
+        "Em": "{seller_details.email}"
+    }},
+    "BuyerDtls": {{
+        "Gstin": "{buyer_details.gstin}",
+        "LglNm": "{buyer_details.legal_name}",
+        "TrdNm": "{buyer_details.trade_name}",
+        "Addr1": "{buyer_details.address_line1}",
+        "Addr2": "{buyer_details.address_line2}",
+        "Loc": "{buyer_details.location}",
+        "Pin": "{buyer_details.pincode}",
+        "Stcd": "{buyer_details.state_code}",
+        "Ph": "{buyer_details.phone}",
+        "Em": "{buyer_details.email}",
+        "Pos": "{buyer_details.place_of_supply}"
+    }},
+    "DispDtls": {{
+        "Nm": "{dispatch_details.company_name}",
+        "Addr1": "{dispatch_details.address_line1}",
+        "Addr2": "{dispatch_details.address_line2}",
+        "Loc": "{dispatch_details.location}",
+        "Pin": "{dispatch_details.pincode}",
+        "Stcd": "{dispatch_details.state_code}"
+    }},
+    "ShipDtls": {{
+        "Gstin": "{shipping_details.gstin}",
+        "LglNm": "{shipping_details.legal_name}",
+        "TrdNm": "{shipping_details.trader_name}",
+        "Addr1": "{shipping_details.address_line1}",
+        "Addr2": "{shipping_details.address_line2}",
+        "Loc": "{shipping_details.location}",
+        "Pin": "{shipping_details.pincode}",
+        "Stcd": "{shipping_details.state_code}"
+    }},
+    "ItemList": [
+        {item_list}
+    ],
+    "ValDtls": {{
+        "AssVal": "{invoice_value_details.base_net_total}",
+        "CgstVal": "{invoice_value_details.total_cgst_amt}",
+        "SgstVal": "{invoice_value_details.total_sgst_amt}",
+        "IgstVal": "{invoice_value_details.total_igst_amt}",
+        "CesVal": "{invoice_value_details.total_cess_amt}",
+        "Discount": "{invoice_value_details.invoice_discount_amt}",
+        "RndOffAmt": "{invoice_value_details.round_off}",
+        "OthChrg": "{invoice_value_details.total_other_charges}",
+        "TotInvVal": "{invoice_value_details.base_grand_total}",
+        "TotInvValFc": "{invoice_value_details.grand_total}"
+    }},
+    "PayDtls": {{
+        "Nm": "{payment_details.payee_name}",
+        "AccDet": "{payment_details.account_no}",
+        "Mode": "{payment_details.mode_of_payment}",
+        "FinInsBr": "{payment_details.ifsc_code}",
+        "PayTerm": "{payment_details.terms}",
+        "PaidAmt": "{payment_details.paid_amount}",
+        "PaymtDue": "{payment_details.outstanding_amount}"
+    }},
+    "RefDtls": {{
+        "DocPerdDtls": {{
+            "InvStDt": "{period_details.start_date}",
+            "InvEndDt": "{period_details.end_date}"
+        }},
+        "PrecDocDtls": [{{
+            "InvNo": "{prev_doc_details.invoice_name}",
+            "InvDt": "{prev_doc_details.invoice_date}"
+        }}]
+    }},
+    "ExpDtls": {{
+        "ShipBNo": "{export_details.bill_no}",
+        "ShipBDt": "{export_details.bill_date}",
+        "Port": "{export_details.port}",
+        "ForCur": "{export_details.foreign_curr_code}",
+        "CntCode": "{export_details.country_code}",
+        "ExpDuty": "{export_details.export_duty}"
+    }},
+    "EwbDtls": {{
+        "TransId": "{eway_bill_details.gstin}",
+        "TransName": "{eway_bill_details.name}",
+        "TransMode": "{eway_bill_details.mode_of_transport}",
+        "Distance": "{eway_bill_details.distance}",
+        "TransDocNo": "{eway_bill_details.document_name}",
+        "TransDocDt": "{eway_bill_details.document_date}",
+        "VehNo": "{eway_bill_details.vehicle_no}",
+        "VehType": "{eway_bill_details.vehicle_type}"
+    }}
+}}
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
new file mode 100644
index 0000000..86290cf
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -0,0 +1,956 @@
+{
+  "Version": {
+    "type": "string",
+    "minLength": 1,
+    "maxLength": 6,
+    "description": "Version of the schema"
+  },
+  "Irn": {
+    "type": "string",
+    "minLength": 64,
+    "maxLength": 64,
+    "description": "Invoice Reference Number"
+  },
+  "TranDtls": {
+    "type": "object",
+    "properties": {
+      "TaxSch": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 10,
+        "enum": ["GST"],
+        "description": "GST- Goods and Services Tax Scheme"
+      },
+      "SupTyp": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 10,
+        "enum": ["B2B", "SEZWP", "SEZWOP", "EXPWP", "EXPWOP", "DEXP"],
+        "description": "Type of Supply: B2B-Business to Business, SEZWP - SEZ with payment, SEZWOP - SEZ without payment, EXPWP - Export with Payment, EXPWOP - Export without payment,DEXP - Deemed Export"
+      },
+      "RegRev": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Y- whether the tax liability is payable under reverse charge"
+      },
+      "EcmGstin": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "pattern": "([0-9]{2}[0-9A-Z]{13})",
+        "description": "E-Commerce GSTIN",
+        "validationMsg": "E-Commerce GSTIN is invalid"
+      },
+      "IgstOnIntra": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Y- indicates the supply is intra state but chargeable to IGST"
+      }
+    },
+    "required": ["TaxSch", "SupTyp"]
+  },
+  "DocDtls": {
+    "type": "object",
+    "properties": {
+      "Typ": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 3,
+        "enum": ["INV", "CRN", "DBN"],
+        "description": "Document Type"
+      },
+      "No": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 16,
+        "pattern": "^([A-Z1-9]{1}[A-Z0-9/-]{0,15})$",
+        "description": "Document Number",
+        "validationMsg": "Document Number should not be starting with 0, / and -"
+      },
+      "Dt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Document Date"
+      }
+    },
+    "required": ["Typ", "No", "Dt"]
+  },
+  "SellerDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "pattern": "([0-9]{2}[0-9A-Z]{13})",
+        "description": "Supplier GSTIN",
+        "validationMsg": "Company GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Tradename"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 50,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Supplier State Code"
+      },
+      "Ph": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 12,
+        "description": "Phone"
+      },
+      "Em": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 100,
+        "description": "Email-Id"
+      }
+    },
+    "required": ["Gstin", "LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "BuyerDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 15,
+        "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+        "description": "Buyer GSTIN",
+        "validationMsg": "Customer GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Trade Name"
+      },
+      "Pos": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Place of Supply State code"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "Buyer State Code"
+      },
+      "Ph": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 12,
+        "description": "Phone"
+      },
+      "Em": {
+        "type": "string",
+        "minLength": 6,
+        "maxLength": 100,
+        "description": "Email-Id"
+      }
+    },
+    "required": ["Gstin", "LglNm", "Pos", "Addr1", "Loc", "Stcd"]
+  },
+  "DispDtls": {
+    "type": "object",
+    "properties": {
+      "Nm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Dispatch Address Name"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "State Code"
+      }
+    },
+    "required": ["Nm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "ShipDtls": {
+    "type": "object",
+    "properties": {
+      "Gstin": {
+        "type": "string",
+        "maxLength": 15,
+        "minLength": 3,
+        "pattern": "^(([0-9]{2}[0-9A-Z]{13})|URP)$",
+        "description": "Shipping Address GSTIN",
+        "validationMsg": "Shipping Address GSTIN is invalid"
+      },
+      "LglNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Legal Name"
+      },
+      "TrdNm": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Trade Name"
+      },
+      "Addr1": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Address Line 1"
+      },
+      "Addr2": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Address Line 2"
+      },
+      "Loc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Location"
+      },
+      "Pin": {
+        "type": "number",
+        "minimum": 100000,
+        "maximum": 999999,
+        "description": "Pincode"
+      },
+      "Stcd": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 2,
+        "description": "State Code"
+      }
+    },
+    "required": ["LglNm", "Addr1", "Loc", "Pin", "Stcd"]
+  },
+  "ItemList": {
+    "type": "Array",
+    "properties": {
+      "SlNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 6,
+        "description": "Serial No. of Item"
+      },
+      "PrdDesc": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 300,
+        "description": "Item Name"
+      },
+      "IsServc": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["Y", "N"],
+        "description": "Is Service Item"
+      },
+      "HsnCd": {
+        "type": "string",
+        "minLength": 4,
+        "maxLength": 8,
+        "description": "HSN Code"
+      },
+      "Barcde": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 30,
+        "description": "Barcode"
+      },
+      "Qty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999999999.999,
+        "description": "Quantity"
+      },
+      "FreeQty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999999999.999,
+        "description": "Free Quantity"
+      },
+      "Unit": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 8,
+        "description": "UOM"
+      },
+      "UnitPrice": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.999,
+        "description": "Rate"
+      },
+      "TotAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Gross Amount"
+      },
+      "Discount": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Discount"
+      },
+      "PreTaxVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Pre tax value"
+      },
+      "AssAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Taxable Value"
+      },
+      "GstRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "GST Rate"
+      },
+      "IgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "IGST Amount"
+      },
+      "CgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "CGST Amount"
+      },
+      "SgstAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "SGST Amount"
+      },
+      "CesRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "Cess Rate"
+      },
+      "CesAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Cess Amount (Advalorem)"
+      },
+      "CesNonAdvlAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Cess Amount (Non-Advalorem)"
+      },
+      "StateCesRt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999.999,
+        "description": "State CESS Rate"
+      },
+      "StateCesAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "State CESS Amount"
+      },
+      "StateCesNonAdvlAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "State CESS Amount (Non Advalorem)"
+      },
+      "OthChrg": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Other Charges"
+      },
+      "TotItemVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Total Item Value"
+      },
+      "OrdLineRef": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 50,
+        "description": "Order line reference"
+      },
+      "OrgCntry": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 2,
+        "description": "Origin Country"
+      },
+      "PrdSlNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 20,
+        "description": "Serial number"
+      },
+      "BchDtls": {
+        "type": "object",
+        "properties": {
+          "Nm": {
+            "type": "string",
+            "minLength": 3,
+            "maxLength": 20,
+            "description": "Batch number"
+          },
+          "ExpDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Batch Expiry Date"
+          },
+          "WrDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Warranty Date"
+          }
+        },
+        "required": ["Nm"]
+      },
+      "AttribDtls": {
+        "type": "Array",
+        "Attribute": {
+          "type": "object",
+          "properties": {
+            "Nm": {
+              "type": "string",
+              "minLength": 1,
+              "maxLength": 100,
+              "description": "Attribute name of the item"
+            },
+            "Val": {
+              "type": "string",
+              "minLength": 1,
+              "maxLength": 100,
+              "description": "Attribute value of the item"
+            }
+          }
+        }
+      }
+    },
+    "required": [
+      "SlNo",
+      "IsServc",
+      "HsnCd",
+      "UnitPrice",
+      "TotAmt",
+      "AssAmt",
+      "GstRt",
+      "TotItemVal"
+    ]
+  },
+  "ValDtls": {
+    "type": "object",
+    "properties": {
+      "AssVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total Assessable value of all items"
+      },
+      "CgstVal": {
+        "type": "number",
+        "maximum": 99999999999999.99,
+        "minimum": 0,
+        "description": "Total CGST value of all items"
+      },
+      "SgstVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total SGST value of all items"
+      },
+      "IgstVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total IGST value of all items"
+      },
+      "CesVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total CESS value of all items"
+      },
+      "StCesVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Total State CESS value of all items"
+      },
+      "Discount": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Invoice Discount"
+      },
+      "OthChrg": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Other Charges"
+      },
+      "RndOffAmt": {
+        "type": "number",
+        "minimum": -99.99,
+        "maximum": 99.99,
+        "description": "Rounded off Amount"
+      },
+      "TotInvVal": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Final Invoice Value "
+      },
+      "TotInvValFc": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Final Invoice value in Foreign Currency"
+      }
+    },
+    "required": ["AssVal", "TotInvVal"]
+  },
+  "PayDtls": {
+    "type": "object",
+    "properties": {
+      "Nm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Payee Name"
+      },
+      "AccDet": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 18,
+        "description": "Bank Account Number of Payee"
+      },
+      "Mode": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 18,
+        "description": "Mode of Payment"
+      },
+      "FinInsBr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 11,
+        "description": "Branch or IFSC code"
+      },
+      "PayTerm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Terms of Payment"
+      },
+      "PayInstr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Payment Instruction"
+      },
+      "CrTrn": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Credit Transfer"
+      },
+      "DirDr": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 100,
+        "description": "Direct Debit"
+      },
+      "CrDay": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 9999,
+        "description": "Credit Days"
+      },
+      "PaidAmt": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Advance Amount"
+      },
+      "PaymtDue": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 99999999999999.99,
+        "description": "Outstanding Amount"
+      }
+    }
+  },
+  "RefDtls": {
+    "type": "object",
+    "properties": {
+      "InvRm": {
+        "type": "string",
+        "maxLength": 100,
+        "minLength": 3,
+        "pattern": "^[0-9A-Za-z/-]{3,100}$",
+        "description": "Remarks/Note"
+      },
+      "DocPerdDtls": {
+        "type": "object",
+        "properties": {
+          "InvStDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Invoice Period Start Date"
+          },
+          "InvEndDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Invoice Period End Date"
+          }
+        },
+        "required": ["InvStDt ", "InvEndDt "]
+      },
+      "PrecDocDtls": {
+        "type": "object",
+        "properties": {
+          "InvNo": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 16,
+            "pattern": "^[1-9A-Z]{1}[0-9A-Z/-]{1,15}$",
+            "description": "Reference of Original Invoice"
+          },
+          "InvDt": {
+            "type": "string",
+            "maxLength": 10,
+            "minLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Date of Orginal Invoice"
+          },
+          "OthRefNo": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "description": "Other Reference"
+          }
+        }
+      },
+      "required": ["InvNo", "InvDt"],
+      "ContrDtls": {
+        "type": "object",
+        "properties": {
+          "RecAdvRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Receipt Advice No."
+          },
+          "RecAdvDt": {
+            "type": "string",
+            "minLength": 10,
+            "maxLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "Date of receipt advice"
+          },
+          "TendRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Lot/Batch Reference No."
+          },
+          "ContrRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Contract Reference Number"
+          },
+          "ExtRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Any other reference"
+          },
+          "ProjRefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 20,
+            "pattern": "^([0-9A-Za-z/-]){1,20}$",
+            "description": "Project Reference Number"
+          },
+          "PORefr": {
+            "type": "string",
+            "minLength": 1,
+            "maxLength": 16,
+            "pattern": "^([0-9A-Za-z/-]){1,16}$",
+            "description": "PO Reference Number"
+          },
+          "PORefDt": {
+            "type": "string",
+            "minLength": 10,
+            "maxLength": 10,
+            "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+            "description": "PO Reference date"
+          }
+        }
+      }
+    }
+  },
+  "AddlDocDtls": {
+    "type": "Array",
+    "properties": {
+      "Url": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Supporting document URL"
+      },
+      "Docs": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 1000,
+        "description": "Supporting document in Base64 Format"
+      },
+      "Info": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 1000,
+        "description": "Any additional information"
+      }
+    }
+  },
+
+  "ExpDtls": {
+    "type": "object",
+    "properties": {
+      "ShipBNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 20,
+        "description": "Shipping Bill No."
+      },
+      "ShipBDt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Shipping Bill Date"
+      },
+      "Port": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 10,
+        "pattern": "^[0-9A-Za-z]{2,10}$",
+        "description": "Port Code. Refer the master"
+      },
+      "RefClm": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "description": "Claiming Refund. Y/N"
+      },
+      "ForCur": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 16,
+        "description": "Additional Currency Code. Refer the master"
+      },
+      "CntCode": {
+        "type": "string",
+        "minLength": 2,
+        "maxLength": 2,
+        "description": "Country Code. Refer the master"
+      },
+      "ExpDuty": {
+        "type": "number",
+        "minimum": 0,
+        "maximum": 999999999999.99,
+        "description": "Export Duty"
+      }
+    }
+  },
+  "EwbDtls": {
+    "type": "object",
+    "properties": {
+      "TransId": {
+        "type": "string",
+        "minLength": 15,
+        "maxLength": 15,
+        "description": "Transporter GSTIN"
+      },
+      "TransName": {
+        "type": "string",
+        "minLength": 3,
+        "maxLength": 100,
+        "description": "Transporter Name"
+      },
+      "TransMode": {
+        "type": "string",
+        "maxLength": 1,
+        "minLength": 1,
+        "enum": ["1", "2", "3", "4"],
+        "description": "Mode of Transport"
+      },
+      "Distance": {
+        "type": "number",
+        "minimum": 1,
+        "maximum": 9999,
+        "description": "Distance"
+      },
+      "TransDocNo": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 15,
+        "pattern": "^([0-9A-Z/-]){1,15}$",
+        "description": "Tranport Document Number"
+      },
+      "TransDocDt": {
+        "type": "string",
+        "minLength": 10,
+        "maxLength": 10,
+        "pattern": "[0-3][0-9]/[0-1][0-9]/[2][0][1-2][0-9]",
+        "description": "Transport Document Date"
+      },
+      "VehNo": {
+        "type": "string",
+        "minLength": 4,
+        "maxLength": 20,
+        "description": "Vehicle Number"
+      },
+      "VehType": {
+        "type": "string",
+        "minLength": 1,
+        "maxLength": 1,
+        "enum": ["O", "R"],
+        "description": "Vehicle Type"
+      }
+    },
+    "required": ["Distance"]
+  },
+  "required": [
+    "Version",
+    "TranDtls",
+    "DocDtls",
+    "SellerDtls",
+    "BuyerDtls",
+    "ItemList",
+    "ValDtls"
+  ]
+}
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
new file mode 100644
index 0000000..9c86cc8
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -0,0 +1,305 @@
+erpnext.setup_einvoice_actions = (doctype) => {
+	frappe.ui.form.on(doctype, {
+		refresh(frm) {
+			const einvoicing_enabled = frappe.db.get_value("E Invoice Settings", "E Invoice Settings", "enable");
+			const supply_type = frm.doc.gst_category;
+			const valid_supply_type = ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export'].includes(supply_type);
+			const company_transaction = frm.doc.billing_address_gstin == frm.doc.company_gstin;
+
+			if (!einvoicing_enabled || !valid_supply_type || company_transaction) return;
+
+			const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
+
+			const add_custom_button = (label, action) => {
+				if (!frm.custom_buttons[label]) {
+					frm.add_custom_button(label, action, __('E Invoicing'));
+				}
+			};
+
+			if (!irn && !__unsaved) {
+				const action = () => {
+					frappe.call({
+						method: 'erpnext.regional.india.e_invoice.utils.get_einvoice',
+						args: { doctype, docname: name },
+						freeze: true,
+						callback: (res) => {
+							const einvoice = res.message;
+							show_einvoice_preview(frm, einvoice);
+						}
+					});
+				};
+
+				add_custom_button(__("Generate IRN"), action);
+			}
+
+			if (irn && !irn_cancelled && !ewaybill) {
+				const fields = [
+					{
+						"label": "Reason",
+						"fieldname": "reason",
+						"fieldtype": "Select",
+						"reqd": 1,
+						"default": "1-Duplicate",
+						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+					},
+					{ 
+						"label": "Remark",
+						"fieldname": "remark",
+						"fieldtype": "Data",
+						"reqd": 1
+					}
+				];
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __("Cancel IRN"),
+						fields: fields,
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.cancel_irn',
+								args: { 
+									doctype,
+									docname: name,
+									irn: irn,
+									reason: data.reason.split('-')[0],
+									remark: data.remark
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+				add_custom_button(__("Cancel IRN"), action);
+			}
+
+			if (irn && !irn_cancelled && !ewaybill) {
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __('Generate E-Way Bill'),
+						wide: 1,
+						fields: get_ewaybill_fields(frm),
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.generate_eway_bill',
+								args: {
+									doctype,
+									docname: name,
+									irn,
+									...data
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+
+				add_custom_button(__("Generate E-Way Bill"), action);
+			}
+
+			if (irn && ewaybill && !irn_cancelled && !eway_bill_cancelled) {
+				const fields = [
+					{
+						"label": "Reason",
+						"fieldname": "reason",
+						"fieldtype": "Select",
+						"reqd": 1,
+						"default": "1-Duplicate",
+						"options": ["1-Duplicate", "2-Data Entry Error", "3-Order Cancelled", "4-Other"]
+					},
+					{
+						"label": "Remark",
+						"fieldname": "remark",
+						"fieldtype": "Data",
+						"reqd": 1
+					}
+				];
+				const action = () => {
+					const d = new frappe.ui.Dialog({
+						title: __('Cancel E-Way Bill'),
+						fields: fields,
+						primary_action: function() {
+							const data = d.get_values();
+							frappe.call({
+								method: 'erpnext.regional.india.e_invoice.utils.cancel_eway_bill',
+								args: {
+									doctype,
+									docname: name,
+									eway_bill: ewaybill,
+									reason: data.reason.split('-')[0],
+									remark: data.remark
+								},
+								freeze: true,
+								callback: () => frm.reload_doc() || d.hide(),
+								error: () => d.hide()
+							});
+						},
+						primary_action_label: __('Submit')
+					});
+					d.show();
+				};
+				add_custom_button(__("Cancel E-Way Bill"), action);
+			}
+		}
+	});
+};
+
+const get_ewaybill_fields = (frm) => {
+	return [
+		{
+			'fieldname': 'transporter',
+			'label': 'Transporter',
+			'fieldtype': 'Link',
+			'options': 'Supplier',
+			'default': frm.doc.transporter
+		},
+		{
+			'fieldname': 'gst_transporter_id',
+			'label': 'GST Transporter ID',
+			'fieldtype': 'Data',
+			'fetch_from': 'transporter.gst_transporter_id',
+			'default': frm.doc.gst_transporter_id
+		},
+		{
+			'fieldname': 'driver',
+			'label': 'Driver',
+			'fieldtype': 'Link',
+			'options': 'Driver',
+			'default': frm.doc.driver
+		},
+		{
+			'fieldname': 'lr_no',
+			'label': 'Transport Receipt No',
+			'fieldtype': 'Data',
+			'default': frm.doc.lr_no
+		},
+		{
+			'fieldname': 'vehicle_no',
+			'label': 'Vehicle No',
+			'fieldtype': 'Data',
+			'depends_on': 'eval:(doc.mode_of_transport === "Road")',
+			'default': frm.doc.vehicle_no
+		},
+		{
+			'fieldname': 'distance',
+			'label': 'Distance (in km)',
+			'fieldtype': 'Float',
+			'default': frm.doc.distance
+		},
+		{
+			'fieldname': 'transporter_col_break',
+			'fieldtype': 'Column Break',
+		},
+		{
+			'fieldname': 'transporter_name',
+			'label': 'Transporter Name',
+			'fieldtype': 'Data',
+			'fetch_from': 'transporter.name',
+			'read_only': 1,
+			'default': frm.doc.transporter_name
+		},
+		{
+			'fieldname': 'mode_of_transport',
+			'label': 'Mode of Transport',
+			'fieldtype': 'Select',
+			'options': `\nRoad\nAir\nRail\nShip`,
+			'default': frm.doc.mode_of_transport
+		},
+		{
+			'fieldname': 'driver_name',
+			'label': 'Driver Name',
+			'fieldtype': 'Data',
+			'fetch_from': 'driver.full_name',
+			'read_only': 1,
+			'default': frm.doc.driver_name
+		},
+		{
+			'fieldname': 'lr_date',
+			'label': 'Transport Receipt Date',
+			'fieldtype': 'Date',
+			'default': frm.doc.lr_date
+		},
+		{
+			'fieldname': 'gst_vehicle_type',
+			'label': 'GST Vehicle Type',
+			'fieldtype': 'Select',
+			'options': `Regular\nOver Dimensional Cargo (ODC)`,
+			'depends_on': 'eval:(doc.mode_of_transport === "Road")',
+			'default': frm.doc.gst_vehicle_type
+		}
+	];
+};
+
+const request_irn_generation = (frm) => {
+	frappe.call({
+		method: 'erpnext.regional.india.e_invoice.utils.generate_irn',
+		args: { doctype: frm.doc.doctype, docname: frm.doc.name },
+		freeze: true,
+		callback: () => frm.reload_doc()
+	});
+};
+
+const get_preview_dialog = (frm, action) => {
+	const dialog = new frappe.ui.Dialog({
+		title: __("Preview"),
+		wide: 1,
+		fields: [
+			{ 
+				"label": "Preview",
+				"fieldname": "preview_html",
+				"fieldtype": "HTML"
+			}
+		],
+		primary_action: () => action(frm) || dialog.hide(),
+		primary_action_label: __('Generate IRN')
+	});
+	return dialog;
+};
+
+const show_einvoice_preview = (frm, einvoice) => {
+	const preview_dialog = get_preview_dialog(frm, request_irn_generation);
+
+	// initialize e-invoice fields
+	einvoice["Irn"] = einvoice["AckNo"] = ''; einvoice["AckDt"] = frappe.datetime.nowdate();
+	frm.doc.signed_einvoice = JSON.stringify(einvoice);
+
+	// initialize preview wrapper
+	const $preview_wrapper = preview_dialog.get_field("preview_html").$wrapper;
+	$preview_wrapper.html(
+		`<div>
+			<div class="print-preview">
+				<div class="print-format"></div>
+			</div>
+			<div class="page-break-message text-muted text-center text-medium margin-top"></div>
+		</div>`
+	);
+
+	frappe.call({
+		method: "frappe.www.printview.get_html_and_style",
+		args: {
+			doc: frm.doc,
+			print_format: "GST E-Invoice",
+			no_letterhead: 1
+		},
+		callback: function (r) {
+			if (!r.exc) {
+				$preview_wrapper.find(".print-format").html(r.message.html);
+				const style = `
+					.print-format { box-shadow: 0px 0px 5px rgba(0,0,0,0.2); padding: 0.30in; min-height: 80vh; }
+					.print-preview { min-height: 0px; }
+					.modal-dialog { width: 720px; }`;
+
+				frappe.dom.set_style(style, "custom-print-style");
+				preview_dialog.show();
+			}
+		}
+	});
+};
\ No newline at end of file
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
new file mode 100644
index 0000000..cb92c42
--- /dev/null
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -0,0 +1,772 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import os
+import re
+import jwt
+import sys
+import json
+import base64
+import frappe
+import traceback
+from frappe import _, bold
+from pyqrcode import create as qrcreate
+from frappe.integrations.utils import make_post_request, make_get_request
+from erpnext.regional.india.utils import get_gst_accounts, get_place_of_supply
+from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date
+
+def validate_einvoice_fields(doc):
+	einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
+	invalid_doctype = doc.doctype not in ['Sales Invoice']
+	invalid_supply_type = doc.get('gst_category') not in ['Registered Regular', 'SEZ', 'Overseas', 'Deemed Export']
+	company_transaction = doc.get('billing_address_gstin') == doc.get('company_gstin')
+
+	if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction: return
+
+	if doc.docstatus == 0 and doc._action == 'save':
+		if doc.irn:
+			frappe.throw(_('You cannot edit the invoice after generating IRN'), title=_('Edit Not Allowed'))
+		if len(doc.name) > 16:
+			raise_document_name_too_long_error()
+
+	elif doc.docstatus == 1 and doc._action == 'submit' and not doc.irn:
+		frappe.throw(_('You must generate IRN before submitting the document.'), title=_('Missing IRN'))
+
+	elif doc.docstatus == 2 and doc._action == 'cancel' and not doc.irn_cancelled:
+		frappe.throw(_('You must cancel IRN before cancelling the document.'), title=_('Cancel Not Allowed'))
+
+def raise_document_name_too_long_error():
+	title = _('Document ID Too Long')
+	msg = _('As you have E-Invoicing enabled, to be able to generate IRN for this invoice, ')
+	msg += _('document id {} exceed 16 letters. ').format(bold(_('should not')))
+	msg += '<br><br>'
+	msg += _('You must {} your {} in order to have document id of {} length 16. ').format(
+		bold(_('modify')), bold(_('naming series')), bold(_('maximum'))
+	)
+	msg += _('Please account for ammended documents too. ')
+	frappe.throw(msg, title=title)
+
+def read_json(name):
+	file_path = os.path.join(os.path.dirname(__file__), '{name}.json'.format(name=name))
+	with open(file_path, 'r') as f:
+		return cstr(f.read())
+
+def get_transaction_details(invoice):
+	supply_type = ''
+	if invoice.gst_category == 'Registered Regular': supply_type = 'B2B'
+	elif invoice.gst_category == 'SEZ': supply_type = 'SEZWOP'
+	elif invoice.gst_category == 'Overseas': supply_type = 'EXPWOP'
+	elif invoice.gst_category == 'Deemed Export': supply_type = 'DEXP'
+
+	if not supply_type: 
+		rr, sez, overseas, export = bold('Registered Regular'), bold('SEZ'), bold('Overseas'), bold('Deemed Export')
+		frappe.throw(_('GST category should be one of {}, {}, {}, {}').format(rr, sez, overseas, export),
+			title=_('Invalid Supply Type'))
+
+	return frappe._dict(dict(
+		tax_scheme='GST',
+		supply_type=supply_type,
+		reverse_charge=invoice.reverse_charge
+	))
+
+def get_doc_details(invoice):
+	invoice_type = 'CRN' if invoice.is_return else 'INV'
+
+	invoice_name = invoice.name
+	invoice_date = format_date(invoice.posting_date, 'dd/mm/yyyy')
+
+	return frappe._dict(dict(
+		invoice_type=invoice_type,
+		invoice_name=invoice_name,
+		invoice_date=invoice_date
+	))
+
+def get_party_details(address_name):
+	address = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
+	gstin = address.get('gstin')
+
+	gstin_details = get_gstin_details(gstin)
+	legal_name = gstin_details.get('LegalName')
+	location = gstin_details.get('AddrLoc') or address.get('city')
+	state_code = gstin_details.get('StateCode')
+	pincode = gstin_details.get('AddrPncd')
+	address_line1 = '{} {}'.format(gstin_details.get('AddrBno'), gstin_details.get('AddrFlno'))
+	address_line2 = '{} {}'.format(gstin_details.get('AddrBnm'), gstin_details.get('AddrSt'))
+	email_id = address.get('email_id')
+	phone = address.get('phone')
+	# get last 10 digit 
+	phone = phone.replace(" ", "")[-10:] if phone else ''
+
+	if state_code == 97:
+		# according to einvoice standard
+		pincode = 999999
+
+	return frappe._dict(dict(
+		gstin=gstin, legal_name=legal_name, location=location,
+		pincode=pincode, state_code=state_code, address_line1=address_line1,
+		address_line2=address_line2, email=email_id, phone=phone
+	))
+
+def get_gstin_details(gstin):
+	if not hasattr(frappe.local, 'gstin_cache'):
+		frappe.local.gstin_cache = {}
+
+	key = gstin
+	details = frappe.local.gstin_cache.get(key)
+	if details:
+		return details
+
+	details = frappe.cache().hget('gstin_cache', key)
+	if details:
+		frappe.local.gstin_cache[key] = details
+		return details
+	
+	if not details:
+		return GSPConnector.get_gstin_details(gstin)
+
+def get_overseas_address_details(address_name):
+	address_title, address_line1, address_line2, city, phone, email_id = frappe.db.get_value(
+		'Address', address_name, ['address_title', 'address_line1', 'address_line2', 'city', 'phone', 'email_id']
+	)
+
+	return frappe._dict(dict(
+		gstin='URP', legal_name=address_title, address_line1=address_line1,
+		address_line2=address_line2, email=email_id, phone=phone,
+		pincode=999999, state_code=96, place_of_supply=96, location=city
+	))
+
+def get_item_list(invoice):
+	item_list = []
+
+	for d in invoice.items:
+		einvoice_item_schema = read_json('einv_item_template')
+		item = frappe._dict({})
+		item.update(d.as_dict())
+
+		item.sr_no = d.idx
+		item.qty = abs(item.qty)
+		item.description = d.item_name
+		item.taxable_value = abs(item.base_net_amount)
+		item.discount_amount = abs(item.discount_amount * item.qty)
+		item.unit_rate = abs(item.base_price_list_rate) if item.discount_amount else abs(item.base_net_rate)
+		item.gross_amount = abs(item.unit_rate * item.qty)
+
+		item.batch_expiry_date = frappe.db.get_value('Batch', d.batch_no, 'expiry_date') if d.batch_no else None
+		item.batch_expiry_date = format_date(item.batch_expiry_date, 'dd/mm/yyyy') if item.batch_expiry_date else None
+		item.is_service_item = 'N' if frappe.db.get_value('Item', d.item_code, 'is_stock_item') else 'Y'
+
+		item = update_item_taxes(invoice, item)
+		
+		item.total_value = abs(
+			item.taxable_value + item.igst_amount + item.sgst_amount +
+			item.cgst_amount + item.cess_amount + item.cess_nadv_amount + item.other_charges
+		)
+		einv_item = einvoice_item_schema.format(item=item)
+		item_list.append(einv_item)
+
+	return ', '.join(item_list)
+
+def update_item_taxes(invoice, item):
+	gst_accounts = get_gst_accounts(invoice.company)
+	gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
+
+	for attr in [
+		'tax_rate', 'cess_rate', 'cess_nadv_amount',
+		'cgst_amount',  'sgst_amount', 'igst_amount',
+		'cess_amount', 'cess_nadv_amount', 'other_charges'
+		]:
+		item[attr] = 0
+
+	for t in invoice.taxes:
+		item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
+		if t.account_head in gst_accounts_list:
+			if t.account_head in gst_accounts.cess_account:
+				if t.charge_type == 'On Item Quantity':
+					item.cess_nadv_amount += abs(item_tax_detail[1])
+				else:
+					item.cess_rate += item_tax_detail[0]
+					item.cess_amount += abs(item_tax_detail[1])
+			elif t.account_head in gst_accounts.igst_account:
+				item.tax_rate += item_tax_detail[0]
+				item.igst_amount += abs(item_tax_detail[1])
+			elif t.account_head in gst_accounts.sgst_account:
+				item.tax_rate += item_tax_detail[0]
+				item.sgst_amount += abs(item_tax_detail[1])
+			elif t.account_head in gst_accounts.cgst_account:
+				item.tax_rate += item_tax_detail[0]
+				item.cgst_amount += abs(item_tax_detail[1])
+	
+	return item
+
+def get_invoice_value_details(invoice):
+	invoice_value_details = frappe._dict(dict())
+	invoice_value_details.base_net_total = abs(invoice.base_net_total)
+	invoice_value_details.invoice_discount_amt = invoice.discount_amount if invoice.discount_amount and invoice.discount_amount > 0 else 0
+	# discount amount cannnot be -ve in an e-invoice, so if -ve include discount in round_off
+	invoice_value_details.round_off = invoice.rounding_adjustment - (invoice.discount_amount if invoice.discount_amount and invoice.discount_amount < 0 else 0)
+	disable_rounded = frappe.db.get_single_value('Global Defaults', 'disable_rounded_total')
+	invoice_value_details.base_grand_total = abs(invoice.base_grand_total) if disable_rounded else abs(invoice.base_rounded_total)
+	invoice_value_details.grand_total = abs(invoice.grand_total) if disable_rounded else abs(invoice.rounded_total)
+	
+	invoice_value_details = update_invoice_taxes(invoice, invoice_value_details)
+	
+	return invoice_value_details
+
+def update_invoice_taxes(invoice, invoice_value_details):
+	gst_accounts = get_gst_accounts(invoice.company)
+	gst_accounts_list = [d for accounts in gst_accounts.values() for d in accounts if d]
+
+	invoice_value_details.total_cgst_amt = 0
+	invoice_value_details.total_sgst_amt = 0
+	invoice_value_details.total_igst_amt = 0
+	invoice_value_details.total_cess_amt = 0
+	invoice_value_details.total_other_charges = 0
+	for t in invoice.taxes:
+		if t.account_head in gst_accounts_list:
+			if t.account_head in gst_accounts.cess_account:
+				invoice_value_details.total_cess_amt += abs(t.base_tax_amount_after_discount_amount)
+			elif t.account_head in gst_accounts.igst_account:
+				invoice_value_details.total_igst_amt += abs(t.base_tax_amount_after_discount_amount)
+			elif t.account_head in gst_accounts.sgst_account:
+				invoice_value_details.total_sgst_amt += abs(t.base_tax_amount_after_discount_amount)
+			elif t.account_head in gst_accounts.cgst_account:
+				invoice_value_details.total_cgst_amt += abs(t.base_tax_amount_after_discount_amount)
+		else:
+			invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
+	
+	return invoice_value_details
+
+def get_payment_details(invoice):
+	payee_name = invoice.company
+	mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
+	paid_amount = invoice.base_paid_amount
+	outstanding_amount = invoice.outstanding_amount
+
+	return frappe._dict(dict(
+		payee_name=payee_name, mode_of_payment=mode_of_payment,
+		paid_amount=paid_amount, outstanding_amount=outstanding_amount
+	))
+
+def get_return_doc_reference(invoice):
+	invoice_date = frappe.db.get_value('Sales Invoice', invoice.return_against, 'posting_date')
+	return frappe._dict(dict(
+		invoice_name=invoice.return_against, invoice_date=format_date(invoice_date, 'dd/mm/yyyy')
+	))
+
+def get_eway_bill_details(invoice):
+	if invoice.is_return:
+		frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes'), title=_('E Invoice Validation Failed'))
+
+	mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
+	vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
+
+	return frappe._dict(dict(
+		gstin=invoice.gst_transporter_id,
+		name=invoice.transporter_name,
+		mode_of_transport=mode_of_transport[invoice.mode_of_transport],
+		distance=invoice.distance or 0,
+		document_name=invoice.lr_no,
+		document_date=format_date(invoice.lr_date, 'dd/mm/yyyy'),
+		vehicle_no=invoice.vehicle_no,
+		vehicle_type=vehicle_type[invoice.gst_vehicle_type]
+	))
+
+def make_einvoice(invoice):
+	schema = read_json('einv_template')
+
+	transaction_details = get_transaction_details(invoice)
+	item_list = get_item_list(invoice)
+	doc_details = get_doc_details(invoice)
+	invoice_value_details = get_invoice_value_details(invoice)
+	seller_details = get_party_details(invoice.company_address)
+
+	if invoice.gst_category == 'Overseas':
+		buyer_details = get_overseas_address_details(invoice.customer_address)
+	else:
+		buyer_details = get_party_details(invoice.customer_address)
+		place_of_supply = get_place_of_supply(invoice, invoice.doctype) or invoice.billing_address_gstin
+		place_of_supply = place_of_supply[:2]
+		buyer_details.update(dict(place_of_supply=place_of_supply))
+	
+	shipping_details = payment_details = prev_doc_details = eway_bill_details = frappe._dict({})
+	if invoice.shipping_address_name and invoice.customer_address != invoice.shipping_address_name:
+		shipping_details = get_party_details(invoice.shipping_address_name)
+	
+	if invoice.is_pos and invoice.base_paid_amount:
+		payment_details = get_payment_details(invoice)
+	
+	if invoice.is_return and invoice.return_against:
+		prev_doc_details = get_return_doc_reference(invoice)
+	
+	if invoice.transporter:
+		eway_bill_details = get_eway_bill_details(invoice)
+	
+	# not yet implemented
+	dispatch_details = period_details = export_details = frappe._dict({})
+
+	einvoice = schema.format(
+		transaction_details=transaction_details, doc_details=doc_details, dispatch_details=dispatch_details,
+		seller_details=seller_details, buyer_details=buyer_details, shipping_details=shipping_details,
+		item_list=item_list, invoice_value_details=invoice_value_details, payment_details=payment_details,
+		period_details=period_details, prev_doc_details=prev_doc_details,
+		export_details=export_details, eway_bill_details=eway_bill_details
+	)
+	einvoice = json.loads(einvoice)
+	
+	validations = json.loads(read_json('einv_validation'))
+	errors = validate_einvoice(validations, einvoice)
+	if errors:
+		message = "\n".join([
+			"E Invoice: ", json.dumps(einvoice, indent=4),
+			"-" * 50,
+			"Errors: ", json.dumps(errors, indent=4)
+		])
+		frappe.log_error(title="E Invoice Validation Failed", message=message)
+		frappe.throw(errors, title=_('E Invoice Validation Failed'), as_list=1)
+
+	return einvoice
+
+def validate_einvoice(validations, einvoice, errors=[]):
+	for fieldname, field_validation in validations.items():
+		value = einvoice.get(fieldname, None)
+		if not value or value == "None":
+			# remove keys with empty values
+			einvoice.pop(fieldname, None)
+			continue
+
+		value_type = field_validation.get("type").lower()
+		if value_type in ['object', 'array']:
+			child_validations = field_validation.get('properties')
+
+			if isinstance(value, list):
+				for d in value:
+					validate_einvoice(child_validations, d, errors)
+					if not d:
+						# remove empty dicts
+						einvoice.pop(fieldname, None)
+			else:
+				validate_einvoice(child_validations, value, errors)
+				if not value:
+					# remove empty dicts
+					einvoice.pop(fieldname, None)
+			continue
+		
+		# convert to int or str
+		if value_type == 'string':
+			einvoice[fieldname] = str(value)
+		elif value_type == 'number':
+			is_integer = '.' not in str(field_validation.get('maximum'))
+			einvoice[fieldname] = flt(value, 2) if not is_integer else cint(value)
+			value = einvoice[fieldname]
+
+		max_length = field_validation.get('maxLength')
+		minimum = flt(field_validation.get('minimum'))
+		maximum = flt(field_validation.get('maximum'))
+		pattern_str = field_validation.get('pattern')
+		pattern = re.compile(pattern_str or '')
+
+		label = field_validation.get('description') or fieldname
+
+		if value_type == 'string' and len(value) > max_length:
+			errors.append(_('{} should not exceed {} characters').format(label, max_length))
+		if value_type == 'number' and (value > maximum or value < minimum):
+			errors.append(_('{} {} should be between {} and {}').format(label, value, minimum, maximum))
+		if pattern_str and not pattern.match(value):
+			errors.append(field_validation.get('validationMsg'))
+	
+	return errors
+
+class RequestFailed(Exception): pass
+
+class GSPConnector():
+	def __init__(self, doctype=None, docname=None):
+		self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
+		self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
+		self.credentials = self.get_credentials()
+
+		self.base_url = 'https://gsp.adaequare.com/'
+		self.authenticate_url = self.base_url + 'gsp/authenticate?grant_type=token'
+		self.gstin_details_url = self.base_url + 'test/enriched/ei/api/master/gstin'
+		self.generate_irn_url = self.base_url + 'test/enriched/ei/api/invoice'
+		self.irn_details_url = self.base_url + 'test/enriched/ei/api/invoice/irn'
+		self.cancel_irn_url = self.base_url + 'test/enriched/ei/api/invoice/cancel'
+		self.cancel_ewaybill_url = self.base_url + '/test/enriched/ei/api/ewayapi'
+		self.generate_ewaybill_url = self.base_url + 'test/enriched/ei/api/ewaybill'
+	
+	def get_credentials(self):
+		if self.invoice:
+			gstin = self.get_seller_gstin()
+			credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
+		else:
+			credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
+		return credentials
+	
+	def get_seller_gstin(self):
+		gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
+		if not gstin:
+			frappe.throw(_('Cannot retrieve Company GSTIN. Please select company address with valid GSTIN.'))
+		return gstin
+	
+	def get_auth_token(self):
+		if time_diff_in_seconds(self.e_invoice_settings.token_expiry, now_datetime()) < 150.0:
+			self.fetch_auth_token()
+		
+		return self.e_invoice_settings.auth_token
+	
+	def make_request(self, request_type, url, headers=None, data=None):
+		if request_type == 'post':
+			res = make_post_request(url, headers=headers, data=data)
+		else:
+			res = make_get_request(url, headers=headers, data=data)
+
+		self.log_request(url, headers, data, res)
+		return res
+	
+	def log_request(self, url, headers, data, res):
+		headers.update({ 'password': self.credentials.password })
+		request_log = frappe.get_doc({
+			"doctype": "E Invoice Request Log",
+			"user": frappe.session.user,
+			"reference_invoice": self.invoice.name if self.invoice else None,
+			"url": url,
+			"headers": json.dumps(headers, indent=4) if headers else None,
+			"data": json.dumps(data, indent=4) if isinstance(data, dict) else data,
+			"response": json.dumps(res, indent=4) if res else None
+		})
+		request_log.insert(ignore_permissions=True)
+		frappe.db.commit()
+
+	def fetch_auth_token(self):
+		headers = {
+			'gspappid': frappe.conf.einvoice_client_id,
+			'gspappsecret': frappe.conf.einvoice_client_secret
+		}
+		res = {}
+		try:
+			res = self.make_request('post', self.authenticate_url, headers)
+			self.e_invoice_settings.auth_token = "{} {}".format(res.get('token_type'), res.get('access_token'))
+			self.e_invoice_settings.token_expiry = add_to_date(None, seconds=res.get('expires_in'))
+			self.e_invoice_settings.save()
+
+		except Exception:
+			self.log_error(res)
+			self.raise_error(True)
+	
+	def get_headers(self):
+		return {
+			'content-type': 'application/json',
+			'user_name': self.credentials.username,
+			'password': self.credentials.get_password(),
+			'gstin': self.credentials.gstin,
+			'authorization': self.get_auth_token(),
+			'requestid': str(base64.b64encode(os.urandom(18))),
+		}
+
+	def fetch_gstin_details(self, gstin):
+		headers = self.get_headers()
+
+		try:
+			params = '?gstin={gstin}'.format(gstin=gstin)
+			res = self.make_request('get', self.gstin_details_url + params, headers)
+			if res.get('success'):
+				return res.get('result')
+			else:
+				self.log_error(res)
+				raise RequestFailed
+		
+		except RequestFailed:
+			self.raise_error()
+
+		except Exception:
+			self.log_error()
+			self.raise_error(True)
+	
+	@staticmethod
+	def get_gstin_details(gstin):
+		'''fetch and cache GSTIN details'''
+		if not hasattr(frappe.local, 'gstin_cache'):
+			frappe.local.gstin_cache = {}
+
+		key = gstin
+		gsp_connector = GSPConnector()
+		details = gsp_connector.fetch_gstin_details(gstin)
+
+		frappe.local.gstin_cache[key] = details
+		frappe.cache().hset('gstin_cache', key, details)
+		return details
+
+	def generate_irn(self):
+		headers = self.get_headers()
+		einvoice = make_einvoice(self.invoice)
+		data = json.dumps(einvoice, indent=4)
+
+		try:
+			res = self.make_request('post', self.generate_irn_url, headers, data)
+			if res.get('success'):
+				self.set_einvoice_data(res.get('result'))
+
+			elif '2150' in res.get('message'):
+				# IRN already generated but not updated in invoice
+				# Extract the IRN from the response description and fetch irn details
+				irn = res.get('result')[0].get('Desc').get('Irn')
+				irn_details = self.get_irn_details(irn)
+				if irn_details:
+					self.set_einvoice_data(irn_details)
+				else:
+					raise RequestFailed('IRN has already been generated for the invoice but cannot fetch details for the it. \
+						Contact ERPNext support to resolve the issue.')
+
+			else:
+				raise RequestFailed
+		
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def get_irn_details(self, irn):
+		headers = self.get_headers()
+
+		try:
+			params = '?irn={irn}'.format(irn=irn)
+			res = self.make_request('get', self.irn_details_url + params, headers)
+			if res.get('success'):
+				return res.get('result')
+			else:
+				raise RequestFailed
+		
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error()
+			self.raise_error(True)
+	
+	def cancel_irn(self, irn, reason, remark):
+		headers = self.get_headers()
+		data = json.dumps({
+			'Irn': irn,
+			'Cnlrsn': reason,
+			'Cnlrem': remark
+		}, indent=4)
+
+		try:
+			res = self.make_request('post', self.cancel_irn_url, headers, data)
+			if res.get('success'):
+				self.invoice.irn_cancelled = 1
+				self.invoice.flags.updater_reference = {
+					'doctype': self.invoice.doctype,
+					'docname': self.invoice.name,
+					'label': _('IRN Cancelled - {}').format(remark)
+				}
+				self.update_invoice()
+
+			else:
+				raise RequestFailed
+		
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def generate_eway_bill(self, **kwargs):
+		args = frappe._dict(kwargs)
+
+		headers = self.get_headers()
+		eway_bill_details = get_eway_bill_details(args)
+		data = json.dumps({
+			'Irn': args.irn,
+			'Distance': cint(eway_bill_details.distance),
+			'TransMode': eway_bill_details.mode_of_transport,
+			'TransId': eway_bill_details.gstin,
+			'TransName': eway_bill_details.transporter,
+			'TrnDocDt': eway_bill_details.document_date,
+			'TrnDocNo': eway_bill_details.document_name,
+			'VehNo': eway_bill_details.vehicle_no,
+			'VehType': eway_bill_details.vehicle_type
+		}, indent=4)
+
+		try:
+			res = self.make_request('post', self.generate_ewaybill_url, headers, data)
+			if res.get('success'):
+				self.invoice.ewaybill = res.get('result').get('EwbNo')
+				self.invoice.eway_bill_cancelled = 0
+				self.invoice.update(args)
+				self.invoice.flags.updater_reference = {
+					'doctype': self.invoice.doctype,
+					'docname': self.invoice.name,
+					'label': _('E-Way Bill Generated')
+				}
+				self.update_invoice()
+
+			else:
+				raise RequestFailed
+
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def cancel_eway_bill(self, eway_bill, reason, remark):
+		headers = self.get_headers()
+		data = json.dumps({
+			'ewbNo': eway_bill,
+			'cancelRsnCode': reason,
+			'cancelRmrk': remark
+		}, indent=4)
+
+		try:
+			res = self.make_request('post', self.cancel_ewaybill_url, headers, data)
+			if res.get('success'):
+				self.invoice.ewaybill = ''
+				self.invoice.eway_bill_cancelled = 1
+				self.invoice.flags.updater_reference = {
+					'doctype': self.invoice.doctype,
+					'docname': self.invoice.name,
+					'label': _('E-Way Bill Cancelled - {}').format(remark)
+				}
+				self.update_invoice()
+
+			else:
+				raise RequestFailed
+
+		except RequestFailed:
+			errors = self.sanitize_error_message(res.get('message'))
+			self.raise_error(errors=errors)
+
+		except Exception:
+			self.log_error(data)
+			self.raise_error(True)
+	
+	def sanitize_error_message(self, message):
+		'''
+			On validation errors, response message looks something like this:
+			message = '2174 : For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable,
+						3095 : Supplier GSTIN is inactive'
+			we search for string between ':' to extract the error messages
+			errors = [
+				': For inter-state transaction, CGST and SGST amounts are not applicable; only IGST amount is applicable, 3095 ',
+				': Test'
+			]
+			then we trim down the message by looping over errors
+		'''
+		errors = re.findall(': [^:]+', message)
+		for idx, e in enumerate(errors):
+			# remove colons
+			errors[idx] = errors[idx].replace(':', '').strip()
+			# if not last
+			if idx != len(errors) - 1:
+				# remove last 7 chars eg: ', 3095 '
+				errors[idx] = errors[idx][:-6]
+
+		return errors
+
+	def log_error(self, data={}):
+		if not isinstance(data, dict):
+			data = json.loads(data)
+
+		seperator = "--" * 50
+		err_tb = traceback.format_exc()
+		err_msg = str(sys.exc_info()[1])
+		data = json.dumps(data, indent=4)
+
+		message = "\n".join([
+			"Error", err_msg, seperator,
+			"Data:", data, seperator,
+			"Exception:", err_tb
+		])
+		frappe.log_error(title=_('E Invoice Request Failed'), message=message)
+	
+	def raise_error(self, raise_exception=False, errors=[]):
+		title = _('E Invoice Request Failed')
+		if errors:
+			frappe.throw(errors, title=title, as_list=1)
+		else:
+			link_to_error_list = '<a href="desk#List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
+			frappe.msgprint(
+				_('An error occurred while making e-invoicing request. Please check {} for more information.').format(link_to_error_list),
+				title=title,
+				raise_exception=raise_exception,
+				indicator='red'
+			)
+	
+	def set_einvoice_data(self, res):
+		enc_signed_invoice = res.get('SignedInvoice')
+		dec_signed_invoice = jwt.decode(enc_signed_invoice, verify=False)['data']
+
+		self.invoice.irn = res.get('Irn')
+		self.invoice.ewaybill = res.get('EwbNo')
+		self.invoice.signed_einvoice = dec_signed_invoice
+		self.invoice.signed_qr_code = res.get('SignedQRCode')
+
+		self.attach_qrcode_image()
+
+		self.invoice.flags.updater_reference = {
+			'doctype': self.invoice.doctype,
+			'docname': self.invoice.name,
+			'label': _('IRN Generated')
+		}
+		self.update_invoice()
+	
+	def attach_qrcode_image(self):
+		qrcode = self.invoice.signed_qr_code
+		doctype = self.invoice.doctype
+		docname = self.invoice.name
+
+		_file = frappe.new_doc('File')
+		_file.update({
+			'file_name': f'QRCode_{docname}.png',
+			'attached_to_doctype': doctype,
+			'attached_to_name': docname,
+			'content': 'qrcode',
+			'is_private': 1
+		})
+		_file.insert()
+		frappe.db.commit()
+		url = qrcreate(qrcode, error='L')
+		abs_file_path = os.path.abspath(_file.get_full_path())
+		url.png(abs_file_path, scale=2, quiet_zone=1)
+
+		self.invoice.qrcode_image = _file.file_url
+	
+	def update_invoice(self):
+		self.invoice.flags.ignore_validate_update_after_submit = True
+		self.invoice.flags.ignore_validate = True
+		self.invoice.save()
+
+@frappe.whitelist()
+def get_einvoice(doctype, docname):
+	invoice = frappe.get_doc(doctype, docname)
+	return make_einvoice(invoice)
+
+@frappe.whitelist()
+def generate_irn(doctype, docname):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.generate_irn()
+
+@frappe.whitelist()
+def cancel_irn(doctype, docname, irn, reason, remark):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.cancel_irn(irn, reason, remark)
+
+@frappe.whitelist()
+def generate_eway_bill(doctype, docname, **kwargs):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.generate_eway_bill(**kwargs)
+
+@frappe.whitelist()
+def cancel_eway_bill(doctype, docname, eway_bill, reason, remark):
+	gsp_connector = GSPConnector(doctype, docname)
+	gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
\ No newline at end of file
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index cbcd6e3..5321a9a 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -87,7 +87,7 @@
 			)).insert()
 
 def add_permissions():
-	for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate'):
+	for doctype in ('GST HSN Code', 'GST Settings', 'GSTR 3B Report', 'Lower Deduction Certificate', 'E Invoice Settings'):
 		add_permission(doctype, 'All', 0)
 		for role in ('Accounts Manager', 'Accounts User', 'System Manager'):
 			add_permission(doctype, role, 0)
@@ -103,9 +103,10 @@
 def add_print_formats():
 	frappe.reload_doc("regional", "print_format", "gst_tax_invoice")
 	frappe.reload_doc("accounts", "print_format", "gst_pos_invoice")
+	frappe.reload_doc("accounts", "print_format", "GST E-Invoice")
 
 	frappe.db.sql(""" update `tabPrint Format` set disabled = 0 where
-		name in('GST POS Invoice', 'GST Tax Invoice') """)
+		name in('GST POS Invoice', 'GST Tax Invoice', 'GST E-Invoice') """)
 
 def make_custom_fields(update=True):
 	hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
@@ -351,7 +352,6 @@
 			'label': 'Mode of Transport',
 			'fieldtype': 'Select',
 			'options': '\nRoad\nAir\nRail\nShip',
-			'default': 'Road',
 			'insert_after': 'transporter_name',
 			'print_hide': 1,
 			'translatable': 0
@@ -388,13 +388,34 @@
 			'fieldname': 'ewaybill',
 			'label': 'E-Way Bill No.',
 			'fieldtype': 'Data',
-			'depends_on': 'eval:(doc.docstatus === 1)',
+			'depends_on': 'eval:((doc.docstatus === 1 || doc.ewaybill) && doc.eway_bill_cancelled === 0)',
 			'allow_on_submit': 1,
 			'insert_after': 'tax_id',
 			'translatable': 0
 		}
 	]
 
+	si_einvoice_fields = [
+		dict(fieldname='irn', label='IRN', fieldtype='Data', read_only=1, insert_after='customer', no_copy=1, print_hide=1,
+			depends_on='eval:in_list(["Registered Regular", "SEZ", "Overseas", "Deemed Export"], doc.gst_category) && doc.irn_cancelled === 0'),
+		
+		dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='irn', no_copy=1, print_hide=1),
+		
+		dict(fieldname='ack_date', label='Ack. Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_no', no_copy=1, print_hide=1),
+
+		dict(fieldname='irn_cancelled', label='IRN Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+			depends_on='eval:(doc.irn_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+		dict(fieldname='eway_bill_cancelled', label='E-Way Bill Cancelled', fieldtype='Check', no_copy=1, print_hide=1,
+			depends_on='eval:(doc.eway_bill_cancelled === 1)', read_only=1, allow_on_submit=1, insert_after='customer'),
+
+		dict(fieldname='signed_einvoice', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+	]
+
 	custom_fields = {
 		'Address': [
 			dict(fieldname='gstin', label='Party GSTIN', fieldtype='Data',
@@ -407,7 +428,7 @@
 		'Purchase Invoice': purchase_invoice_gst_category + invoice_gst_fields + purchase_invoice_itc_fields + purchase_invoice_gst_fields,
 		'Purchase Order': purchase_invoice_gst_fields,
 		'Purchase Receipt': purchase_invoice_gst_fields,
-		'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields,
+		'Sales Invoice': sales_invoice_gst_category + invoice_gst_fields + sales_invoice_shipping_fields + sales_invoice_gst_fields + si_ewaybill_fields + si_einvoice_fields,
 		'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
 		'Sales Order': sales_invoice_gst_fields,
 		'Tax Category': inter_state_gst_field,
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 03921c5..5f2658c 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -15,6 +15,7 @@
 			'Installation Note': 'Installation Note',
 			'Sales Invoice': 'Invoice',
 			'Stock Entry': 'Return',
+			'Shipment': 'Shipment'
 		},
 		frm.set_indicator_formatter('item_code',
 			function(doc) {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 1a6a555..a30cadf 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -598,6 +598,9 @@
 				pickup_contact_display += '<br>' + user.mobile_no
 		target.pickup_contact = pickup_contact_display
 
+		# As we are using session user details in the pickup_contact then pickup_contact_person will be session user
+		target.pickup_contact_person = frappe.session.user
+
 		contact = frappe.db.get_value("Contact", source.contact_person, ['email_id', 'phone', 'mobile_no'], as_dict=1)
 		delivery_contact_display = '{}'.format(source.contact_display)
 		if contact:
@@ -609,6 +612,13 @@
 				delivery_contact_display += '<br>' + contact.mobile_no
 		target.delivery_contact = delivery_contact_display
 
+		if source.shipping_address_name:
+			target.delivery_address_name = source.shipping_address_name
+			target.delivery_address = source.shipping_address
+		elif source.customer_address:
+			target.delivery_address_name = source.customer_address
+			target.delivery_address = source.address_display
+
 	doclist = get_mapped_doc("Delivery Note", source_name, 	{
 		"Delivery Note": {
 			"doctype": "Shipment",
@@ -617,9 +627,7 @@
 				"company": "pickup_company",
 				"company_address": "pickup_address_name",
 				"company_address_display": "pickup_address",
-				"address_display": "delivery_address",
 				"customer": "delivery_customer",
-				"shipping_address_name": "delivery_address_name",
 				"contact_person": "delivery_contact_name",
 				"contact_email": "delivery_contact_email"
 			},
@@ -637,7 +645,7 @@
 			}
 		}
 	}, target_doc, postprocess)
-	
+
 	return doclist
 
 @frappe.whitelist()
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
index beeb9eb..47684d5 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note_dashboard.py
@@ -19,7 +19,7 @@
 			},
 			{
 				'label': _('Reference'),
-				'items': ['Sales Order', 'Quality Inspection']
+				'items': ['Sales Order', 'Shipment', 'Quality Inspection']
 			},
 			{
 				'label': _('Returns'),
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 226064b..f833fc7 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -325,7 +325,7 @@
 				elif d.warehouse not in warehouse_with_no_account or \
 					d.rejected_warehouse not in warehouse_with_no_account:
 						warehouse_with_no_account.append(d.warehouse)
-			elif d.item_code not in stock_items and flt(d.qty) and auto_accounting_for_non_stock_items:
+			elif d.item_code not in stock_items and not d.is_fixed_asset and flt(d.qty) and auto_accounting_for_non_stock_items:
 
 				service_received_but_not_billed_account = self.get_company_default("service_received_but_not_billed")
 				credit_currency = get_account_currency(service_received_but_not_billed_account)
@@ -408,7 +408,7 @@
 		if warehouse_with_no_account:
 			frappe.msgprint(_("No accounting entries for the following warehouses") + ": \n" +
 				"\n".join(warehouse_with_no_account))
-
+		
 		return process_gl_map(gl_entries)
 
 	def get_asset_gl_entry(self, gl_entries):
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 83012d3..f99ca89 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -575,7 +575,7 @@
 
 		se = make_stock_entry(item_code=item_code, target="_Test Warehouse - _TC", qty=1,
 			serial_no=serial_no, basic_rate=100, do_not_submit=True)
-		self.assertRaises(SerialNoDuplicateError, se.submit)
+		se.submit()
 
 		dn.cancel()
 		pr1.cancel()
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index a942f2e..ba2c2c6 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -5,11 +5,11 @@
 from __future__ import unicode_literals
 import frappe, erpnext
 from frappe.model.document import Document
-from frappe.utils import cint
+from frappe.utils import cint, get_link_to_form
 from erpnext.stock.stock_ledger import repost_future_sle
-from erpnext.accounts.utils import update_gl_entries_after
-
-
+from erpnext.accounts.utils import update_gl_entries_after, check_if_stock_and_account_balance_synced
+from frappe.utils.user import get_users_with_role
+from frappe import _
 class RepostItemValuation(Document):
 	def validate(self):
 		self.set_status()
@@ -51,12 +51,20 @@
 
 		repost_sl_entries(doc)
 		repost_gl_entries(doc)
+		check_if_stock_and_account_balance_synced(doc.posting_date, doc.company)
+
 		doc.set_status('Completed')
 	except Exception:
 		frappe.db.rollback()
 		traceback = frappe.get_traceback()
 		frappe.log_error(traceback)
-		frappe.db.set_value(doc.doctype, doc.name, 'error_log', traceback)
+
+		message = frappe.message_log.pop()
+		if traceback:
+			message += "<br>" + "Traceback: <br>" + traceback
+		frappe.db.set_value(doc.doctype, doc.name, 'error_log', message)
+
+		notify_error_to_stock_managers(doc)
 		doc.set_status('Failed')
 		raise
 	finally:
@@ -86,4 +94,19 @@
 		warehouses = [doc.warehouse]
 
 	update_gl_entries_after(doc.posting_date, doc.posting_time,
-		warehouses, items, company=doc.company)
\ No newline at end of file
+		warehouses, items, company=doc.company)
+
+def notify_error_to_stock_managers(doc, traceback):
+	recipients = get_users_with_role("Stock Manager")
+	if not recipients:
+		get_users_with_role("System Manager")
+	
+	subject = _("Error while reposting item valuation")
+	message = (_("Hi,") + "<br>"
+		+ _("An error has been appeared while reposting item valuation via {0}")
+			.format(get_link_to_form(doc.doctype, doc.name)) + "<br>"
+		+ _("Please check the error message and take necessary actions to fix the error and then restart the reposting again.")
+	)
+	frappe.sendmail(recipients=recipients, subject=subject, message=message)
+
+
diff --git a/erpnext/stock/doctype/serial_no/serial_no.py b/erpnext/stock/doctype/serial_no/serial_no.py
index 39ccf49..6bacf1f 100644
--- a/erpnext/stock/doctype/serial_no/serial_no.py
+++ b/erpnext/stock/doctype/serial_no/serial_no.py
@@ -6,7 +6,7 @@
 import json
 
 from frappe.model.naming import make_autoname
-from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate
+from frappe.utils import cint, cstr, flt, add_days, nowdate, getdate, get_link_to_form
 from erpnext.stock.get_item_details import get_reserved_qty_for_so
 
 from frappe import _, ValidationError
@@ -241,7 +241,7 @@
 			for serial_no in serial_nos:
 				if frappe.db.exists("Serial No", serial_no):
 					sr = frappe.db.get_value("Serial No", serial_no, ["name", "item_code", "batch_no", "sales_order",
-						"delivery_document_no", "delivery_document_type", "warehouse",
+						"delivery_document_no", "delivery_document_type", "warehouse", "purchase_document_type",
 						"purchase_document_no", "company"], as_dict=1)
 
 					if sr.item_code!=sle.item_code:
@@ -249,9 +249,10 @@
 							frappe.throw(_("Serial No {0} does not belong to Item {1}").format(serial_no,
 								sle.item_code), SerialNoItemError)
 
-					if cint(sle.actual_qty) > 0 and has_duplicate_serial_no(sr, sle):
-						frappe.throw(_("Serial No {0} has already been received").format(serial_no),
-							SerialNoDuplicateError)
+					if cint(sle.actual_qty) > 0 and has_serial_no_exists(sr, sle):
+						doc_name = frappe.bold(get_link_to_form(sr.purchase_document_type, sr.purchase_document_no))
+						frappe.throw(_("Serial No {0} has already been received in the {1} #{2}")
+							.format(frappe.bold(serial_no), sr.purchase_document_type, doc_name), SerialNoDuplicateError)
 
 					if (sr.delivery_document_no and sle.voucher_type not in ['Stock Entry', 'Stock Reconciliation']
 						and sle.voucher_type == sr.delivery_document_type):
@@ -348,7 +349,7 @@
 		frappe.throw(_("""{0} Serial No {1} cannot be delivered""")
 			.format(msg, sr.name))
 
-def has_duplicate_serial_no(sn, sle):
+def has_serial_no_exists(sn, sle):
 	if (sn.warehouse and not sle.skip_serial_no_validaiton
 		and sle.voucher_type != 'Stock Reconciliation'):
 		return True
@@ -358,12 +359,13 @@
 
 	status = False
 	if sn.purchase_document_no:
-		if sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and \
-			sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]:
+		if (sle.voucher_type in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"] and
+			sn.delivery_document_type not in ['Purchase Receipt', 'Stock Entry', "Purchase Invoice"]):
 			status = True
 
-		if status and sle.voucher_type == 'Stock Entry' and \
-			frappe.db.get_value('Stock Entry', sle.voucher_no, 'purpose') != 'Material Receipt':
+		# If status is receipt then system will allow to in-ward the delivered serial no
+		if (status and sle.voucher_type == "Stock Entry" and frappe.db.get_value("Stock Entry",
+			sle.voucher_no, "purpose") in ("Material Receipt", "Material Transfer")):
 			status = False
 
 	return status
@@ -419,7 +421,7 @@
 		if is_new:
 			created_numbers.append(sr.name)
 
-	form_links = list(map(lambda d: frappe.utils.get_link_to_form('Serial No', d), created_numbers))
+	form_links = list(map(lambda d: get_link_to_form('Serial No', d), created_numbers))
 
 	# Setting up tranlated title field for all cases
 	singular_title = _("Serial Number Created")
diff --git a/erpnext/stock/doctype/shipment/shipment.json b/erpnext/stock/doctype/shipment/shipment.json
index 37a9cc6..76c331c 100644
--- a/erpnext/stock/doctype/shipment/shipment.json
+++ b/erpnext/stock/doctype/shipment/shipment.json
@@ -345,7 +345,8 @@
    "label": "Status",
    "no_copy": 1,
    "options": "Draft\nSubmitted\nBooked\nCancelled\nCompleted",
-   "print_hide": 1
+   "print_hide": 1,
+   "read_only": 1
   },
   {
    "fieldname": "tracking_url",
@@ -430,7 +431,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-02 15:43:44.607039",
+ "modified": "2020-12-25 15:02:34.891976",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Shipment",
diff --git a/erpnext/stock/doctype/shipment/shipment.py b/erpnext/stock/doctype/shipment/shipment.py
index de0c243..4697a7b 100644
--- a/erpnext/stock/doctype/shipment/shipment.py
+++ b/erpnext/stock/doctype/shipment/shipment.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import flt
+from frappe.utils import flt, get_time
 from frappe.model.document import Document
 from erpnext.accounts.party import get_party_shipping_address
 from frappe.contacts.doctype.contact.contact import get_default_contact
@@ -13,6 +13,7 @@
 class Shipment(Document):
 	def validate(self):
 		self.validate_weight()
+		self.validate_pickup_time()
 		self.set_value_of_goods()
 		if self.docstatus == 0:
 			self.status = 'Draft'
@@ -32,6 +33,10 @@
 			if flt(parcel.weight) <= 0:
 				frappe.throw(_('Parcel weight cannot be 0'))
 
+	def validate_pickup_time(self):
+		if self.pickup_from and self.pickup_to and get_time(self.pickup_to) < get_time(self.pickup_from):
+			frappe.throw(_("Pickup To time should be greater than Pickup From time"))
+
 	def set_value_of_goods(self):
 		value_of_goods = 0
 		for entry in self.get("shipment_delivery_note"):
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 579b8c5..92d268f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -442,6 +442,7 @@
 		"""
 		# Set rate for outgoing items
 		outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate)
+		finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item])
 
 		# Set basic rate for incoming items
 		for d in self.get('items'):
@@ -451,7 +452,7 @@
 				d.basic_rate = 0.0
 			elif d.is_finished_item:
 				if self.purpose == "Manufacture":
-					d.basic_rate = self.get_basic_rate_for_manufactured_item(d.transfer_qty, outgoing_items_cost)
+					d.basic_rate = self.get_basic_rate_for_manufactured_item(finished_item_qty, outgoing_items_cost)
 				elif self.purpose == "Repack":
 					d.basic_rate = self.get_basic_rate_for_repacked_items(d.transfer_qty, outgoing_items_cost)
 
@@ -666,7 +667,7 @@
 		production_item, wo_qty = frappe.db.get_value("Work Order",
 			self.work_order, ["production_item", "qty"])
 
-		number_of_finished_items = 0
+		finished_items = []
 		for d in self.get('items'):
 			if d.is_finished_item:
 				if d.item_code != production_item:
@@ -675,9 +676,9 @@
 				elif flt(d.transfer_qty) > flt(self.fg_completed_qty):
 					frappe.throw(_("Quantity in row {0} ({1}) must be same as manufactured quantity {2}"). \
 						format(d.idx, d.transfer_qty, self.fg_completed_qty))
-				number_of_finished_items += 1
+				finished_items.append(d.item_code)
 
-		if number_of_finished_items > 1:
+		if len(set(finished_items)) > 1:
 			frappe.throw(_("Multiple items cannot be marked as finished item"))
 
 		if self.purpose == "Manufacture":
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 1a64185..123f0c8 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -179,22 +179,20 @@
 	def test_material_transfer_gl_entry(self):
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 
-		create_stock_reconciliation(qty=100, rate=100)
-
 		mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1",
-			target="Finished Goods - TCP1", qty=45)
+			target="Finished Goods - TCP1", qty=45, company=company)
 
 		self.check_stock_ledger_entries("Stock Entry", mtn.name,
 			[["_Test Item", "Stores - TCP1", -45.0], ["_Test Item", "Finished Goods - TCP1", 45.0]])
 
-		stock_in_hand_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
+		source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
 
-		fixed_asset_account = get_inventory_account(mtn.company, mtn.get("items")[0].t_warehouse)
+		target_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].t_warehouse)
 
-		if stock_in_hand_account == fixed_asset_account:
+		if source_warehouse_account == target_warehouse_account:
 			# no gl entry as both source and target warehouse has linked to same account.
 			self.assertFalse(frappe.db.sql("""select * from `tabGL Entry`
-				where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name))
+				where voucher_type='Stock Entry' and voucher_no=%s""", mtn.name, as_dict=1))
 
 		else:
 			stock_value_diff = abs(frappe.db.get_value("Stock Ledger Entry", {"voucher_type": "Stock Entry",
@@ -202,8 +200,8 @@
 
 			self.check_gl_entries("Stock Entry", mtn.name,
 				sorted([
-					[stock_in_hand_account, 0.0, stock_value_diff],
-					[fixed_asset_account, stock_value_diff, 0.0],
+					[source_warehouse_account, 0.0, stock_value_diff],
+					[target_warehouse_account, stock_value_diff, 0.0],
 				])
 			)
 
@@ -754,37 +752,37 @@
 
 	def test_total_basic_amount_zero(self):
 		se = frappe.get_doc({"doctype":"Stock Entry",
-		"purpose":"Material Receipt",
-		"stock_entry_type":"Material Receipt",
-		"posting_date": nowdate(),
-		"company":"_Test Company with perpetual inventory",
-		"items":[
-			{
-				"item_code":"_Test Item",
-				"description":"_Test Item",
-				"qty": 1,
-				"basic_rate": 0,
-				"uom":"Nos",
-				"t_warehouse": "Stores - TCP1",
-				"allow_zero_valuation_rate": 1,
-				"cost_center": "Main - TCP1"
-			 },
-			 {
-				"item_code":"_Test Item",
-				"description":"_Test Item",
-				"qty": 2,
-				"basic_rate": 0,
-				"uom":"Nos",
-				"t_warehouse": "Stores - TCP1",
-				"allow_zero_valuation_rate": 1,
-				"cost_center": "Main - TCP1"
-			 },
-			 ],
-		"additional_costs":[
-			{"expense_account":"Miscellaneous Expenses - TCP1",
-			"amount":100,
-			"description": "miscellanous"}
-			]
+			"purpose":"Material Receipt",
+			"stock_entry_type":"Material Receipt",
+			"posting_date": nowdate(),
+			"company":"_Test Company with perpetual inventory",
+			"items":[
+				{
+					"item_code":"_Test Item",
+					"description":"_Test Item",
+					"qty": 1,
+					"basic_rate": 0,
+					"uom":"Nos",
+					"t_warehouse": "Stores - TCP1",
+					"allow_zero_valuation_rate": 1,
+					"cost_center": "Main - TCP1"
+				},
+				{
+					"item_code":"_Test Item",
+					"description":"_Test Item",
+					"qty": 2,
+					"basic_rate": 0,
+					"uom":"Nos",
+					"t_warehouse": "Stores - TCP1",
+					"allow_zero_valuation_rate": 1,
+					"cost_center": "Main - TCP1"
+				},
+			],
+			"additional_costs":[
+				{"expense_account":"Miscellaneous Expenses - TCP1",
+				"amount":100,
+				"description": "miscellanous"
+			}]
 		})
 		se.insert()
 		se.submit()
diff --git a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
index 6fe6029..b78ae6d 100644
--- a/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
+++ b/erpnext/stock/doctype/stock_entry_detail/stock_entry_detail.json
@@ -526,7 +526,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-23 17:55:03.384138",
+ "modified": "2020-12-23 17:55:03.384138",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry Detail",
diff --git a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
index 1af68dd..14d543b 100644
--- a/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
+++ b/erpnext/stock/report/stock_and_account_value_comparison/stock_and_account_value_comparison.py
@@ -57,8 +57,7 @@
 	if report_filters.account:
 		stock_accounts = [report_filters.account]
 	else:
-		stock_accounts = [k.name
-			for k in get_stock_accounts(report_filters.company)]
+		stock_accounts = get_stock_accounts(report_filters.company)
 
 	filters.update({
 		"account": ("in", stock_accounts)
diff --git a/requirements.txt b/requirements.txt
index 678cf74..4511aa5 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,3 +12,4 @@
 tweepy==3.8.0
 Unidecode==1.1.1
 WooCommerce==2.1.1
+pycryptodome==3.9.8
\ No newline at end of file