Merge branch 'develop' into patch-8
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000..399b176
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,32 @@
+[flake8]
+ignore =
+    E121,
+    E126,
+    E127,
+    E128,
+    E203,
+    E225,
+    E226,
+    E231,
+    E241,
+    E251,
+    E261,
+    E265,
+    E302,
+    E303,
+    E305,
+    E402,
+    E501,
+    E741,
+    W291,
+    W292,
+    W293,
+    W391,
+    W503,
+    W504,
+    F403,
+    B007,
+    B950,
+    W191,
+
+max-line-length = 200
\ No newline at end of file
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 253ad70..7b0f944 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -12,7 +12,7 @@
 
 pip install frappe-bench
 
-git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF}" --depth 1
+git clone https://github.com/frappe/frappe --branch "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}" --depth 1
 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
 
 mkdir ~/frappe-bench/sites/test_site
@@ -43,4 +43,4 @@
 
 bench get-app erpnext "${GITHUB_WORKSPACE}"
 bench start &
-bench --site test_site reinstall --yes
\ No newline at end of file
+bench --site test_site reinstall --yes
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
index 2a1db14..78c2f5a 100644
--- a/.github/workflows/ci-tests.yml
+++ b/.github/workflows/ci-tests.yml
@@ -1,12 +1,10 @@
 name: CI
 
-on:
-  pull_request:
-  workflow_dispatch:
+on: [pull_request, workflow_dispatch, push]
 
 jobs:
   test:
-    runs-on: ubuntu-latest
+    runs-on: ubuntu-18.04
 
     strategy:
       fail-fast: false
diff --git a/README.md b/README.md
index 15782a2..bb592ae 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@
         <p>ERP made simple</p>
     </p>
 
-[![Build Status](https://api.travis-ci.com/frappe/erpnext.svg?branch=develop)](https://travis-ci.com/frappe/erpnext)
+[![CI](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml/badge.svg?branch=develop)](https://github.com/frappe/erpnext/actions/workflows/ci-tests.yml)
 [![Open Source Helpers](https://www.codetriage.com/frappe/erpnext/badges/users.svg)](https://www.codetriage.com/frappe/erpnext)
 [![Coverage Status](https://coveralls.io/repos/github/frappe/erpnext/badge.svg?branch=develop)](https://coveralls.io/github/frappe/erpnext?branch=develop)
 
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index c801cfc..0606823 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -214,6 +214,7 @@
 				if parent_value_changed:
 					doc.save()
 
+	@frappe.whitelist()
 	def convert_group_to_ledger(self):
 		if self.check_if_child_exists():
 			throw(_("Account with child nodes cannot be converted to ledger"))
@@ -224,6 +225,7 @@
 			self.save()
 			return 1
 
+	@frappe.whitelist()
 	def convert_ledger_to_group(self):
 		if self.check_gle_exists():
 			throw(_("Account with existing transaction can not be converted to group."))
diff --git a/erpnext/accounts/doctype/accounting_period/accounting_period.py b/erpnext/accounts/doctype/accounting_period/accounting_period.py
index df6cedd..63b5dbb 100644
--- a/erpnext/accounts/doctype/accounting_period/accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/accounting_period.py
@@ -39,6 +39,7 @@
 			frappe.throw(_("Accounting Period overlaps with {0}")
 				.format(existing_accounting_period[0].get("name")), OverlapError)
 
+	@frappe.whitelist()
 	def get_doctypes_for_closing(self):
 		docs_for_closing = []
 		doctypes = ["Sales Invoice", "Purchase Invoice", "Journal Entry", "Payroll Entry", \
diff --git a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
index 022d7a7..10cd939 100644
--- a/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
+++ b/erpnext/accounts/doctype/accounting_period/test_accounting_period.py
@@ -11,36 +11,36 @@
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 
 class TestAccountingPeriod(unittest.TestCase):
-    def test_overlap(self):
-        ap1 = create_accounting_period(start_date = "2018-04-01",
-            end_date = "2018-06-30", company = "Wind Power LLC")
-        ap1.save()
+	def test_overlap(self):
+		ap1 = create_accounting_period(start_date = "2018-04-01",
+			end_date = "2018-06-30", company = "Wind Power LLC")
+		ap1.save()
 
-        ap2 = create_accounting_period(start_date = "2018-06-30",
-            end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
-        self.assertRaises(OverlapError, ap2.save)
+		ap2 = create_accounting_period(start_date = "2018-06-30",
+			end_date = "2018-07-10", company = "Wind Power LLC", period_name = "Test Accounting Period 1")
+		self.assertRaises(OverlapError, ap2.save)
 
-    def test_accounting_period(self):
-        ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
-        ap1.save()
+	def test_accounting_period(self):
+		ap1 = create_accounting_period(period_name = "Test Accounting Period 2")
+		ap1.save()
 
-        doc = create_sales_invoice(do_not_submit=1, cost_center = "_Test Company - _TC", warehouse = "Stores - _TC")
-        self.assertRaises(ClosedAccountingPeriod, doc.submit)
+		doc = create_sales_invoice(do_not_submit=1, cost_center="_Test Company - _TC", warehouse="Stores - _TC")
+		self.assertRaises(ClosedAccountingPeriod, doc.submit)
 
-    def tearDown(self):
-        for d in frappe.get_all("Accounting Period"):
-            frappe.delete_doc("Accounting Period", d.name)
+	def tearDown(self):
+		for d in frappe.get_all("Accounting Period"):
+			frappe.delete_doc("Accounting Period", d.name)
 
 def create_accounting_period(**args):
-    args = frappe._dict(args)
+	args = frappe._dict(args)
 
-    accounting_period = frappe.new_doc("Accounting Period")
-    accounting_period.start_date = args.start_date or nowdate()
-    accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
-    accounting_period.company = args.company or "_Test Company"
-    accounting_period.period_name =args.period_name or  "_Test_Period_Name_1"
-    accounting_period.append("closed_documents", {
-        "document_type": 'Sales Invoice', "closed": 1
-    })
+	accounting_period = frappe.new_doc("Accounting Period")
+	accounting_period.start_date = args.start_date or nowdate()
+	accounting_period.end_date = args.end_date or add_months(nowdate(), 1)
+	accounting_period.company = args.company or "_Test Company"
+	accounting_period.period_name =args.period_name or  "_Test_Period_Name_1"
+	accounting_period.append("closed_documents", {
+		"document_type": 'Sales Invoice', "closed": 1
+	})
 
-    return accounting_period
\ No newline at end of file
+	return accounting_period
diff --git a/erpnext/accounts/doctype/bank/bank.js b/erpnext/accounts/doctype/bank/bank.js
index 49b2b18..059e1d3 100644
--- a/erpnext/accounts/doctype/bank/bank.js
+++ b/erpnext/accounts/doctype/bank/bank.js
@@ -42,10 +42,9 @@
 		});
 	});
 
-	frappe.meta.get_docfield("Bank Transaction Mapping", "bank_transaction_field",
-		frm.doc.name).options = options;
-
-	frm.fields_dict.bank_transaction_mapping.grid.refresh();
+	frm.fields_dict.bank_transaction_mapping.grid.update_docfield_property(
+		'bank_transaction_field', 'options', options
+	);
 };
 
 erpnext.integrations.refreshPlaidLink = class refreshPlaidLink {
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 76d82e7..79f5596 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -12,6 +12,7 @@
 }
 
 class BankClearance(Document):
+	@frappe.whitelist()
 	def get_payment_entries(self):
 		if not (self.from_date and self.to_date):
 			frappe.throw(_("From Date and To Date are Mandatory"))
@@ -108,6 +109,7 @@
 			row.update(d)
 			self.total_amount += flt(amount)
 
+	@frappe.whitelist()
 	def update_clearance_date(self):
 		clearance_date_updated = False
 		for d in self.get('payment_entries'):
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
index ad4ff9e..3dbd605 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.js
@@ -532,43 +532,4 @@
 			</table>
 		`);
 	},
-
-	show_missing_link_values(frm, missing_link_values) {
-		let can_be_created_automatically = missing_link_values.every(
-			(d) => d.has_one_mandatory_field
-		);
-
-		let html = missing_link_values
-			.map((d) => {
-				let doctype = d.doctype;
-				let values = d.missing_values;
-				return `
-					<h5>${doctype}</h5>
-					<ul>${values.map((v) => `<li>${v}</li>`).join("")}</ul>
-				`;
-			})
-			.join("");
-
-		if (can_be_created_automatically) {
-			// prettier-ignore
-			let message = __('There are some linked records which needs to be created before we can import your file. Do you want to create the following missing records automatically?');
-			frappe.confirm(message + html, () => {
-				frm.call("create_missing_link_values", {
-					missing_link_values,
-				}).then((r) => {
-					let records = r.message;
-					frappe.msgprint(__(
-						"Created {0} records successfully.", [
-							records.length,
-						]
-					));
-				});
-			});
-		} else {
-			frappe.msgprint(
-				// prettier-ignore
-				__('The following records needs to be created before we can import your file.') + html
-			);
-		}
-	},
 });
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 3b14e4e..ce149f9 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -15,12 +15,14 @@
 test_dependencies = ["Item", "Cost Center"]
 
 class TestBankTransaction(unittest.TestCase):
-	def setUp(self):
+	@classmethod
+	def setUpClass(cls):
 		make_pos_profile()
 		add_transactions()
 		add_vouchers()
 
-	def tearDown(self):
+	@classmethod
+	def tearDownClass(cls):
 		for bt in frappe.get_all("Bank Transaction"):
 			doc = frappe.get_doc("Bank Transaction", bt.name)
 			doc.cancel()
@@ -33,9 +35,6 @@
 		# Delete POS Profile
 		frappe.db.sql("delete from `tabPOS Profile`")
 
-		frappe.flags.test_bank_transactions_created = False
-		frappe.flags.test_payments_created = False
-
 	# This test checks if ERPNext is able to provide a linked payment for a bank transaction based on the amount of the bank transaction.
 	def test_linked_payments(self):
 		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Re 95282925234 FE/000002917 AT171513000281183046 Conrad Electronic"))
@@ -44,8 +43,8 @@
 
 	# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
 	def test_reconcile(self):
-		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000002918 OPSKATTUZWXXX AT776000000098709837 Herr G"))
-		payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1200))
+		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="1512567 BG/000003025 OPSKATTUZWXXX AT776000000098709849 Herr G"))
+		payment = frappe.get_doc("Payment Entry", dict(party="Mr G", paid_amount=1700))
 		vouchers = json.dumps([{
 		"payment_doctype":"Payment Entry",
 		"payment_name":payment.name,
@@ -62,7 +61,6 @@
 	def test_debit_credit_output(self):
 		bank_transaction = frappe.get_doc("Bank Transaction", dict(description="Auszahlung Karte MC/000002916 AUTOMAT 698769 K002 27.10. 14:07"))
 		linked_payments = get_linked_payments(bank_transaction.name, ['payment_entry', 'exact_match'])
-		print(linked_payments)
 		self.assertTrue(linked_payments[0][3])
 
 	# Check error if already reconciled
@@ -116,10 +114,6 @@
 		pass
 
 def add_transactions():
-	if frappe.flags.test_bank_transactions_created:
-		return
-
-	frappe.set_user("Administrator")
 	create_bank_account()
 
 	doc = frappe.get_doc({
@@ -172,14 +166,8 @@
 	}).insert()
 	doc.submit()
 
-	frappe.flags.test_bank_transactions_created = True
 
 def add_vouchers():
-	if frappe.flags.test_payments_created:
-		return
-
-	frappe.set_user("Administrator")
-
 	try:
 		frappe.get_doc({
 			"doctype": "Supplier",
@@ -272,13 +260,6 @@
 	except frappe.DuplicateEntryError:
 		pass
 
-	si = create_sales_invoice(customer="Fayva", qty=1, rate=109080)
-	pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Bank - _TC")
-	pe.reference_no = "Fayva Oct 18"
-	pe.reference_date = "2018-10-29"
-	pe.insert()
-	pe.submit()
-
 	mode_of_payment = frappe.get_doc({
 		"doctype": "Mode of Payment",
 		"name": "Cash"
@@ -291,14 +272,12 @@
 		})
 		mode_of_payment.save()
 
-	si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_submit=1)
+	si = create_sales_invoice(customer="Fayva", qty=1, rate=109080, do_not_save=1)
 	si.is_pos = 1
 	si.append("payments", {
 		"mode_of_payment": "Cash",
 		"account": "_Test Bank - _TC",
 		"amount": 109080
 	})
-	si.save()
+	si.insert()
 	si.submit()
-
-	frappe.flags.test_payments_created = True
diff --git a/erpnext/accounts/doctype/c_form/c_form.py b/erpnext/accounts/doctype/c_form/c_form.py
index 9b64f81..fd86ed4 100644
--- a/erpnext/accounts/doctype/c_form/c_form.py
+++ b/erpnext/accounts/doctype/c_form/c_form.py
@@ -57,6 +57,7 @@
 		total = sum([flt(d.grand_total) for d in self.get('invoices')])
 		frappe.db.set(self, 'total_invoiced_amount', total)
 
+	@frappe.whitelist()
 	def get_invoice_details(self, invoice_no):
 		"""	Pull details from invoices for referrence """
 		if invoice_no:
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index 03c3eb0..f96f591 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -293,6 +293,11 @@
 	accounts_dict = {}
 	for account in accounts:
 		accounts_dict.setdefault(account["account_name"], account)
+		if not hasattr(account, "parent_account"):
+			msg = _("Please make sure the file you are using has 'Parent Account' column present in the header.")
+			msg += "<br><br>"
+			msg += _("Alternatively, you can download the template and fill your data in.")
+			frappe.throw(msg, title=_("Parent Account Missing"))
 		if account["parent_account"] and accounts_dict.get(account["parent_account"]):
 			accounts_dict[account["parent_account"]]["is_group"] = 1
 
diff --git a/erpnext/accounts/doctype/cost_center/cost_center.py b/erpnext/accounts/doctype/cost_center/cost_center.py
index 12094d4..8a5473f 100644
--- a/erpnext/accounts/doctype/cost_center/cost_center.py
+++ b/erpnext/accounts/doctype/cost_center/cost_center.py
@@ -50,6 +50,7 @@
 				frappe.throw(_("{0} is not a group node. Please select a group node as parent cost center").format(
 					frappe.bold(self.parent_cost_center)))
 
+	@frappe.whitelist()
 	def convert_group_to_ledger(self):
 		if self.check_if_child_exists():
 			frappe.throw(_("Cannot convert Cost Center to ledger as it has child nodes"))
@@ -60,6 +61,7 @@
 			self.save()
 			return 1
 
+	@frappe.whitelist()
 	def convert_ledger_to_group(self):
 		if cint(self.enable_distributed_cost_center):
 			frappe.throw(_("Cost Center with enabled distributed cost center can not be converted to group"))
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index 9594706..c1b8ba7 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -27,6 +27,7 @@
 		if not (self.company and self.posting_date):
 			frappe.throw(_("Please select Company and Posting Date to getting entries"))
 
+	@frappe.whitelist()
 	def get_accounts_data(self, account=None):
 		accounts = []
 		self.validate_mandatory()
@@ -95,6 +96,7 @@
 			message = _("No outstanding invoices found")
 		frappe.msgprint(message)
 
+	@frappe.whitelist()
 	def make_jv_entry(self):
 		if self.total_gain_loss == 0:
 			return
diff --git a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
index da6a3fd..4255626 100644
--- a/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
+++ b/erpnext/accounts/doctype/fiscal_year/fiscal_year.py
@@ -12,6 +12,7 @@
 class FiscalYearIncorrectDate(frappe.ValidationError): pass
 
 class FiscalYear(Document):
+	@frappe.whitelist()
 	def set_as_default(self):
 		frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
 		global_defaults = frappe.get_doc("Global Defaults")
@@ -54,7 +55,7 @@
 	def on_update(self):
 		check_duplicate_fiscal_year(self)
 		frappe.cache().delete_value("fiscal_years")
-	
+
 	def on_trash(self):
 		global_defaults = frappe.get_doc("Global Defaults")
 		if global_defaults.current_fiscal_year == self.name:
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index ce76d0a..78febf9 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -290,4 +290,8 @@
 		oldname = doc.name
 		set_name_from_naming_options(frappe.get_meta(doctype).autoname, doc)
 		newname = doc.name
-		frappe.db.sql("""UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s""".format(doctype), (newname, oldname))
+		frappe.db.sql(
+			"UPDATE `tab{}` SET name = %s, to_rename = 0 where name = %s".format(doctype),
+			(newname, oldname),
+			auto_commit=True
+		)
diff --git a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
index af8940c..7b62b61 100644
--- a/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
+++ b/erpnext/accounts/doctype/invoice_discounting/invoice_discounting.py
@@ -125,6 +125,7 @@
 
 		make_gl_entries(gl_entries, cancel=(self.docstatus == 2), update_outstanding='No')
 
+	@frappe.whitelist()
 	def create_disbursement_entry(self):
 		je = frappe.new_doc("Journal Entry")
 		je.voucher_type = 'Journal Entry'
@@ -174,6 +175,7 @@
 
 		return je
 
+	@frappe.whitelist()
 	def close_loan(self):
 		je = frappe.new_doc("Journal Entry")
 		je.voucher_type = 'Journal Entry'
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index 37b03f3..d76641d 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -327,18 +327,16 @@
 	},
 
 	setup_balance_formatter: function() {
-		var me = this;
-		$.each(["balance", "party_balance"], function(i, field) {
-			var df = frappe.meta.get_docfield("Journal Entry Account", field, me.frm.doc.name);
-			df.formatter = function(value, df, options, doc) {
-				var currency = frappe.meta.get_field_currency(df, doc);
-				var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
-				return "<div style='text-align: right'>"
-					+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
-					+ " " + dr_or_cr
-					+ "</div>";
-			}
-		})
+		const formatter = function(value, df, options, doc) {
+			var currency = frappe.meta.get_field_currency(df, doc);
+			var dr_or_cr = value ? ('<label>' + (value > 0.0 ? __("Dr") : __("Cr")) + '</label>') : "";
+			return "<div style='text-align: right'>"
+				+ ((value==null || value==="") ? "" : format_currency(Math.abs(value), currency))
+				+ " " + dr_or_cr
+				+ "</div>";
+		};
+		this.frm.fields_dict.accounts.grid.update_docfield_property('balance', 'formatter', formatter);
+		this.frm.fields_dict.accounts.grid.update_docfield_property('party_balance', 'formatter', formatter);
 	},
 
 	reference_name: function(doc, cdt, cdn) {
@@ -431,15 +429,6 @@
 	cur_frm.cscript.update_totals(doc);
 }
 
-cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
-	if(doc.select_print_heading){
-		// print heading
-		cur_frm.pformat.print_heading = doc.select_print_heading;
-	}
-	else
-		cur_frm.pformat.print_heading = __("Journal Entry");
-}
-
 frappe.ui.form.on("Journal Entry Account", {
 	party: function(frm, cdt, cdn) {
 		var d = frappe.get_doc(cdt, cdn);
@@ -511,8 +500,11 @@
 		};
 
 		$.each(field_label_map, function (fieldname, label) {
-			var df = frappe.meta.get_docfield("Journal Entry Account", fieldname, frm.doc.name);
-			df.label = frm.doc.multi_currency ? (label + " in Account Currency") : label;
+			frm.fields_dict.accounts.grid.update_docfield_property(
+				fieldname,
+				'label',
+				frm.doc.multi_currency ? (label + " in Account Currency") : label
+			);
 		})
 	},
 
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index 3419bb6..ff2c8c2 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -564,6 +564,7 @@
 		if gl_map:
 			make_gl_entries(gl_map, cancel=cancel, adv_adj=adv_adj, update_outstanding=update_outstanding)
 
+	@frappe.whitelist()
 	def get_balance(self):
 		if not self.get('accounts'):
 			msgprint(_("'Entries' cannot be empty"), raise_exception=True)
diff --git a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
index 18f853c..88667d7 100644
--- a/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
+++ b/erpnext/accounts/doctype/monthly_distribution/monthly_distribution.py
@@ -8,6 +8,7 @@
 from frappe.model.document import Document
 
 class MonthlyDistribution(Document):
+	@frappe.whitelist()
 	def get_months(self):
 		month_list = ['January','February','March','April','May','June','July','August','September',
 		'October','November','December']
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
index e6449b7..29dc96e 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/opening_invoice_creation_tool.py
@@ -167,6 +167,7 @@
 
 		return invoice
 
+	@frappe.whitelist()
 	def make_invoices(self):
 		self.validate_company()
 		invoices = self.get_invoices()
diff --git a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
index bdfe532..8d6de2d 100644
--- a/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
+++ b/erpnext/accounts/doctype/opening_invoice_creation_tool/test_opening_invoice_creation_tool.py
@@ -6,10 +6,12 @@
 import frappe
 import unittest
 
-test_dependencies = ["Customer", "Supplier"]
+from frappe.cache_manager import clear_doctype_cache
 from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from erpnext.accounts.doctype.opening_invoice_creation_tool.opening_invoice_creation_tool import get_temporary_opening_account
 
+test_dependencies = ["Customer", "Supplier"]
+
 class TestOpeningInvoiceCreationTool(unittest.TestCase):
 	def setUp(self):
 		if not frappe.db.exists("Company", "_Test Opening Invoice Company"):
@@ -24,22 +26,25 @@
 
 	def test_opening_sales_invoice_creation(self):
 		property_setter = make_property_setter("Sales Invoice", "update_stock", "default", 1, "Check")
-		invoices = self.make_invoices(company="_Test Opening Invoice Company")
+		try:
+			invoices = self.make_invoices(company="_Test Opening Invoice Company")
 
-		self.assertEqual(len(invoices), 2)
-		expected_value = {
-			"keys": ["customer", "outstanding_amount", "status"],
-			0: ["_Test Customer", 300, "Overdue"],
-			1: ["_Test Customer 1", 250, "Overdue"],
-		}
-		self.check_expected_values(invoices, expected_value)
+			self.assertEqual(len(invoices), 2)
+			expected_value = {
+				"keys": ["customer", "outstanding_amount", "status"],
+				0: ["_Test Customer", 300, "Overdue"],
+				1: ["_Test Customer 1", 250, "Overdue"],
+			}
+			self.check_expected_values(invoices, expected_value)
 
-		si = frappe.get_doc("Sales Invoice", invoices[0])
+			si = frappe.get_doc("Sales Invoice", invoices[0])
 
-		# Check if update stock is not enabled
-		self.assertEqual(si.update_stock, 0)
+			# Check if update stock is not enabled
+			self.assertEqual(si.update_stock, 0)
 
-		property_setter.delete()
+		finally:
+			property_setter.delete()
+			clear_doctype_cache("Sales Invoice")
 
 	def check_expected_values(self, invoices, expected_value, invoice_type="Sales"):
 		doctype = "Sales Invoice" if invoice_type == "Sales" else "Purchase Invoice"
@@ -143,4 +148,4 @@
 		customer.insert(ignore_permissions=True)
 		return customer.name
 	else:
-		return frappe.db.exists("Customer", customer_name)
\ No newline at end of file
+		return frappe.db.exists("Customer", customer_name)
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 6412772..c2e804e 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -605,12 +605,22 @@
 			{fieldtype:"Column Break"},
 			{fieldtype:"Float", label: __("Less Than Amount"), fieldname:"outstanding_amt_less_than"},
 			{fieldtype:"Section Break"},
+			{fieldtype:"Link", label:__("Cost Center"), fieldname:"cost_center", options:"Cost Center",
+                "get_query": function() {
+                    return {
+                        "filters": {"company": frm.doc.company}
+					}
+				}
+			},
+			{fieldtype:"Column Break"},
+			{fieldtype:"Section Break"},
 			{fieldtype:"Check", label: __("Allocate Payment Amount"), fieldname:"allocate_payment_amount", default:1},
 		];
 
 		frappe.prompt(fields, function(filters){
 			frappe.flags.allocate_payment_amount = true;
 			frm.events.validate_filters_data(frm, filters);
+			frm.doc.cost_center = filters.cost_center;
 			frm.events.get_outstanding_documents(frm, filters);
 		}, __("Filters"), __("Get Outstanding Documents"));
 	},
@@ -627,13 +637,13 @@
 			let to_field = fields[key][1];
 
 			if (filters[from_field] && !filters[to_field]) {
-				frappe.throw(__("Error: {0} is mandatory field",
-					[to_field.replace(/_/g, " ")]
-				));
+				frappe.throw(
+					__("Error: {0} is mandatory field", [to_field.replace(/_/g, " ")])
+				);
 			} else if (filters[from_field] && filters[from_field] > filters[to_field]) {
-				frappe.throw(__("{0}: {1} must be less than {2}",
-					[key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")]
-				));
+				frappe.throw(
+					__("{0}: {1} must be less than {2}", [key, from_field.replace(/_/g, " "), to_field.replace(/_/g, " ")])
+				);
 			}
 		}
 	},
@@ -682,6 +692,8 @@
 						c.total_amount = d.invoice_amount;
 						c.outstanding_amount = d.outstanding_amount;
 						c.bill_no = d.bill_no;
+						c.payment_term = d.payment_term;
+						c.allocated_amount = d.allocated_amount;
 
 						if(!in_list(["Sales Order", "Purchase Order", "Expense Claim", "Fees"], d.voucher_type)) {
 							if(flt(d.outstanding_amount) > 0)
@@ -764,12 +776,15 @@
 		} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
 			if(paid_amount > total_negative_outstanding) {
 				if(total_negative_outstanding == 0) {
-					frappe.msgprint(__("Cannot {0} {1} {2} without any negative outstanding invoice",
-						[frm.doc.payment_type,
-							(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type]));
+					frappe.msgprint(
+						__("Cannot {0} {1} {2} without any negative outstanding invoice", [frm.doc.payment_type,
+							(frm.doc.party_type=="Customer" ? "to" : "from"), frm.doc.party_type])
+					);
 					return false
 				} else {
-					frappe.msgprint(__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding]));
+					frappe.msgprint(
+						__("Paid Amount cannot be greater than total negative outstanding amount {0}", [total_negative_outstanding])
+					);
 					return false;
 				}
 			} else {
@@ -781,10 +796,13 @@
 		}
 
 		$.each(frm.doc.references || [], function(i, row) {
-			row.allocated_amount = 0 //If allocate payment amount checkbox is unchecked, set zero to allocate amount
-			if(frappe.flags.allocate_payment_amount != 0){
-				if(row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
-					if(row.outstanding_amount >= allocated_positive_outstanding) {
+			if (frappe.flags.allocate_payment_amount == 0) {
+				//If allocate payment amount checkbox is unchecked, set zero to allocate amount
+				row.allocated_amount = 0;
+
+			} else if (frappe.flags.allocate_payment_amount != 0 && !row.allocated_amount) {
+				if (row.outstanding_amount > 0 && allocated_positive_outstanding > 0) {
+					if (row.outstanding_amount >= allocated_positive_outstanding) {
 						row.allocated_amount = allocated_positive_outstanding;
 					} else {
 						row.allocated_amount = row.outstanding_amount;
@@ -792,9 +810,11 @@
 
 					allocated_positive_outstanding -= flt(row.allocated_amount);
 				} else if (row.outstanding_amount < 0 && allocated_negative_outstanding) {
-					if(Math.abs(row.outstanding_amount) >= allocated_negative_outstanding)
+					if (Math.abs(row.outstanding_amount) >= allocated_negative_outstanding) {
 						row.allocated_amount = -1*allocated_negative_outstanding;
-					else row.allocated_amount = row.outstanding_amount;
+					} else {
+						row.allocated_amount = row.outstanding_amount;
+					};
 
 					allocated_negative_outstanding -= Math.abs(flt(row.allocated_amount));
 				}
@@ -1066,11 +1086,6 @@
 								frm.set_value("paid_from_account_balance", r.message.paid_from_account_balance);
 								frm.set_value("paid_to_account_balance", r.message.paid_to_account_balance);
 								frm.set_value("party_balance", r.message.party_balance);
-							},
-							() => {
-								if(frm.doc.payment_type != "Internal") {
-									frm.clear_table("references");
-								}
 							}
 						]);
 
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 8acd92c..62ab76c 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -333,33 +333,50 @@
 		invoice_payment_amount_map = {}
 		invoice_paid_amount_map = {}
 
-		for reference in self.get('references'):
-			if reference.payment_term and reference.reference_name:
-				key = (reference.payment_term, reference.reference_name)
+		for ref in self.get('references'):
+			if ref.payment_term and ref.reference_name:
+				key = (ref.payment_term, ref.reference_name)
 				invoice_payment_amount_map.setdefault(key, 0.0)
-				invoice_payment_amount_map[key] += reference.allocated_amount
+				invoice_payment_amount_map[key] += ref.allocated_amount
 
 				if not invoice_paid_amount_map.get(key):
-					payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': reference.reference_name},
-						fields=['paid_amount', 'payment_amount', 'payment_term'])
+					payment_schedule = frappe.get_all(
+						'Payment Schedule',
+						filters={'parent': ref.reference_name},
+						fields=['paid_amount', 'payment_amount', 'payment_term', 'discount', 'outstanding']
+					)
 					for term in payment_schedule:
-						invoice_key = (term.payment_term, reference.reference_name)
+						invoice_key = (term.payment_term, ref.reference_name)
 						invoice_paid_amount_map.setdefault(invoice_key, {})
-						invoice_paid_amount_map[invoice_key]['outstanding'] = term.payment_amount - term.paid_amount
+						invoice_paid_amount_map[invoice_key]['outstanding'] = term.outstanding
+						invoice_paid_amount_map[invoice_key]['discounted_amt'] = ref.total_amount * (term.discount / 100)
 
-		for key, amount in iteritems(invoice_payment_amount_map):
+		for key, allocated_amount in iteritems(invoice_payment_amount_map):
+			outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
+			discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get('discounted_amt'))
+
 			if cancel:
-				frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` - %s
-					WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
+				frappe.db.sql("""
+					UPDATE `tabPayment Schedule`
+					SET
+						paid_amount = `paid_amount` - %s,
+						discounted_amount = `discounted_amount` - %s,
+						outstanding = `outstanding` + %s
+					WHERE parent = %s and payment_term = %s""",
+					(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
 			else:
-				outstanding = flt(invoice_paid_amount_map.get(key, {}).get('outstanding'))
-
-				if amount > outstanding:
+				if allocated_amount > outstanding:
 					frappe.throw(_('Cannot allocate more than {0} against payment term {1}').format(outstanding, key[0]))
 
-				if amount and outstanding:
-					frappe.db.sql(""" UPDATE `tabPayment Schedule` SET paid_amount = `paid_amount` + %s
-							WHERE parent = %s and payment_term = %s""", (amount, key[1], key[0]))
+				if allocated_amount and outstanding:
+					frappe.db.sql("""
+						UPDATE `tabPayment Schedule`
+						SET
+							paid_amount = `paid_amount` + %s,
+							discounted_amount = `discounted_amount` + %s,
+							outstanding = `outstanding` - %s
+						WHERE parent = %s and payment_term = %s""",
+					(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]))
 
 	def set_status(self):
 		if self.docstatus == 2:
@@ -708,6 +725,8 @@
 	outstanding_invoices = get_outstanding_invoices(args.get("party_type"), args.get("party"),
 		args.get("party_account"), filters=args, condition=condition)
 
+	outstanding_invoices = split_invoices_based_on_payment_terms(outstanding_invoices)
+
 	for d in outstanding_invoices:
 		d["exchange_rate"] = 1
 		if party_account_currency != company_currency:
@@ -735,6 +754,46 @@
 	return data
 
 
+def split_invoices_based_on_payment_terms(outstanding_invoices):
+	invoice_ref_based_on_payment_terms = {}
+	for idx, d in enumerate(outstanding_invoices):
+		if d.voucher_type in ['Sales Invoice', 'Purchase Invoice']:
+			payment_term_template = frappe.db.get_value(d.voucher_type, d.voucher_no, 'payment_terms_template')
+			if payment_term_template:
+				allocate_payment_based_on_payment_terms = frappe.db.get_value(
+					'Payment Terms Template', payment_term_template, 'allocate_payment_based_on_payment_terms')
+				if allocate_payment_based_on_payment_terms:
+					payment_schedule = frappe.get_all('Payment Schedule', filters={'parent': d.voucher_no}, fields=["*"])
+
+					for payment_term in payment_schedule:
+						if payment_term.outstanding > 0.1:
+							invoice_ref_based_on_payment_terms.setdefault(idx, [])
+							invoice_ref_based_on_payment_terms[idx].append(frappe._dict({
+								'due_date': d.due_date,
+								'currency': d.currency,
+								'voucher_no': d.voucher_no,
+								'voucher_type': d.voucher_type,
+								'posting_date': d.posting_date,
+								'invoice_amount': flt(d.invoice_amount),
+								'outstanding_amount': flt(d.outstanding_amount),
+								'payment_amount': payment_term.payment_amount,
+								'payment_term': payment_term.payment_term,
+								'allocated_amount': payment_term.outstanding
+							}))
+
+	if invoice_ref_based_on_payment_terms:
+		for idx, ref in invoice_ref_based_on_payment_terms.items():
+			voucher_no = outstanding_invoices[idx]['voucher_no']
+			voucher_type = outstanding_invoices[idx]['voucher_type']
+
+			frappe.msgprint(_("Spliting {} {} into {} rows as per payment terms").format(
+				voucher_type, voucher_no, len(ref)), alert=True)
+
+			outstanding_invoices.pop(idx - 1)
+			outstanding_invoices += invoice_ref_based_on_payment_terms[idx]
+	
+	return outstanding_invoices
+
 def get_orders_to_be_billed(posting_date, party_type, party,
 	company, party_account_currency, company_currency, cost_center=None, filters=None):
 	if party_type == "Customer":
@@ -1091,6 +1150,8 @@
 	paid_amount, received_amount = set_paid_amount_and_received_amount(
 		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc)
 
+	paid_amount, received_amount, discount_amount = apply_early_payment_discount(paid_amount, received_amount, doc)
+
 	pe = frappe.new_doc("Payment Entry")
 	pe.payment_type = payment_type
 	pe.company = doc.company
@@ -1160,11 +1221,20 @@
 
 	pe.setup_party_account_field()
 	pe.set_missing_values()
+
 	if party_account and bank:
 		if dt == "Employee Advance":
 			reference_doc = doc
 		pe.set_exchange_rate(ref_doc=reference_doc)
 		pe.set_amounts()
+		if discount_amount:
+			pe.set_gain_or_loss(account_details={
+				'account': frappe.get_cached_value('Company', pe.company, "default_discount_account"),
+				'cost_center': pe.cost_center or frappe.get_cached_value('Company', pe.company, "cost_center"),
+				'amount': discount_amount * (-1 if payment_type == "Pay" else 1)
+			})
+			pe.set_difference_amount()
+
 	return pe
 
 def get_bank_cash_account(doc, bank_account):
@@ -1285,6 +1355,33 @@
 				paid_amount = received_amount * doc.get('exchange_rate', 1)
 	return paid_amount, received_amount
 
+def apply_early_payment_discount(paid_amount, received_amount, doc):
+	total_discount = 0
+	if doc.doctype in ['Sales Invoice', 'Purchase Invoice'] and doc.payment_schedule:
+		for term in doc.payment_schedule:
+			if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
+				if term.discount_type == 'Percentage':
+					discount_amount = flt(doc.get('grand_total')) * (term.discount / 100)
+				else:
+					discount_amount = term.discount
+
+				discount_amount_in_foreign_currency = discount_amount * doc.get('conversion_rate', 1)
+
+				if doc.doctype == 'Sales Invoice':
+					paid_amount -= discount_amount
+					received_amount -= discount_amount_in_foreign_currency
+				else:
+					received_amount -= discount_amount
+					paid_amount -= discount_amount_in_foreign_currency
+
+				total_discount += discount_amount
+
+		if total_discount:
+			money = frappe.utils.fmt_money(total_discount, currency=doc.get('currency'))
+			frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
+
+	return paid_amount, received_amount, total_discount
+
 def get_reference_as_per_payment_terms(payment_schedule, dt, dn, doc, grand_total, outstanding_amount):
 	references = []
 	for payment_term in payment_schedule:
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 772fc1a..4641d6b 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -193,6 +193,34 @@
 		self.assertEqual(si.payment_schedule[0].paid_amount, 200.0)
 		self.assertEqual(si.payment_schedule[1].paid_amount, 36.0)
 
+	def test_payment_entry_against_payment_terms_with_discount(self):
+		si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
+		create_payment_terms_template_with_discount()
+		si.payment_terms_template = 'Test Discount Template'
+
+		frappe.db.set_value('Company', si.company, 'default_discount_account', 'Write Off - _TC')
+
+		si.append('taxes', {
+			"charge_type": "On Net Total",
+			"account_head": "_Test Account Service Tax - _TC",
+			"cost_center": "_Test Cost Center - _TC",
+			"description": "Service Tax",
+			"rate": 18
+		})
+		si.save()
+
+		si.submit()
+
+		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+		pe.submit()
+		si.load_from_db()
+
+		self.assertEqual(pe.references[0].payment_term, '30 Credit Days with 10% Discount')
+		self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
+		self.assertEqual(si.payment_schedule[0].paid_amount, 212.40)
+		self.assertEqual(si.payment_schedule[0].outstanding, 0)
+		self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
+
 
 	def test_payment_against_purchase_invoice_to_check_status(self):
 		pi = make_purchase_invoice(supplier="_Test Supplier USD", debit_to="_Test Payable USD - _TC",
@@ -591,6 +619,26 @@
 			}]
 		}).insert()
 
+def create_payment_terms_template_with_discount():
+
+	create_payment_term('30 Credit Days with 10% Discount')
+
+	if not frappe.db.exists('Payment Terms Template', 'Test Discount Template'):
+		payment_term_template = frappe.get_doc({
+			'doctype': 'Payment Terms Template',
+			'template_name': 'Test Discount Template',
+			'allocate_payment_based_on_payment_terms': 1,
+			'terms': [{
+				'doctype': 'Payment Terms Template Detail',
+				'payment_term': '30 Credit Days with 10% Discount',
+				'invoice_portion': 100,
+				'credit_days_based_on': 'Day(s) after invoice date',
+				'credit_days': 2,
+				'discount': 10,
+				'discount_validity_based_on': 'Day(s) after invoice date',
+				'discount_validity': 1
+			}]
+		}).insert()
 
 def create_payment_term(name):
 	if not frappe.db.exists('Payment Term', name):
diff --git a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
index 8f5e9fb..912ad09 100644
--- a/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
+++ b/erpnext/accounts/doctype/payment_entry_reference/payment_entry_reference.json
@@ -58,7 +58,7 @@
    "fieldname": "total_amount",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Total Amount",
+   "label": "Grand Total",
    "print_hide": 1,
    "read_only": 1
   },
@@ -92,9 +92,10 @@
    "options": "Payment Term"
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-03-13 12:07:19.362539",
+ "modified": "2021-02-10 11:25:47.144392",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry Reference",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 6b07197..08103184 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -234,8 +234,9 @@
 		});
 
 		if (invoices) {
-			frappe.meta.get_docfield("Payment Reconciliation Payment", "invoice_number",
-				me.frm.doc.name).options = "\n" + invoices.join("\n");
+			this.frm.fields_dict.payment.grid.update_docfield_property(
+				'invoice_number', 'options', "\n" + invoices.join("\n")
+			);
 
 			$.each(me.frm.doc.payments || [], function(i, p) {
 				if(!in_list(invoices, cstr(p.invoice_number))) p.invoice_number = null;
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index f7a15c0..cf6ec18 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -11,6 +11,7 @@
 from erpnext.controllers.accounts_controller import get_advance_payment_entries
 
 class PaymentReconciliation(Document):
+	@frappe.whitelist()
 	def get_unreconciled_entries(self):
 		self.get_nonreconciled_payment_entries()
 		self.get_invoice_entries()
@@ -147,6 +148,7 @@
 			ent.currency = e.get('currency')
 			ent.outstanding_amount = e.get('outstanding_amount')
 
+	@frappe.whitelist()
 	def reconcile(self, args):
 		for e in self.get('payments'):
 			e.invoice_type = None
@@ -197,6 +199,7 @@
 			'difference_account': row.difference_account
 		})
 
+	@frappe.whitelist()
 	def get_difference_amount(self, child_row):
 		if child_row.get("reference_type") != 'Payment Entry': return
 
diff --git a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
index d363cf1..e362566 100644
--- a/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
+++ b/erpnext/accounts/doctype/payment_schedule/payment_schedule.json
@@ -6,11 +6,23 @@
  "engine": "InnoDB",
  "field_order": [
   "payment_term",
+  "section_break_15",
   "description",
+  "section_break_4",
   "due_date",
-  "invoice_portion",
-  "payment_amount",
   "mode_of_payment",
+  "column_break_5",
+  "invoice_portion",
+  "section_break_6",
+  "discount_type",
+  "discount_date",
+  "column_break_9",
+  "discount",
+  "section_break_9",
+  "payment_amount",
+  "discounted_amount",
+  "column_break_3",
+  "outstanding",
   "paid_amount"
  ],
  "fields": [
@@ -25,6 +37,7 @@
   },
   {
    "columns": 2,
+   "fetch_from": "payment_term.description",
    "fieldname": "description",
    "fieldtype": "Small Text",
    "in_list_view": 1,
@@ -62,14 +75,82 @@
    "options": "Mode of Payment"
   },
   {
+   "depends_on": "paid_amount",
    "fieldname": "paid_amount",
    "fieldtype": "Currency",
    "label": "Paid Amount"
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "depends_on": "discounted_amount",
+   "fieldname": "discounted_amount",
+   "fieldtype": "Currency",
+   "label": "Discounted Amount",
+   "read_only": 1
+  },
+  {
+   "fetch_from": "payment_amount",
+   "fieldname": "outstanding",
+   "fieldtype": "Currency",
+   "label": "Outstanding",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_5",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "discount",
+   "fieldname": "discount_date",
+   "fieldtype": "Date",
+   "label": "Discount Date",
+   "mandatory_depends_on": "discount"
+  },
+  {
+   "default": "Percentage",
+   "fetch_from": "payment_term.discount_type",
+   "fieldname": "discount_type",
+   "fieldtype": "Select",
+   "label": "Discount Type",
+   "options": "Percentage\nAmount"
+  },
+  {
+   "fetch_from": "payment_term.discount",
+   "fieldname": "discount",
+   "fieldtype": "Float",
+   "label": "Discount"
+  },
+  {
+   "fieldname": "section_break_9",
+   "fieldtype": "Section Break"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "section_break_15",
+   "fieldtype": "Section Break",
+   "label": "Description"
+  },
+  {
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "column_break_9",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break"
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-03-13 17:58:24.729526",
+ "modified": "2021-02-15 21:03:12.540546",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Schedule",
diff --git a/erpnext/accounts/doctype/payment_term/payment_term.js b/erpnext/accounts/doctype/payment_term/payment_term.js
index 054c2d1..acd0144 100644
--- a/erpnext/accounts/doctype/payment_term/payment_term.js
+++ b/erpnext/accounts/doctype/payment_term/payment_term.js
@@ -1,2 +1,22 @@
 // Copyright (c) 2017, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
+frappe.ui.form.on('Payment Term', {
+	onload(frm) {
+		frm.trigger('set_dynamic_description');
+	},
+	discount(frm) {
+		frm.trigger('set_dynamic_description');
+	},
+	discount_type(frm) {
+		frm.trigger('set_dynamic_description');
+	},
+	set_dynamic_description(frm) {
+		if (frm.doc.discount) {
+			let description = __("{0}% of total invoice value will be given as discount.", [frm.doc.discount]);
+			if (frm.doc.discount_type == 'Amount') {
+				description = __("{0} will be given as discount.", [fmt_money(frm.doc.discount)]);
+			}
+			frm.set_df_property("discount", "description", description);
+		}
+	}
+});
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_term/payment_term.json b/erpnext/accounts/doctype/payment_term/payment_term.json
index e77c244..aec4965 100644
--- a/erpnext/accounts/doctype/payment_term/payment_term.json
+++ b/erpnext/accounts/doctype/payment_term/payment_term.json
@@ -1,386 +1,166 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 1, 
- "allow_rename": 1, 
- "autoname": "field:payment_term_name", 
- "beta": 0, 
- "creation": "2017-08-10 15:24:54.876365", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "allow_import": 1,
+ "allow_rename": 1,
+ "autoname": "field:payment_term_name",
+ "creation": "2017-08-10 15:24:54.876365",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "payment_term_name",
+  "invoice_portion",
+  "mode_of_payment",
+  "column_break_3",
+  "due_date_based_on",
+  "credit_days",
+  "credit_months",
+  "section_break_8",
+  "discount_type",
+  "discount",
+  "column_break_11",
+  "discount_validity_based_on",
+  "discount_validity",
+  "section_break_6",
+  "description"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 1, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "payment_term_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Payment Term Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "bold": 1,
+   "fieldname": "payment_term_name",
+   "fieldtype": "Data",
+   "label": "Payment Term Name",
+   "unique": 1
+  },
   {
-   "description": "Provide the invoice portion in percent",
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 1, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "invoice_portion", 
-   "fieldtype": "Float", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Invoice Portion", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "bold": 1,
+   "fieldname": "invoice_portion",
+   "fieldtype": "Float",
+   "label": "Invoice Portion (%)"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "mode_of_payment", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Mode of Payment", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Mode of Payment", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "mode_of_payment",
+   "fieldtype": "Link",
+   "label": "Mode of Payment",
+   "options": "Mode of Payment"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_3", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 1, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "due_date_based_on", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Due Date Based On", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "bold": 1,
+   "fieldname": "due_date_based_on",
+   "fieldtype": "Select",
+   "label": "Due Date Based On",
+   "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
+  },
   {
-   "description": "Give number of days according to prior selection",
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 1, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", 
-   "fieldname": "credit_days", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Credit Days", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "bold": 1,
+   "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
+   "fieldname": "credit_days",
+   "fieldtype": "Int",
+   "label": "Credit Days"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", 
-   "fieldname": "credit_months", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Credit Months", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
+   "fieldname": "credit_months",
+   "fieldtype": "Int",
+   "label": "Credit Months"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_6", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "fieldname": "section_break_6",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 1, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "description", 
-   "fieldtype": "Small Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
+   "bold": 1,
+   "fieldname": "description",
+   "fieldtype": "Small Text",
+   "label": "Description"
+  },
+  {
+   "fieldname": "section_break_8",
+   "fieldtype": "Section Break",
+   "label": "Discount Settings"
+  },
+  {
+   "default": "Percentage",
+   "fieldname": "discount_type",
+   "fieldtype": "Select",
+   "label": "Discount Type",
+   "options": "Percentage\nAmount"
+  },
+  {
+   "fieldname": "discount",
+   "fieldtype": "Float",
+   "label": "Discount"
+  },
+  {
+   "default": "Day(s) after invoice date",
+   "depends_on": "discount",
+   "fieldname": "discount_validity_based_on",
+   "fieldtype": "Select",
+   "label": "Discount Validity Based On",
+   "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
+  },
+  {
+   "depends_on": "discount",
+   "fieldname": "discount_validity",
+   "fieldtype": "Int",
+   "label": "Discount Validity",
+   "mandatory_depends_on": "discount"
+  },
+  {
+   "fieldname": "column_break_11",
+   "fieldtype": "Column Break"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2020-10-14 10:47:32.830478", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Payment Term", 
- "name_case": "", 
- "owner": "Administrator", 
+ ],
+ "links": [],
+ "modified": "2021-02-15 20:30:56.256403",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Term",
+ "owner": "Administrator",
  "permissions": [
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "System Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Accounts Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager",
+   "share": 1,
    "write": 1
-  }, 
+  },
   {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Accounts User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User",
+   "share": 1,
    "write": 1
   }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0
-}
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js
index f5c5bca..84c8d09 100644
--- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js
+++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.js
@@ -3,11 +3,6 @@
 
 frappe.ui.form.on('Payment Terms Template', {
 	setup: function(frm) {
-		frm.add_fetch("payment_term", "description", "description");
-		frm.add_fetch("payment_term", "invoice_portion", "invoice_portion");
-		frm.add_fetch("payment_term", "due_date_based_on", "due_date_based_on");
-		frm.add_fetch("payment_term", "credit_days", "credit_days");
-		frm.add_fetch("payment_term", "credit_months", "credit_months");
-		frm.add_fetch("payment_term", "mode_of_payment", "mode_of_payment");
+		
 	}
 });
diff --git a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
index 2b2b6af..80e3348 100644
--- a/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
+++ b/erpnext/accounts/doctype/payment_terms_template/payment_terms_template.py
@@ -13,7 +13,6 @@
 class PaymentTermsTemplate(Document):
 	def validate(self):
 		self.validate_invoice_portion()
-		self.validate_credit_days()
 		self.check_duplicate_terms()
 
 	def validate_invoice_portion(self):
@@ -24,11 +23,6 @@
 		if flt(total_portion, 2) != 100.00:
 			frappe.msgprint(_('Combined invoice portion must equal 100%'), raise_exception=1, indicator='red')
 
-	def validate_credit_days(self):
-		for term in self.terms:
-			if cint(term.credit_days) < 0:
-				frappe.msgprint(_('Credit Days cannot be a negative number'), raise_exception=1, indicator='red')
-
 	def check_duplicate_terms(self):
 		terms = []
 		for term in self.terms:
diff --git a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json
index eee3223..20b3dca 100644
--- a/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json
+++ b/erpnext/accounts/doctype/payment_terms_template_detail/payment_terms_template_detail.json
@@ -1,278 +1,164 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "", 
- "beta": 0, 
- "creation": "2017-08-10 15:34:09.409562", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2017-08-10 15:34:09.409562",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "payment_term",
+  "section_break_13",
+  "description",
+  "section_break_4",
+  "invoice_portion",
+  "mode_of_payment",
+  "column_break_3",
+  "due_date_based_on",
+  "credit_days",
+  "credit_months",
+  "section_break_8",
+  "discount_type",
+  "discount",
+  "column_break_11",
+  "discount_validity_based_on",
+  "discount_validity"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "fieldname": "payment_term", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Payment Term", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Payment Term", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "columns": 2,
+   "fieldname": "payment_term",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Payment Term",
+   "options": "Payment Term"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "fieldname": "description", 
-   "fieldtype": "Small Text", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "columns": 2,
+   "fetch_from": "payment_term.description",
+   "fieldname": "description",
+   "fieldtype": "Small Text",
+   "in_list_view": 1,
+   "label": "Description"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "default": "0", 
-   "fieldname": "invoice_portion", 
-   "fieldtype": "Percent", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Invoice Portion", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "columns": 2,
+   "fetch_from": "payment_term.invoice_portion",
+   "fetch_if_empty": 1,
+   "fieldname": "invoice_portion",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Invoice Portion (%)",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "fieldname": "due_date_based_on", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Due Date Based On", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "columns": 2,
+   "fetch_from": "payment_term.due_date_based_on",
+   "fetch_if_empty": 1,
+   "fieldname": "due_date_based_on",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Due Date Based On",
+   "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 2, 
-   "default": "0", 
-   "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)", 
-   "fieldname": "credit_days", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Credit Days", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "columns": 2,
+   "default": "0",
+   "depends_on": "eval:in_list(['Day(s) after invoice date', 'Day(s) after the end of the invoice month'], doc.due_date_based_on)",
+   "fetch_from": "payment_term.credit_days",
+   "fetch_if_empty": 1,
+   "fieldname": "credit_days",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "Credit Days",
+   "non_negative": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "0", 
-   "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'", 
-   "fieldname": "credit_months", 
-   "fieldtype": "Int", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Credit Months", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
-  }, 
+   "default": "0",
+   "depends_on": "eval:doc.due_date_based_on=='Month(s) after the end of the invoice month'",
+   "fetch_from": "payment_term.credit_months",
+   "fetch_if_empty": 1,
+   "fieldname": "credit_months",
+   "fieldtype": "Int",
+   "label": "Credit Months",
+   "non_negative": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_in_quick_entry": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "mode_of_payment", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Mode of Payment", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Mode of Payment", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0, 
-   "unique": 0
+   "fetch_from": "payment_term.mode_of_payment",
+   "fieldname": "mode_of_payment",
+   "fieldtype": "Link",
+   "label": "Mode of Payment",
+   "options": "Mode of Payment"
+  },
+  {
+   "fieldname": "column_break_3",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_8",
+   "fieldtype": "Section Break",
+   "label": "Discount Settings"
+  },
+  {
+   "default": "Percentage",
+   "fetch_from": "payment_term.discount_type",
+   "fetch_if_empty": 1,
+   "fieldname": "discount_type",
+   "fieldtype": "Select",
+   "label": "Discount Type",
+   "options": "Percentage\nAmount"
+  },
+  {
+   "fetch_from": "payment_term.discount",
+   "fetch_if_empty": 1,
+   "fieldname": "discount",
+   "fieldtype": "Float",
+   "label": "Discount"
+  },
+  {
+   "fieldname": "column_break_11",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "Day(s) after invoice date",
+   "depends_on": "discount",
+   "fetch_from": "payment_term.discount_validity_based_on",
+   "fetch_if_empty": 1,
+   "fieldname": "discount_validity_based_on",
+   "fieldtype": "Select",
+   "label": "Discount Validity Based On",
+   "options": "Day(s) after invoice date\nDay(s) after the end of the invoice month\nMonth(s) after the end of the invoice month"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "section_break_13",
+   "fieldtype": "Section Break",
+   "label": "Description"
+  },
+  {
+   "depends_on": "discount",
+   "fetch_from": "payment_term.discount_validity",
+   "fetch_if_empty": 1,
+   "fieldname": "discount_validity",
+   "fieldtype": "Int",
+   "label": "Discount Validity",
+   "mandatory_depends_on": "discount"
+  },
+  {
+   "fieldname": "section_break_4",
+   "fieldtype": "Section Break"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-08-21 16:15:55.143025", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Payment Terms Template Detail", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 1, 
- "track_seen": 0, 
- "track_views": 0
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2021-02-24 11:56:12.410807",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Payment Terms Template Detail",
+ "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/accounts/doctype/pos_closing_entry/pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
index f5224a2..a05e598 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/pos_closing_entry.py
@@ -18,7 +18,7 @@
 
 		self.validate_pos_closing()
 		self.validate_pos_invoices()
-	
+
 	def validate_pos_closing(self):
 		user = frappe.db.sql("""
 			SELECT name FROM `tabPOS Closing Entry`
@@ -37,12 +37,12 @@
 			bold_user = frappe.bold(self.user)
 			frappe.throw(_("POS Closing Entry {} against {} between selected period")
 				.format(bold_already_exists, bold_user), title=_("Invalid Period"))
-	
+
 	def validate_pos_invoices(self):
 		invalid_rows = []
 		for d in self.pos_transactions:
 			invalid_row = {'idx': d.idx}
-			pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice, 
+			pos_invoice = frappe.db.get_values("POS Invoice", d.pos_invoice,
 				["consolidated_invoice", "pos_profile", "docstatus", "owner"], as_dict=1)[0]
 			if pos_invoice.consolidated_invoice:
 				invalid_row.setdefault('msg', []).append(_('POS Invoice is {}').format(frappe.bold("already consolidated")))
@@ -68,14 +68,15 @@
 
 		frappe.throw(error_list, title=_("Invalid POS Invoices"), as_list=True)
 
+	@frappe.whitelist()
 	def get_payment_reconciliation_details(self):
 		currency = frappe.get_cached_value('Company', self.company,  "default_currency")
 		return frappe.render_template("erpnext/accounts/doctype/pos_closing_entry/closing_voucher_details.html",
 			{"data": self, "currency": currency})
-	
+
 	def on_submit(self):
 		consolidate_pos_invoices(closing_entry=self)
-	
+
 	def on_cancel(self):
 		unconsolidate_pos_invoices(closing_entry=self)
 
diff --git a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
index 40db09e..b596c0c 100644
--- a/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
+++ b/erpnext/accounts/doctype/pos_closing_entry/test_pos_closing_entry.py
@@ -5,12 +5,21 @@
 import frappe
 import unittest
 from frappe.utils import nowdate
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
 from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
 from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import make_closing_entry_from_opening
 from erpnext.accounts.doctype.pos_opening_entry.test_pos_opening_entry import create_opening_entry
 from erpnext.accounts.doctype.pos_profile.test_pos_profile import make_pos_profile
 
 class TestPOSClosingEntry(unittest.TestCase):
+	def setUp(self):
+		# Make stock available for POS Sales
+		make_stock_entry(target="_Test Warehouse - _TC", qty=2, basic_rate=100)
+
+	def tearDown(self):
+		frappe.set_user("Administrator")
+		frappe.db.sql("delete from `tabPOS Profile`")
+
 	def test_pos_closing_entry(self):
 		test_user, pos_profile = init_user_and_profile()
 		opening_entry = create_opening_entry(pos_profile, test_user.name)
@@ -41,9 +50,6 @@
 		self.assertEqual(pcv_doc.total_quantity, 2)
 		self.assertEqual(pcv_doc.net_total, 6700)
 
-		frappe.set_user("Administrator")
-		frappe.db.sql("delete from `tabPOS Profile`")
-
 	def test_cancelling_of_pos_closing_entry(self):
 		test_user, pos_profile = init_user_and_profile()
 		opening_entry = create_opening_entry(pos_profile, test_user.name)
@@ -84,8 +90,6 @@
 		self.assertEqual(si_doc.docstatus, 2)
 		self.assertEqual(pos_inv1.status, 'Paid')
 
-		frappe.set_user("Administrator")
-		frappe.db.sql("delete from `tabPOS Profile`")
 
 def init_user_and_profile(**args):
 	user = 'test@example.com'
@@ -103,4 +107,4 @@
 
 	pos_profile.save()
 
-	return test_user, pos_profile
\ No newline at end of file
+	return test_user, pos_profile
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 76e0092..832fb80 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -57,7 +57,7 @@
 			self.apply_loyalty_points()
 		self.check_phone_payments()
 		self.set_status(update=True)
-	
+
 	def before_cancel(self):
 		if self.consolidated_invoice and frappe.db.get_value('Sales Invoice', self.consolidated_invoice, 'docstatus') == 1:
 			pos_closing_entry = frappe.get_all(
@@ -221,7 +221,7 @@
 		base_grand_total = flt(self.base_rounded_total) or flt(self.base_grand_total)
 		if not flt(self.change_amount) and grand_total < flt(self.paid_amount):
 			self.change_amount = flt(self.paid_amount - grand_total + flt(self.write_off_amount))
-			self.base_change_amount = flt(self.base_paid_amount - base_grand_total + flt(self.base_write_off_amount))
+			self.base_change_amount = flt(self.base_paid_amount) - base_grand_total + flt(self.base_write_off_amount)
 
 		if flt(self.change_amount) and not self.account_for_change_amount:
 			frappe.msgprint(_("Please enter Account for Change Amount"), raise_exception=1)
@@ -355,6 +355,7 @@
 
 		return profile
 
+	@frappe.whitelist()
 	def set_missing_values(self, for_validate=False):
 		profile = self.set_pos_fields(for_validate)
 
@@ -377,12 +378,20 @@
 				"allow_print_before_pay": profile.get("allow_print_before_pay")
 			}
 
+	@frappe.whitelist()
+	def reset_mode_of_payments(self):
+		if self.pos_profile:
+			pos_profile = frappe.get_cached_doc('POS Profile', self.pos_profile)
+			update_multi_mode_option(self, pos_profile)
+			self.paid_amount = 0
+
 	def set_account_for_mode_of_payment(self):
 		self.payments = [d for d in self.payments if d.amount or d.base_amount or d.default]
 		for pay in self.payments:
 			if not pay.account:
 				pay.account = get_bank_cash_account(pay.mode_of_payment, self.company).get("account")
 
+	@frappe.whitelist()
 	def create_payment_request(self):
 		for pay in self.payments:
 			if pay.type == "Phone":
@@ -400,7 +409,7 @@
 					pay_req.request_phone_payment()
 
 				return pay_req
-	
+
 	def get_new_payment_request(self, mop):
 		payment_gateway_account = frappe.db.get_value("Payment Gateway Account", {
 			"payment_account": mop.account,
diff --git a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
index eb52fd6..6d388c4 100644
--- a/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/test_pos_invoice.py
@@ -9,8 +9,20 @@
 from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
+from erpnext.stock.doctype.item.test_item import make_item
 
 class TestPOSInvoice(unittest.TestCase):
+	@classmethod
+	def setUpClass(cls):
+		frappe.db.sql("delete from `tabTax Rule`")
+
+	def tearDown(self):
+		if frappe.session.user != "Administrator":
+			frappe.set_user("Administrator")
+
+		if frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
+			frappe.db.set_value("Selling Settings", None, "validate_selling_price", 0)
+
 	def test_timestamp_change(self):
 		w = create_pos_invoice(do_not_save=1)
 		w.docstatus = 0
@@ -370,7 +382,6 @@
 		pos_inv.load_from_db()
 		rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
 		self.assertEqual(rounded_total, 3470)
-		frappe.set_user("Administrator")
 
 	def test_merging_into_sales_invoice_with_discount_and_inclusive_tax(self):
 		from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
@@ -412,7 +423,6 @@
 		pos_inv.load_from_db()
 		rounded_total = frappe.db.get_value("Sales Invoice", pos_inv.consolidated_invoice, "rounded_total")
 		self.assertEqual(rounded_total, 840)
-		frappe.set_user("Administrator")
 
 	def test_merging_with_validate_selling_price(self):
 		from erpnext.accounts.doctype.pos_closing_entry.test_pos_closing_entry import init_user_and_profile
@@ -421,10 +431,12 @@
 		if not frappe.db.get_single_value("Selling Settings", "validate_selling_price"):
 			frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 1)
 
-		make_purchase_receipt(item_code="_Test Item", warehouse="_Test Warehouse - _TC", qty=1, rate=300)
+		item = "Test Selling Price Validation"
+		make_item(item, {"is_stock_item": 1})
+		make_purchase_receipt(item_code=item, warehouse="_Test Warehouse - _TC", qty=1, rate=300)
 		frappe.db.sql("delete from `tabPOS Invoice`")
 		test_user, pos_profile = init_user_and_profile()
-		pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+		pos_inv = create_pos_invoice(item=item, rate=300, do_not_submit=1)
 		pos_inv.append('payments', {
 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
 		})
@@ -438,7 +450,7 @@
 		})
 		self.assertRaises(frappe.ValidationError, pos_inv.submit)
 
-		pos_inv2 = create_pos_invoice(rate=400, do_not_submit=1)
+		pos_inv2 = create_pos_invoice(item=item, rate=400, do_not_submit=1)
 		pos_inv2.append('payments', {
 			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 400
 		})
@@ -457,8 +469,6 @@
 		pos_inv2.load_from_db()
 		rounded_total = frappe.db.get_value("Sales Invoice", pos_inv2.consolidated_invoice, "rounded_total")
 		self.assertEqual(rounded_total, 400)
-		frappe.set_user("Administrator")
-		frappe.db.set_value("Selling Settings", "Selling Settings", "validate_selling_price", 0)
 
 def create_pos_invoice(**args):
 	args = frappe._dict(args)
@@ -508,4 +518,4 @@
 	else:
 		pos_inv.payment_schedule = []
 
-	return pos_inv
\ No newline at end of file
+	return pos_inv
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
index 40f77b4..6d2cffc 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/pos_invoice_merge_log.py
@@ -12,6 +12,7 @@
 from frappe.model.mapper import map_doc, map_child_doc
 from frappe.utils.scheduler import is_scheduler_inactive
 from frappe.core.page.background_jobs.background_jobs import get_info
+import json
 
 from six import iteritems
 
@@ -78,8 +79,11 @@
 		sales_invoice = self.merge_pos_invoice_into(sales_invoice, data)
 
 		sales_invoice.is_consolidated = 1
+		sales_invoice.set_posting_time = 1
+		sales_invoice.posting_date = getdate(self.posting_date)
 		sales_invoice.save()
 		sales_invoice.submit()
+
 		self.consolidated_invoice = sales_invoice.name
 
 		return sales_invoice.name
@@ -91,10 +95,13 @@
 		credit_note = self.merge_pos_invoice_into(credit_note, data)
 
 		credit_note.is_consolidated = 1
+		credit_note.set_posting_time = 1
+		credit_note.posting_date = getdate(self.posting_date)
 		# TODO: return could be against multiple sales invoice which could also have been consolidated?
 		# credit_note.return_against = self.consolidated_invoice
 		credit_note.save()
 		credit_note.submit()
+
 		self.consolidated_credit_note = credit_note.name
 
 		return credit_note.name
@@ -131,12 +138,14 @@
 					if t.account_head == tax.account_head and t.cost_center == tax.cost_center:
 						t.tax_amount = flt(t.tax_amount) + flt(tax.tax_amount_after_discount_amount)
 						t.base_tax_amount = flt(t.base_tax_amount) + flt(tax.base_tax_amount_after_discount_amount)
+						update_item_wise_tax_detail(t, tax)
 						found = True
 				if not found:
 					tax.charge_type = 'Actual'
 					tax.included_in_print_rate = 0
 					tax.tax_amount = tax.tax_amount_after_discount_amount
 					tax.base_tax_amount = tax.base_tax_amount_after_discount_amount
+					tax.item_wise_tax_detail = tax.item_wise_tax_detail
 					taxes.append(tax)
 
 			for payment in doc.get('payments'):
@@ -168,11 +177,9 @@
 		sales_invoice = frappe.new_doc('Sales Invoice')
 		sales_invoice.customer = self.customer
 		sales_invoice.is_pos = 1
-		# date can be pos closing date?
-		sales_invoice.posting_date = getdate(nowdate())
 
 		return sales_invoice
-	
+
 	def update_pos_invoices(self, invoice_docs, sales_invoice='', credit_note=''):
 		for doc in invoice_docs:
 			doc.load_from_db()
@@ -187,6 +194,26 @@
 			si.flags.ignore_validate = True
 			si.cancel()
 
+def update_item_wise_tax_detail(consolidate_tax_row, tax_row):
+	consolidated_tax_detail = json.loads(consolidate_tax_row.item_wise_tax_detail)
+	tax_row_detail = json.loads(tax_row.item_wise_tax_detail)
+
+	if not consolidated_tax_detail:
+		consolidated_tax_detail = {}
+
+	for item_code, tax_data in tax_row_detail.items():
+		if consolidated_tax_detail.get(item_code):
+			consolidated_tax_data = consolidated_tax_detail.get(item_code)
+			consolidated_tax_detail.update({
+				item_code: [consolidated_tax_data[0], consolidated_tax_data[1] + tax_data[1]]
+			})
+		else:
+			consolidated_tax_detail.update({
+				item_code: [tax_data[0], tax_data[1]]
+			})
+
+	consolidate_tax_row.item_wise_tax_detail = json.dumps(consolidated_tax_detail, separators=(',', ':'))
+
 def get_all_unconsolidated_invoices():
 	filters = {
 		'consolidated_invoice': [ 'in', [ '', None ]],
@@ -214,7 +241,7 @@
 
 	if len(invoices) >= 5 and closing_entry:
 		closing_entry.set_status(update=True, status='Queued')
-		enqueue_job(create_merge_logs, invoice_by_customer, closing_entry)
+		enqueue_job(create_merge_logs, invoice_by_customer=invoice_by_customer, closing_entry=closing_entry)
 	else:
 		create_merge_logs(invoice_by_customer, closing_entry)
 
@@ -227,21 +254,21 @@
 
 	if len(merge_logs) >= 5:
 		closing_entry.set_status(update=True, status='Queued')
-		enqueue_job(cancel_merge_logs, merge_logs, closing_entry)
+		enqueue_job(cancel_merge_logs, merge_logs=merge_logs, closing_entry=closing_entry)
 	else:
 		cancel_merge_logs(merge_logs, closing_entry)
 
 def create_merge_logs(invoice_by_customer, closing_entry={}):
 	for customer, invoices in iteritems(invoice_by_customer):
 		merge_log = frappe.new_doc('POS Invoice Merge Log')
-		merge_log.posting_date = getdate(nowdate())
+		merge_log.posting_date = getdate(closing_entry.get('posting_date'))
 		merge_log.customer = customer
 		merge_log.pos_closing_entry = closing_entry.get('name', None)
 
 		merge_log.set('pos_invoices', invoices)
 		merge_log.save(ignore_permissions=True)
 		merge_log.submit()
-	
+
 	if closing_entry:
 		closing_entry.set_status(update=True, status='Submitted')
 		closing_entry.update_opening_entry()
@@ -256,7 +283,7 @@
 		closing_entry.set_status(update=True, status='Cancelled')
 		closing_entry.update_opening_entry(for_cancel=True)
 
-def enqueue_job(job, invoice_by_customer, closing_entry):
+def enqueue_job(job, merge_logs=None, invoice_by_customer=None, closing_entry=None):
 	check_scheduler_status()
 
 	job_name = closing_entry.get("name")
@@ -269,6 +296,7 @@
 			job_name=job_name,
 			closing_entry=closing_entry,
 			invoice_by_customer=invoice_by_customer,
+			merge_logs=merge_logs,
 			now=frappe.conf.developer_mode or frappe.flags.in_test
 		)
 
diff --git a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
index db046c9..040a815 100644
--- a/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
+++ b/erpnext/accounts/doctype/pos_invoice_merge_log/test_pos_invoice_merge_log.py
@@ -5,6 +5,7 @@
 
 import frappe
 import unittest
+import json
 from erpnext.accounts.doctype.pos_invoice.test_pos_invoice import create_pos_invoice
 from erpnext.accounts.doctype.pos_invoice.pos_invoice import make_sales_return
 from erpnext.accounts.doctype.pos_invoice_merge_log.pos_invoice_merge_log import consolidate_pos_invoices
@@ -14,85 +15,136 @@
 	def test_consolidated_invoice_creation(self):
 		frappe.db.sql("delete from `tabPOS Invoice`")
 
-		test_user, pos_profile = init_user_and_profile()
+		try:
+			test_user, pos_profile = init_user_and_profile()
 
-		pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
-		pos_inv.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
-		})
-		pos_inv.submit()
+			pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+			pos_inv.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
+			})
+			pos_inv.submit()
 
-		pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
-		pos_inv2.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
-		})
-		pos_inv2.submit()
+			pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+			pos_inv2.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+			})
+			pos_inv2.submit()
 
-		pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
-		pos_inv3.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
-		})
-		pos_inv3.submit()
+			pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
+			pos_inv3.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
+			})
+			pos_inv3.submit()
 
-		consolidate_pos_invoices()
+			consolidate_pos_invoices()
 
-		pos_inv.load_from_db()
-		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
+			pos_inv.load_from_db()
+			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
 
-		pos_inv3.load_from_db()
-		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
+			pos_inv3.load_from_db()
+			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
 
-		self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
+			self.assertFalse(pos_inv.consolidated_invoice == pos_inv3.consolidated_invoice)
 
-		frappe.set_user("Administrator")
-		frappe.db.sql("delete from `tabPOS Profile`")
-		frappe.db.sql("delete from `tabPOS Invoice`")
-	
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
+
 	def test_consolidated_credit_note_creation(self):
 		frappe.db.sql("delete from `tabPOS Invoice`")
 
-		test_user, pos_profile = init_user_and_profile()
+		try:
+			test_user, pos_profile = init_user_and_profile()
 
-		pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
-		pos_inv.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
-		})
-		pos_inv.submit()
+			pos_inv = create_pos_invoice(rate=300, do_not_submit=1)
+			pos_inv.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 300
+			})
+			pos_inv.submit()
 
-		pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
-		pos_inv2.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
-		})
-		pos_inv2.submit()
+			pos_inv2 = create_pos_invoice(rate=3200, do_not_submit=1)
+			pos_inv2.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 3200
+			})
+			pos_inv2.submit()
 
-		pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
-		pos_inv3.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
-		})
-		pos_inv3.submit()
+			pos_inv3 = create_pos_invoice(customer="_Test Customer 2", rate=2300, do_not_submit=1)
+			pos_inv3.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': 2300
+			})
+			pos_inv3.submit()
 
-		pos_inv_cn = make_sales_return(pos_inv.name)
-		pos_inv_cn.set("payments", [])
-		pos_inv_cn.append('payments', {
-			'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
-		})
-		pos_inv_cn.paid_amount = -300
-		pos_inv_cn.submit()
+			pos_inv_cn = make_sales_return(pos_inv.name)
+			pos_inv_cn.set("payments", [])
+			pos_inv_cn.append('payments', {
+				'mode_of_payment': 'Cash', 'account': 'Cash - _TC', 'amount': -300
+			})
+			pos_inv_cn.paid_amount = -300
+			pos_inv_cn.submit()
 
-		consolidate_pos_invoices()
+			consolidate_pos_invoices()
 
-		pos_inv.load_from_db()
-		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
+			pos_inv.load_from_db()
+			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv.consolidated_invoice))
 
-		pos_inv3.load_from_db()
-		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
+			pos_inv3.load_from_db()
+			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv3.consolidated_invoice))
 
-		pos_inv_cn.load_from_db()
-		self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
-		self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
+			pos_inv_cn.load_from_db()
+			self.assertTrue(frappe.db.exists("Sales Invoice", pos_inv_cn.consolidated_invoice))
+			self.assertTrue(frappe.db.get_value("Sales Invoice", pos_inv_cn.consolidated_invoice, "is_return"))
 
-		frappe.set_user("Administrator")
-		frappe.db.sql("delete from `tabPOS Profile`")
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
+
+	def test_consolidated_invoice_item_taxes(self):
 		frappe.db.sql("delete from `tabPOS Invoice`")
 
+		try:
+			inv = create_pos_invoice(qty=1, rate=100, do_not_save=True)
+
+			inv.append("taxes", {
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 9
+			})
+			inv.insert()
+			inv.submit()
+
+			inv2 = create_pos_invoice(qty=1, rate=100, do_not_save=True)
+			inv2.get('items')[0].item_code = '_Test Item 2'
+			inv2.append("taxes", {
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 5
+			})
+			inv2.insert()
+			inv2.submit()
+
+			consolidate_pos_invoices()
+			inv.load_from_db()
+
+			consolidated_invoice = frappe.get_doc('Sales Invoice', inv.consolidated_invoice)
+			item_wise_tax_detail = json.loads(consolidated_invoice.get('taxes')[0].item_wise_tax_detail)
+
+			tax_rate, amount = item_wise_tax_detail.get('_Test Item')
+			self.assertEqual(tax_rate, 9)
+			self.assertEqual(amount, 9)
+
+			tax_rate2, amount2 = item_wise_tax_detail.get('_Test Item 2')
+			self.assertEqual(tax_rate2, 5)
+			self.assertEqual(amount2, 5)
+		finally:
+			frappe.set_user("Administrator")
+			frappe.db.sql("delete from `tabPOS Profile`")
+			frappe.db.sql("delete from `tabPOS Invoice`")
 
diff --git a/erpnext/accounts/doctype/pos_settings/pos_settings.js b/erpnext/accounts/doctype/pos_settings/pos_settings.js
index 8890d59..3625393 100644
--- a/erpnext/accounts/doctype/pos_settings/pos_settings.js
+++ b/erpnext/accounts/doctype/pos_settings/pos_settings.js
@@ -16,8 +16,11 @@
 				}
 			});
 
-			frappe.meta.get_docfield("POS Field", "fieldname", frm.doc.name).options = [""].concat(fields);
+			frm.fields_dict.invoice_fields.grid.update_docfield_property(
+				'fieldname', 'options', [""].concat(fields)
+			);
 		});
+
 	}
 });
 
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
index 3377164..428989a 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.json
@@ -44,6 +44,14 @@
   "column_break_21",
   "min_amt",
   "max_amt",
+  "product_discount_scheme_section",
+  "same_item",
+  "free_item",
+  "free_qty",
+  "free_item_rate",
+  "column_break_42",
+  "free_item_uom",
+  "is_recursive",
   "section_break_23",
   "valid_from",
   "valid_upto",
@@ -62,13 +70,6 @@
   "discount_amount",
   "discount_percentage",
   "for_price_list",
-  "product_discount_scheme_section",
-  "same_item",
-  "free_item",
-  "free_qty",
-  "column_break_51",
-  "free_item_uom",
-  "free_item_rate",
   "section_break_13",
   "threshold_percentage",
   "priority",
@@ -459,10 +460,6 @@
    "label": "Qty"
   },
   {
-   "fieldname": "column_break_51",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "free_item_uom",
    "fieldtype": "Link",
    "label": "UOM",
@@ -552,19 +549,33 @@
    "fieldname": "promotional_scheme",
    "fieldtype": "Link",
    "label": "Promotional Scheme",
-   "options": "Promotional Scheme"
+   "no_copy": 1,
+   "options": "Promotional Scheme",
+   "print_hide": 1,
+   "read_only": 1
   },
   {
    "description": "Simple Python Expression, Example: territory != 'All Territories'",
    "fieldname": "condition",
    "fieldtype": "Code",
    "label": "Condition"
+  },
+  {
+   "fieldname": "column_break_42",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
+   "fieldname": "is_recursive",
+   "fieldtype": "Check",
+   "label": "Is Recursive"
   }
  ],
  "icon": "fa fa-gift",
  "idx": 1,
  "links": [],
- "modified": "2021-03-01 23:18:38.717613",
+ "modified": "2021-03-06 22:01:24.840422",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Pricing Rule",
diff --git a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
index f0b4e29..aedf1c6 100644
--- a/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/pricing_rule.py
@@ -237,6 +237,7 @@
 		"doctype": args.doctype,
 		"has_margin": False,
 		"name": args.name,
+		"free_item_data": [],
 		"parent": args.parent,
 		"parenttype": args.parenttype,
 		"child_docname": args.get('child_docname')
diff --git a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
index f28cee7..ef9aad5 100644
--- a/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
+++ b/erpnext/accounts/doctype/pricing_rule/test_pricing_rule.py
@@ -328,6 +328,21 @@
 		self.assertEquals(item.discount_amount, 110)
 		self.assertEquals(item.rate, 990)
 
+	def test_pricing_rule_with_margin_and_discount_amount(self):
+		frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
+		make_pricing_rule(selling=1, margin_type="Percentage", margin_rate_or_amount=10,
+			rate_or_discount="Discount Amount", discount_amount=110)
+		si = create_sales_invoice(do_not_save=True)
+		si.items[0].price_list_rate = 1000
+		si.payment_schedule = []
+		si.insert(ignore_permissions=True)
+
+		item = si.items[0]
+		self.assertEquals(item.margin_rate_or_amount, 10)
+		self.assertEquals(item.rate_with_margin, 1100)
+		self.assertEquals(item.discount_amount, 110)
+		self.assertEquals(item.rate, 990)
+
 	def test_pricing_rule_for_product_discount_on_same_item(self):
 		frappe.delete_doc_if_exists('Pricing Rule', '_Test Pricing Rule')
 		test_record = {
@@ -560,6 +575,7 @@
 		"margin_rate_or_amount": args.margin_rate_or_amount or 0.0,
 		"condition": args.condition or '',
 		"priority": 1,
+		"discount_amount": args.discount_amount or 0.0,
 		"apply_multiple_pricing_rules": args.apply_multiple_pricing_rules or 0
 	})
 
diff --git a/erpnext/accounts/doctype/pricing_rule/utils.py b/erpnext/accounts/doctype/pricing_rule/utils.py
index d163335..b91a7a5 100644
--- a/erpnext/accounts/doctype/pricing_rule/utils.py
+++ b/erpnext/accounts/doctype/pricing_rule/utils.py
@@ -367,7 +367,7 @@
 
 	if items and doc.get("items"):
 		for row in doc.get('items'):
-			if row.get(apply_on) not in items: continue
+			if (row.get(apply_on) or args.get(apply_on)) not in items: continue
 
 			if pr_doc.mixed_conditions:
 				amt = args.get('qty') * args.get("price_list_rate")
@@ -471,7 +471,7 @@
 
 					if not d.get(pr_field): continue
 
-					if d.validate_applied_rule and doc.get(field) < d.get(pr_field):
+					if d.validate_applied_rule and doc.get(field) is not None and doc.get(field) < d.get(pr_field):
 						frappe.msgprint(_("User has not applied rule on the invoice {0}")
 							.format(doc.name))
 					else:
@@ -479,7 +479,7 @@
 
 				doc.calculate_taxes_and_totals()
 			elif d.price_or_product_discount == 'Product':
-				item_details = frappe._dict({'parenttype': doc.doctype})
+				item_details = frappe._dict({'parenttype': doc.doctype, 'free_item_data': []})
 				get_product_discount_rule(d, item_details, doc=doc)
 				apply_pricing_rule_for_free_items(doc, item_details.free_item_data)
 				doc.set_missing_values()
@@ -508,9 +508,16 @@
 		frappe.throw(_("Free item not set in the pricing rule {0}")
 			.format(get_link_to_form("Pricing Rule", pricing_rule.name)))
 
-	item_details.free_item_data = {
+	qty = pricing_rule.free_qty or 1
+	if pricing_rule.is_recursive:
+		transaction_qty = args.get('qty') if args else doc.total_qty
+		if transaction_qty:
+			qty = flt(transaction_qty) * qty
+
+	free_item_data_args = {
 		'item_code': free_item,
-		'qty': pricing_rule.free_qty or 1,
+		'qty': qty,
+		'pricing_rules': pricing_rule.name,
 		'rate': pricing_rule.free_item_rate or 0,
 		'price_list_rate': pricing_rule.free_item_rate or 0,
 		'is_free_item': 1
@@ -519,24 +526,26 @@
 	item_data = frappe.get_cached_value('Item', free_item, ['item_name',
 		'description', 'stock_uom'], as_dict=1)
 
-	item_details.free_item_data.update(item_data)
-	item_details.free_item_data['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
-	item_details.free_item_data['conversion_factor'] = get_conversion_factor(free_item,
-		item_details.free_item_data['uom']).get("conversion_factor", 1)
+	free_item_data_args.update(item_data)
+	free_item_data_args['uom'] = pricing_rule.free_item_uom or item_data.stock_uom
+	free_item_data_args['conversion_factor'] = get_conversion_factor(free_item,
+		free_item_data_args['uom']).get("conversion_factor", 1)
 
 	if item_details.get("parenttype") == 'Purchase Order':
-		item_details.free_item_data['schedule_date'] = doc.schedule_date if doc else today()
+		free_item_data_args['schedule_date'] = doc.schedule_date if doc else today()
 
 	if item_details.get("parenttype") == 'Sales Order':
-		item_details.free_item_data['delivery_date'] = doc.delivery_date if doc else today()
+		free_item_data_args['delivery_date'] = doc.delivery_date if doc else today()
+
+	item_details.free_item_data.append(free_item_data_args)
 
 def apply_pricing_rule_for_free_items(doc, pricing_rule_args, set_missing_values=False):
-	if pricing_rule_args.get('item_code'):
-		items = [d.item_code for d in doc.items
-			if d.item_code == (pricing_rule_args.get("item_code")) and d.is_free_item]
+	if pricing_rule_args:
+		items = tuple([(d.item_code, d.pricing_rules) for d in doc.items if d.is_free_item])
 
-		if not items:
-			doc.append('items', pricing_rule_args)
+		for args in pricing_rule_args:
+			if not items or (args.get('item_code'), args.get('pricing_rules')) not in items:
+				doc.append('items', args)
 
 def get_pricing_rule_items(pr_doc):
 	apply_on_data = []
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index 89f7238..523e9ee 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -12,16 +12,16 @@
 pricing_rule_fields = ['apply_on', 'mixed_conditions', 'is_cumulative', 'other_item_code', 'other_item_group'
 	'apply_rule_on_other', 'other_brand', 'selling', 'buying', 'applicable_for', 'valid_from',
 	'valid_upto', 'customer', 'customer_group', 'territory', 'sales_partner', 'campaign', 'supplier',
-	'supplier_group', 'company', 'currency']
+	'supplier_group', 'company', 'currency', 'apply_multiple_pricing_rules']
 
 other_fields = ['min_qty', 'max_qty', 'min_amt',
 	'max_amt', 'priority','warehouse', 'threshold_percentage', 'rule_description']
 
 price_discount_fields = ['rate_or_discount', 'apply_discount_on', 'apply_discount_on_rate',
-	'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule']
+	'rate', 'discount_amount', 'discount_percentage', 'validate_applied_rule', 'apply_multiple_pricing_rules']
 
 product_discount_fields = ['free_item', 'free_qty', 'free_item_uom',
-	'free_item_rate', 'same_item']
+	'free_item_rate', 'same_item', 'is_recursive', 'apply_multiple_pricing_rules']
 
 class PromotionalScheme(Document):
 	def validate(self):
diff --git a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
index 224b8de..795fb1c 100644
--- a/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
+++ b/erpnext/accounts/doctype/promotional_scheme_price_discount/promotional_scheme_price_discount.json
@@ -1,792 +1,181 @@
 {
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
  "creation": "2019-03-24 14:48:59.649168",
- "custom": 0,
- "docstatus": 0,
  "doctype": "DocType",
- "document_type": "",
  "editable_grid": 1,
  "engine": "InnoDB",
+ "field_order": [
+  "disable",
+  "apply_multiple_pricing_rules",
+  "column_break_2",
+  "rule_description",
+  "section_break_2",
+  "min_qty",
+  "max_qty",
+  "column_break_3",
+  "min_amount",
+  "max_amount",
+  "section_break_6",
+  "rate_or_discount",
+  "column_break_10",
+  "rate",
+  "discount_amount",
+  "discount_percentage",
+  "section_break_11",
+  "warehouse",
+  "threshold_percentage",
+  "validate_applied_rule",
+  "column_break_14",
+  "priority",
+  "apply_discount_on_rate"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
+   "default": "0",
    "fieldname": "disable",
    "fieldtype": "Check",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Disable",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Disable"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_2",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "rule_description",
    "fieldtype": "Small Text",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Rule Description",
-   "length": 0,
    "no_copy": 1,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 1,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "reqd": 1
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "section_break_2",
-   "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
    "columns": 1,
    "default": "0",
    "fieldname": "min_qty",
    "fieldtype": "Float",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Min Qty",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Min Qty"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
    "columns": 1,
    "default": "0",
    "fieldname": "max_qty",
    "fieldtype": "Float",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Max Qty",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Max Qty"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_3",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "default": "0",
    "fieldname": "min_amount",
    "fieldtype": "Currency",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Min Amount",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Min Amount"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "default": "0",
-   "depends_on": "",
    "fieldname": "max_amount",
    "fieldtype": "Currency",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Max Amount",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Max Amount"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "depends_on": "",
    "fieldname": "section_break_6",
-   "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "default": "Discount Percentage",
-   "depends_on": "",
    "fieldname": "rate_or_discount",
    "fieldtype": "Select",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
    "label": "Discount Type",
-   "length": 0,
-   "no_copy": 0,
-   "options": "\nRate\nDiscount Percentage\nDiscount Amount",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "options": "\nRate\nDiscount Percentage\nDiscount Amount"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
-   "depends_on": "",
    "fieldname": "column_break_10",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
    "columns": 2,
    "depends_on": "eval:doc.rate_or_discount==\"Rate\"",
    "fieldname": "rate",
    "fieldtype": "Currency",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
    "in_list_view": 1,
-   "in_standard_filter": 0,
-   "label": "Rate",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Rate"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "depends_on": "eval:doc.rate_or_discount==\"Discount Amount\"",
    "fieldname": "discount_amount",
    "fieldtype": "Currency",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Discount Amount",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Discount Amount"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "depends_on": "eval:doc.rate_or_discount==\"Discount Percentage\"",
    "fieldname": "discount_percentage",
    "fieldtype": "Float",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Discount Percentage",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Discount Percentage"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "section_break_11",
-   "fieldtype": "Section Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Section Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "warehouse",
    "fieldtype": "Link",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Warehouse",
-   "length": 0,
-   "no_copy": 0,
-   "options": "Warehouse",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "options": "Warehouse"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "threshold_percentage",
    "fieldtype": "Percent",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Threshold for Suggestion",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Threshold for Suggestion"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "default": "1",
    "fieldname": "validate_applied_rule",
    "fieldtype": "Check",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Validate Applied Rule",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Validate Applied Rule"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "column_break_14",
-   "fieldtype": "Column Break",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "fieldtype": "Column Break"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "fieldname": "priority",
    "fieldtype": "Select",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
    "label": "Priority",
-   "length": 0,
-   "no_copy": 0,
-   "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20",
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "options": "\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
+   "default": "0",
    "depends_on": "priority",
    "fieldname": "apply_multiple_pricing_rules",
    "fieldtype": "Check",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Apply Multiple Pricing Rules",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Apply Multiple Pricing Rules"
   },
   {
-   "allow_bulk_edit": 0,
-   "allow_in_quick_entry": 0,
-   "allow_on_submit": 0,
-   "bold": 0,
-   "collapsible": 0,
-   "columns": 0,
    "default": "0",
    "depends_on": "eval:in_list(['Discount Percentage', 'Discount Amount'], doc.rate_or_discount) && doc.apply_multiple_pricing_rules",
    "fieldname": "apply_discount_on_rate",
    "fieldtype": "Check",
-   "hidden": 0,
-   "ignore_user_permissions": 0,
-   "ignore_xss_filter": 0,
-   "in_filter": 0,
-   "in_global_search": 0,
-   "in_list_view": 0,
-   "in_standard_filter": 0,
-   "label": "Apply Discount on Rate",
-   "length": 0,
-   "no_copy": 0,
-   "permlevel": 0,
-   "precision": "",
-   "print_hide": 0,
-   "print_hide_if_no_value": 0,
-   "read_only": 0,
-   "remember_last_selected_value": 0,
-   "report_hide": 0,
-   "reqd": 0,
-   "search_index": 0,
-   "set_only_once": 0,
-   "translatable": 0,
-   "unique": 0
+   "label": "Apply Discount on Rate"
   }
  ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
+ "index_web_pages_for_search": 1,
  "istable": 1,
- "max_attachments": 0,
- "modified": "2019-03-24 14:48:59.649168",
+ "links": [],
+ "modified": "2021-03-07 11:56:23.424137",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Promotional Scheme Price Discount",
- "name_case": "",
  "owner": "Administrator",
  "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
  "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
index 72d53bf..3eab515 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
@@ -1,10 +1,12 @@
 {
+ "actions": [],
  "creation": "2019-03-24 14:48:59.649168",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
   "disable",
+  "apply_multiple_pricing_rules",
   "column_break_2",
   "rule_description",
   "section_break_1",
@@ -25,7 +27,7 @@
   "threshold_percentage",
   "column_break_15",
   "priority",
-  "apply_multiple_pricing_rules"
+  "is_recursive"
  ],
  "fields": [
   {
@@ -152,10 +154,19 @@
    "fieldname": "apply_multiple_pricing_rules",
    "fieldtype": "Check",
    "label": "Apply Multiple Pricing Rules"
+  },
+  {
+   "default": "0",
+   "description": "Discounts to be applied in sequential ranges like buy 1 get 1, buy 2 get 2, buy 3 get 3 and so on",
+   "fieldname": "is_recursive",
+   "fieldtype": "Check",
+   "label": "Is Recursive"
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
- "modified": "2019-07-21 00:00:56.674284",
+ "links": [],
+ "modified": "2021-03-06 21:58:18.162346",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Promotional Scheme Product Discount",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 06aa20b..e61cde8 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -496,15 +496,6 @@
 	}
 }
 
-cur_frm.cscript.select_print_heading = function(doc,cdt,cdn){
-	if(doc.select_print_heading){
-		// print heading
-		cur_frm.pformat.print_heading = doc.select_print_heading;
-	}
-	else
-		cur_frm.pformat.print_heading = __("Purchase Invoice");
-}
-
 frappe.ui.form.on("Purchase Invoice", {
 	setup: function(frm) {
 		frm.custom_make_buttons = {
@@ -524,7 +515,7 @@
 	},
 
 	onload: function(frm) {
-		if(frm.doc.__onload) {
+		if(frm.doc.__onload && frm.is_new()) {
 			if(frm.doc.supplier) {
 				frm.doc.apply_tds = frm.doc.__onload.supplier_tds ? 1 : 0;
 			}
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 18b6637..739bd67 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -127,7 +127,6 @@
   "write_off_cost_center",
   "advances_section",
   "allocate_advances_automatically",
-  "adjust_advance_taxes",
   "get_advances",
   "advances",
   "payment_schedule_section",
@@ -1327,13 +1326,6 @@
    "options": "Project"
   },
   {
-   "default": "0",
-   "description": "Taxes paid while advance payment will be adjusted against this invoice",
-   "fieldname": "adjust_advance_taxes",
-   "fieldtype": "Check",
-   "label": "Adjust Advance Taxes"
-  },
-  {
    "depends_on": "eval:doc.is_internal_supplier",
    "description": "Unrealized Profit / Loss account for intra-company transfers",
    "fieldname": "unrealized_profit_loss_account",
@@ -1378,7 +1370,7 @@
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-03-09 21:12:30.422084",
+ "modified": "2021-03-30 21:45:58.334107",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index ded293b..50492f5 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -898,7 +898,7 @@
 		acc_settings.submit_journal_entries = 1
 		acc_settings.save()
 
-		item = create_item("_Test Item for Deferred Accounting")
+		item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True)
 		item.enable_deferred_expense = 1
 		item.deferred_expense_account = deferred_account
 		item.save()
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_records.json b/erpnext/accounts/doctype/purchase_invoice/test_records.json
index e7166c5..9f9e90d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_records.json
+++ b/erpnext/accounts/doctype/purchase_invoice/test_records.json
@@ -43,7 +43,7 @@
    }
   ],
   "grand_total": 0,
-  "naming_series": "_T-BILL",
+  "naming_series": "T-PINV-",
   "taxes": [
    {
     "account_head": "_Test Account Shipping Charges - _TC",
@@ -167,7 +167,7 @@
    }
   ],
   "grand_total": 0,
-  "naming_series": "_T-Purchase Invoice-",
+  "naming_series": "T-PINV-",
   "taxes": [
    {
     "account_head": "_Test Account Shipping Charges - _TC",
diff --git a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
index 3e1c522..ada665a 100644
--- a/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
+++ b/erpnext/accounts/doctype/sales_invoice/regional/india_list.js
@@ -1,14 +1,14 @@
 var globalOnload = frappe.listview_settings['Sales Invoice'].onload;
-frappe.listview_settings['Sales Invoice'].onload = function (doclist) {
+frappe.listview_settings['Sales Invoice'].onload = function (list_view) {
 
 	// Provision in case onload event is added to sales_invoice.js in future
 	if (globalOnload) {
-		globalOnload(doclist);
+		globalOnload(list_view);
 	}
 
 	const action = () => {
-		const selected_docs = doclist.get_checked_items();
-		const docnames = doclist.get_checked_items(true);
+		const selected_docs = list_view.get_checked_items();
+		const docnames = list_view.get_checked_items(true);
 
 		for (let doc of selected_docs) {
 			if (doc.docstatus !== 1) {
@@ -19,7 +19,7 @@
 		frappe.call({
 			method: 'erpnext.regional.india.utils.generate_ewb_json',
 			args: {
-				'dt': doclist.doctype,
+				'dt': list_view.doctype,
 				'dn': docnames
 			},
 			callback: function(r) {
@@ -35,5 +35,140 @@
 		});
 	};
 
-	doclist.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
+	list_view.page.add_actions_menu_item(__('Generate E-Way Bill JSON'), action, false);
+
+	const generate_irns = () => {
+		const docnames = list_view.get_checked_items(true);
+		if (docnames && docnames.length) {
+			frappe.call({
+				method: 'erpnext.regional.india.e_invoice.utils.generate_einvoices',
+				args: { docnames },
+				freeze: true,
+				freeze_message: __('Generating E-Invoices...')
+			});
+		} else {
+			frappe.msgprint({
+				message: __('Please select at least one sales invoice to generate IRN'),
+				title: __('No Invoice Selected'),
+				indicator: 'red'
+			});
+		}
+	};
+
+	const cancel_irns = () => {
+		const docnames = list_view.get_checked_items(true);
+
+		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 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_irns',
+					args: { 
+						doctype: list_view.doctype,
+						docnames,
+						reason: data.reason.split('-')[0],
+						remark: data.remark
+					},
+					freeze: true,
+					freeze_message: __('Cancelling E-Invoices...'),
+				});
+				d.hide();
+			},
+			primary_action_label: __('Submit')
+		});
+		d.show();
+	};
+
+	let einvoicing_enabled = false;
+	frappe.db.get_single_value("E Invoice Settings", "enable").then(enabled => {
+		einvoicing_enabled = enabled;
+	});
+
+	list_view.$result.on("change", "input[type=checkbox]", () => {
+		if (einvoicing_enabled) {
+			const docnames = list_view.get_checked_items(true);
+			// show/hide e-invoicing actions when no sales invoices are checked
+			if (docnames && docnames.length) {
+				// prevent adding actions twice if e-invoicing action group already exists
+				if (list_view.page.get_inner_group_button(__('E-Invoicing')).length == 0) {
+					list_view.page.add_inner_button(__('Generate IRNs'), generate_irns, __('E-Invoicing'));
+					list_view.page.add_inner_button(__('Cancel IRNs'), cancel_irns, __('E-Invoicing'));
+				}
+			} else {
+				list_view.page.remove_inner_button(__('Generate IRNs'), __('E-Invoicing'));
+				list_view.page.remove_inner_button(__('Cancel IRNs'), __('E-Invoicing'));
+			}
+		}
+	});
+
+	frappe.realtime.on("bulk_einvoice_generation_complete", (data) => {
+		const { failures, user, invoices } = data;
+		
+		if (invoices.length != failures.length) {
+			frappe.msgprint({
+				message: __('{0} e-invoices generated successfully', [invoices.length]),
+				title: __('Bulk E-Invoice Generation Complete'),
+				indicator: 'orange'
+			});
+		}
+
+		if (failures && failures.length && user == frappe.session.user) {
+			let message = `
+				Failed to generate IRNs for following ${failures.length} sales invoices:
+				<ul style="padding-left: 20px; padding-top: 5px;">
+					${failures.map(d => `<li>${d.docname}</li>`).join('')}
+				</ul>
+			`;
+			frappe.msgprint({
+				message: message,
+				title: __('Bulk E-Invoice Generation Complete'),
+				indicator: 'orange'
+			});
+		}
+	});
+
+	frappe.realtime.on("bulk_einvoice_cancellation_complete", (data) => {
+		const { failures, user, invoices } = data;
+
+		if (invoices.length != failures.length) {
+			frappe.msgprint({
+				message: __('{0} e-invoices cancelled successfully', [invoices.length]),
+				title: __('Bulk E-Invoice Cancellation Complete'),
+				indicator: 'orange'
+			});
+		}
+
+		if (failures && failures.length && user == frappe.session.user) {
+			let message = `
+				Failed to cancel IRNs for following ${failures.length} sales invoices:
+				<ul style="padding-left: 20px; padding-top: 5px;">
+					${failures.map(d => `<li>${d.docname}</li>`).join('')}
+				</ul>
+			`;
+			frappe.msgprint({
+				message: message,
+				title: __('Bulk E-Invoice Cancellation Complete'),
+				indicator: 'orange'
+			});
+		}
+	});
 };
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index b361c0c..8a42d9e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -1,9 +1,6 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
-// print heading
-cur_frm.pformat.print_heading = 'Invoice';
-
 {% include 'erpnext/selling/sales_common.js' %};
 frappe.provide("erpnext.accounts");
 
@@ -916,7 +913,7 @@
 				},
 				callback: function(r, rt) {
 					if(r.message){
-						data = r.message;
+						let data = r.message;
 						frappe.model.set_value(cdt, cdn, "billing_hours", data.billing_hours);
 						frappe.model.set_value(cdt, cdn, "billing_amount", data.billing_amount);
 						frappe.model.set_value(cdt, cdn, "timesheet_detail", data.timesheet_detail);
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 720a917..d382386 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -1952,13 +1952,12 @@
  "is_submittable": 1,
  "links": [
   {
-   "custom": 1,
    "group": "Reference",
    "link_doctype": "POS Invoice",
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2021-02-01 15:42:26.261540",
+ "modified": "2021-03-31 15:42:26.261540",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 4076be7..21d550a 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -76,7 +76,7 @@
 
 		if not self.is_pos:
 			self.so_dn_required()
-		
+
 		self.set_tax_withholding()
 
 		self.validate_proj_cust()
@@ -390,6 +390,7 @@
 		if validate_against_credit_limit:
 			check_credit_limit(self.customer, self.company, bypass_credit_limit_check_at_sales_order)
 
+	@frappe.whitelist()
 	def set_missing_values(self, for_validate=False):
 		pos = self.set_pos_fields(for_validate)
 
@@ -729,6 +730,7 @@
 		else:
 			self.calculate_billing_amount_for_timesheet()
 
+	@frappe.whitelist()
 	def add_timesheet_data(self):
 		self.set('timesheets', [])
 		if self.project:
@@ -1286,6 +1288,7 @@
 				break
 
 	# Healthcare
+	@frappe.whitelist()
 	def set_healthcare_services(self, checked_values):
 		self.set("items", [])
 		from erpnext.stock.get_item_details import get_item_details
diff --git a/erpnext/accounts/doctype/sales_invoice/test_records.json b/erpnext/accounts/doctype/sales_invoice/test_records.json
index e00a58f..3781f8c 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_records.json
+++ b/erpnext/accounts/doctype/sales_invoice/test_records.json
@@ -31,7 +31,7 @@
   "base_grand_total": 561.8,
   "grand_total": 561.8,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "base_net_total": 500.0,
   "taxes": [
    {
@@ -104,7 +104,7 @@
   "base_grand_total": 630.0,
   "grand_total": 630.0,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "base_net_total": 500.0,
   "taxes": [
    {
@@ -175,7 +175,7 @@
   ],
   "grand_total": 0,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "taxes": [
    {
     "account_head": "_Test Account Shipping Charges - _TC",
@@ -301,7 +301,7 @@
   ],
   "grand_total": 0,
   "is_pos": 0,
-  "naming_series": "_T-Sales Invoice-",
+  "naming_series": "T-SINV-",
   "taxes": [
    {
     "account_head": "_Test Account Excise Duty - _TC",
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 1b95578..f09cc5a 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1800,6 +1800,15 @@
 		si.selling_price_list = "_Test Price List Rest of the World"
 		si.update_stock = 1
 		si.items[0].target_warehouse = 'Work In Progress - TCP1'
+
+		# Add stock to stores for succesful stock transfer
+		make_stock_entry(
+			target="Stores - TCP1",
+			company = "_Test Company with perpetual inventory",
+			qty=1,
+			basic_rate=100
+		)
+
 		add_taxes(si)
 		si.save()
 
@@ -2106,6 +2115,7 @@
 	si.return_against = args.return_against
 	si.currency=args.currency or "INR"
 	si.conversion_rate = args.conversion_rate or 1
+	si.naming_series = args.naming_series or "T-SINV-"
 
 	si.append("items", {
 		"item_code": args.item or args.item_code or "_Test Item",
@@ -2269,4 +2279,4 @@
 		"cost_center": "Main - TCP1",
 		"description": "Excise Duty",
 		"rate": 12
-	})
\ No newline at end of file
+	})
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
index 429a9f3..52d19d5 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.py
@@ -46,5 +46,5 @@
 		frappe.throw(_("Disabled template must not be default template"))
 
 def validate_for_tax_category(doc):
-	if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0}):
+	if frappe.db.exists(doc.doctype, {"company": doc.company, "tax_category": doc.tax_category, "disabled": 0, "name": ["!=", doc.name]}):
 		frappe.throw(_("A template with tax category {0} already exists. Only one template is allowed with each tax category").format(frappe.bold(doc.tax_category)))
diff --git a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
index 632e30d..ac1ffd9 100644
--- a/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/test_tax_rule.py
@@ -14,10 +14,15 @@
 from six import iteritems
 
 class TestTaxRule(unittest.TestCase):
-	def setUp(self):
+	@classmethod
+	def setUpClass(cls):
+		frappe.db.set_value("Shopping Cart Settings", None, "enabled", 0)
+
+	@classmethod
+	def tearDownClass(cls):
 		frappe.db.sql("delete from `tabTax Rule`")
 
-	def tearDown(self):
+	def setUp(self):
 		frappe.db.sql("delete from `tabTax Rule`")
 
 	def test_conflict(self):
diff --git a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
index 9ce8e3f..dd3b49a 100644
--- a/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
+++ b/erpnext/accounts/doctype/tax_withholding_category/test_tax_withholding_category.py
@@ -177,7 +177,7 @@
 
 	for d in purchase_invoices:
 		frappe.get_doc('Purchase Invoice', d).cancel()
-	
+
 	for d in sales_invoices:
 		frappe.get_doc('Sales Invoice', d).cancel()
 
@@ -229,7 +229,8 @@
 			'qty': args.qty or 1,
 			'rate': args.rate or 10000,
 			'cost_center': 'Main - _TC',
-			'expense_account': 'Cost of Goods Sold - _TC'
+			'expense_account': 'Cost of Goods Sold - _TC',
+			'warehouse': args.warehouse or '_Test Warehouse - _TC'
 		}]
 	})
 
@@ -353,4 +354,4 @@
 				'company': '_Test Company',
 				'account': 'TDS - _TC'
 			}]
-		}).insert()
\ No newline at end of file
+		}).insert()
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 51fc7ec..444b40e 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -364,7 +364,7 @@
 		payment_terms_details = frappe.db.sql("""
 			select
 				si.name, si.party_account_currency, si.currency, si.conversion_rate,
-				ps.due_date, ps.payment_amount, ps.description, ps.paid_amount
+				ps.due_date, ps.payment_amount, ps.description, ps.paid_amount, ps.discounted_amount
 			from `tab{0}` si, `tabPayment Schedule` ps
 			where
 				si.name = ps.parent and
@@ -395,13 +395,13 @@
 			"invoiced": invoiced,
 			"invoice_grand_total": row.invoiced,
 			"payment_term": d.description,
-			"paid": d.paid_amount,
+			"paid": d.paid_amount + d.discounted_amount,
 			"credit_note": 0.0,
-			"outstanding": invoiced - d.paid_amount
+			"outstanding": invoiced - d.paid_amount - d.discounted_amount
 		}))
 
 		if d.paid_amount:
-			row['paid'] -= d.paid_amount
+			row['paid'] -= d.paid_amount + d.discounted_amount
 
 	def allocate_closing_to_term(self, row, term, key):
 		if row[key]:
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 7dfce85..14efa1f 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -51,7 +51,11 @@
 			"from_date": start_date
 		})
 
-		to_date = add_months(start_date, months_to_add)
+		if i==0 and filter_based_on == 'Date Range':
+			to_date = add_months(get_first_day(start_date), months_to_add)
+		else:
+			to_date = add_months(start_date, months_to_add)
+
 		start_date = to_date
 
 		# Subtract one day from to_date, as it may be first day in next fiscal year or month
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 89a05b1..5a64e27 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -406,9 +406,10 @@
 		throw(_("""Payment Entry has been modified after you pulled it. Please pull it again."""))
 
 def validate_allocated_amount(args):
+	precision = args.get('precision') or frappe.db.get_single_value("System Settings", "currency_precision")
 	if args.get("allocated_amount") < 0:
 		throw(_("Allocated amount cannot be negative"))
-	elif args.get("allocated_amount") > args.get("unadjusted_amount"):
+	elif flt(args.get("allocated_amount"), precision) > flt(args.get("unadjusted_amount"), precision):
 		throw(_("Allocated amount cannot be greater than unadjusted amount"))
 
 def update_reference_in_journal_entry(d, jv_obj):
diff --git a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
index afbd9b4..9000dea 100644
--- a/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
+++ b/erpnext/agriculture/doctype/crop_cycle/crop_cycle.py
@@ -71,6 +71,7 @@
 				"exp_end_date": add_days(start_date, crop_task.get("end_day") - 1)
 			}).insert()
 
+	@frappe.whitelist()
 	def reload_linked_analysis(self):
 		linked_doctypes = ['Soil Texture', 'Soil Analysis', 'Plant Analysis']
 		required_fields = ['location', 'name', 'collection_datetime']
@@ -87,6 +88,7 @@
 		frappe.publish_realtime("List of Linked Docs",
 								output, user=frappe.session.user)
 
+	@frappe.whitelist()
 	def append_to_child(self, obj_to_append):
 		for doctype in obj_to_append:
 			for doc_name in set(obj_to_append[doctype]):
diff --git a/erpnext/agriculture/doctype/fertilizer/fertilizer.py b/erpnext/agriculture/doctype/fertilizer/fertilizer.py
index dc2781c..9cb492a 100644
--- a/erpnext/agriculture/doctype/fertilizer/fertilizer.py
+++ b/erpnext/agriculture/doctype/fertilizer/fertilizer.py
@@ -7,6 +7,7 @@
 from frappe.model.document import Document
 
 class Fertilizer(Document):
+	@frappe.whitelist()
 	def load_contents(self):
 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Fertilizer'})
 		for doc in docs:
diff --git a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
index 304727e..2806cc6 100644
--- a/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
+++ b/erpnext/agriculture/doctype/plant_analysis/plant_analysis.py
@@ -8,6 +8,7 @@
 from frappe.model.document import Document
 
 class PlantAnalysis(Document):
+	@frappe.whitelist()
 	def load_contents(self):
 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Plant Analysis'})
 		for doc in docs:
diff --git a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
index 17b96a0..37835f8 100644
--- a/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
+++ b/erpnext/agriculture/doctype/soil_analysis/soil_analysis.py
@@ -7,6 +7,7 @@
 from frappe.model.document import Document
 
 class SoilAnalysis(Document):
+	@frappe.whitelist()
 	def load_contents(self):
 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Analysis'})
 		for doc in docs:
diff --git a/erpnext/agriculture/doctype/soil_texture/soil_texture.py b/erpnext/agriculture/doctype/soil_texture/soil_texture.py
index 8c1d7ed..209b2c8 100644
--- a/erpnext/agriculture/doctype/soil_texture/soil_texture.py
+++ b/erpnext/agriculture/doctype/soil_texture/soil_texture.py
@@ -13,6 +13,7 @@
 	soil_edit_order = [2, 1, 0]
 	soil_types = ['clay_composition', 'sand_composition', 'silt_composition']
 
+	@frappe.whitelist()
 	def load_contents(self):
 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Soil Texture'})
 		for doc in docs:
@@ -26,6 +27,7 @@
 		if sum(self.get(soil_type) for soil_type in self.soil_types) != 100:
 			frappe.throw(_('Soil compositions do not add up to 100'))
 
+	@frappe.whitelist()
 	def update_soil_edit(self, soil_type):
 		self.soil_edit_order[self.soil_types.index(soil_type)] = max(self.soil_edit_order)+1
 		self.soil_type = self.get_soil_type()
@@ -35,8 +37,8 @@
 		if sum(self.soil_edit_order) < 5: return
 		last_edit_index = self.soil_edit_order.index(min(self.soil_edit_order))
 
-		# set composition of the last edited soil 
-		self.set( self.soil_types[last_edit_index], 
+		# set composition of the last edited soil
+		self.set(self.soil_types[last_edit_index],
 			100 - sum(cint(self.get(soil_type)) for soil_type in self.soil_types) + cint(self.get(self.soil_types[last_edit_index])))
 
 		# calculate soil type
@@ -67,4 +69,4 @@
 		elif (c >= 40 and sa <= 45 and si < 40):
 			return 'Clay'
 		else:
-			return 'Select'
\ No newline at end of file
+			return 'Select'
diff --git a/erpnext/agriculture/doctype/water_analysis/water_analysis.py b/erpnext/agriculture/doctype/water_analysis/water_analysis.py
index 88f1fbd..d9f007c 100644
--- a/erpnext/agriculture/doctype/water_analysis/water_analysis.py
+++ b/erpnext/agriculture/doctype/water_analysis/water_analysis.py
@@ -9,11 +9,13 @@
 from frappe import _
 
 class WaterAnalysis(Document):
+	@frappe.whitelist()
 	def load_contents(self):
 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Water Analysis'})
 		for doc in docs:
 			self.append('water_analysis_criteria', {'title': str(doc.name)})
 
+	@frappe.whitelist()
 	def update_lab_result_date(self):
 		if not self.result_datetime:
 			self.result_datetime = self.laboratory_testing_datetime
diff --git a/erpnext/agriculture/doctype/weather/weather.py b/erpnext/agriculture/doctype/weather/weather.py
index 938daa2..235e684 100644
--- a/erpnext/agriculture/doctype/weather/weather.py
+++ b/erpnext/agriculture/doctype/weather/weather.py
@@ -7,6 +7,7 @@
 from frappe.model.document import Document
 
 class Weather(Document):
+	@frappe.whitelist()
 	def load_contents(self):
 		docs = frappe.get_all("Agriculture Analysis Criteria", filters={'linked_doctype':'Weather'})
 		for doc in docs:
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index e8e8ec6..9aff144 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -553,6 +553,7 @@
 			make_gl_entries(gl_entries)
 			self.db_set('booked_fixed_asset', 1)
 
+	@frappe.whitelist()
 	def get_depreciation_rate(self, args, on_validate=False):
 		if isinstance(args, string_types):
 			args = json.loads(args)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index d32e98e..ef9372e 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -133,6 +133,7 @@
 						d.material_request_item, "schedule_date")
 
 
+	@frappe.whitelist()
 	def get_last_purchase_rate(self):
 		"""get last purchase rates for all items"""
 
@@ -252,6 +253,7 @@
 		self.update_prevdoc_status()
 
 		# Must be called after updating ordered qty in Material Request
+		# bin uses Material Request Items to recalculate & update
 		self.update_requested_qty()
 		self.update_ordered_qty()
 
@@ -366,7 +368,6 @@
 		"Purchase Order": {
 			"doctype": "Purchase Receipt",
 			"field_map": {
-				"per_billed": "per_billed",
 				"supplier_warehouse":"supplier_warehouse"
 			},
 			"validation": {
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 02d4865..3c4f908 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -90,6 +90,50 @@
 		frappe.db.set_value('Item', '_Test Item', 'over_billing_allowance', 0)
 		frappe.db.set_value("Accounts Settings", None, "over_billing_allowance", 0)
 
+	def test_update_remove_child_linked_to_mr(self):
+		"""Test impact on linked PO and MR on deleting/updating row."""
+		mr = make_material_request(qty=10)
+		po = make_purchase_order(mr.name)
+		po.supplier = "_Test Supplier"
+		po.save()
+		po.submit()
+
+		first_item_of_po = po.get("items")[0]
+		existing_ordered_qty = get_ordered_qty() # 10
+		existing_requested_qty = get_requested_qty() # 0
+
+		# decrease ordered qty by 3 (10 -> 7) and add item
+		trans_item = json.dumps([
+			{
+				'item_code': first_item_of_po.item_code,
+				'rate': first_item_of_po.rate,
+				'qty': 7,
+				'docname': first_item_of_po.name
+			},
+			{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
+		])
+		update_child_qty_rate('Purchase Order', trans_item, po.name)
+		mr.reload()
+
+		# requested qty increases as ordered qty decreases
+		self.assertEqual(get_requested_qty(), existing_requested_qty + 3) # 3
+		self.assertEqual(mr.items[0].ordered_qty, 7)
+
+		self.assertEqual(get_ordered_qty(), existing_ordered_qty - 3) # 7
+
+		# delete first item linked to Material Request
+		trans_item = json.dumps([
+			{'item_code' : '_Test Item 2', 'rate' : 200, 'qty' : 2}
+		])
+		update_child_qty_rate('Purchase Order', trans_item, po.name)
+		mr.reload()
+
+		# requested qty increases as ordered qty is 0 (deleted row)
+		self.assertEqual(get_requested_qty(), existing_requested_qty + 10) # 10
+		self.assertEqual(mr.items[0].ordered_qty, 0)
+
+		# ordered qty decreases as ordered qty is 0 (deleted row)
+		self.assertEqual(get_ordered_qty(), existing_ordered_qty - 10) # 0
 
 	def test_update_child(self):
 		mr = make_material_request(qty=10)
@@ -120,7 +164,6 @@
 		self.assertEqual(po.get("items")[0].amount, 1400)
 		self.assertEqual(get_ordered_qty(), existing_ordered_qty + 3)
 
-
 	def test_update_child_adding_new_item(self):
 		po = create_purchase_order(do_not_save=1)
 		po.items[0].qty = 4
@@ -129,6 +172,7 @@
 		pr = make_pr_against_po(po.name, 2)
 
 		po.load_from_db()
+		existing_ordered_qty = get_ordered_qty()
 		first_item_of_po = po.get("items")[0]
 
 		trans_item = json.dumps([
@@ -145,7 +189,8 @@
 		po.reload()
 		self.assertEquals(len(po.get('items')), 2)
 		self.assertEqual(po.status, 'To Receive and Bill')
-
+		# ordered qty should increase on row addition
+		self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
 
 	def test_update_child_removing_item(self):
 		po = create_purchase_order(do_not_save=1)
@@ -156,6 +201,7 @@
 
 		po.reload()
 		first_item_of_po = po.get("items")[0]
+		existing_ordered_qty = get_ordered_qty()
 		# add an item
 		trans_item = json.dumps([
 			{
@@ -168,6 +214,10 @@
 		update_child_qty_rate('Purchase Order', trans_item, po.name)
 
 		po.reload()
+
+		# ordered qty should increase on row addition
+		self.assertEqual(get_ordered_qty(), existing_ordered_qty + 7)
+
 		# check if can remove received item
 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': po.get("items")[1].name}])
 		self.assertRaises(frappe.ValidationError, update_child_qty_rate, 'Purchase Order', trans_item, po.name)
@@ -187,6 +237,9 @@
 		self.assertEquals(len(po.get('items')), 1)
 		self.assertEqual(po.status, 'To Receive and Bill')
 
+		# ordered qty should decrease (back to initial) on row deletion
+		self.assertEqual(get_ordered_qty(), existing_ordered_qty)
+
 	def test_update_child_perm(self):
 		po = create_purchase_order(item_code= "_Test Item", qty=4)
 
@@ -230,11 +283,13 @@
 
 		new_item_with_tax = frappe.get_doc("Item", "Test Item with Tax")
 
-		new_item_with_tax.append("taxes", {
-			"item_tax_template": "Test Update Items Template - _TC",
-			"valid_from": nowdate()
-		})
-		new_item_with_tax.save()
+		if not frappe.db.exists("Item Tax",
+			{"item_tax_template": "Test Update Items Template - _TC", "parent": "Test Item with Tax"}):
+			new_item_with_tax.append("taxes", {
+				"item_tax_template": "Test Update Items Template - _TC",
+				"valid_from": nowdate()
+			})
+			new_item_with_tax.save()
 
 		tax_template = "_Test Account Excise Duty @ 10 - _TC"
 		item =  "_Test Item Home Desktop 100"
@@ -723,7 +778,7 @@
 			is_subcontracted="Yes", supplier_warehouse="_Test Warehouse 1 - _TC")
 
 		make_stock_entry(target="_Test Warehouse - _TC",
-			item_code="_Test Item Home Desktop 100", qty=10, basic_rate=100)
+			item_code="_Test Item Home Desktop 100", qty=20, basic_rate=100)
 		make_stock_entry(target="_Test Warehouse - _TC",
 			item_code = "Test Extra Item 1", qty=100, basic_rate=100)
 		make_stock_entry(target="_Test Warehouse - _TC",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 7cf22f8..b530d1a 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -66,6 +66,7 @@
 	def on_cancel(self):
 		frappe.db.set(self, 'status', 'Cancelled')
 
+	@frappe.whitelist()
 	def get_supplier_email_preview(self, supplier):
 		"""Returns formatted email preview as string."""
 		rfq_suppliers = list(filter(lambda row: row.supplier == supplier, self.suppliers))
diff --git a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
index 6e6eaed..2528240 100644
--- a/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
+++ b/erpnext/buying/doctype/supplier_scorecard/test_supplier_scorecard.py
@@ -9,9 +9,7 @@
 class TestSupplierScorecard(unittest.TestCase):
 
 	def test_create_scorecard(self):
-		delete_test_scorecards()
-		my_doc = make_supplier_scorecard()
-		doc = my_doc.insert()
+		doc = make_supplier_scorecard().insert()
 		self.assertEqual(doc.name, valid_scorecard[0].get("supplier"))
 
 	def test_criteria_weight(self):
@@ -121,7 +119,8 @@
 			{
 				"weight":100.0,
 				"doctype":"Supplier Scorecard Scoring Criteria",
-				"criteria_name":"Delivery"
+				"criteria_name":"Delivery",
+				"formula": "100"
 			}
 		],
 		"supplier":"_Test Supplier",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 12a81c7..33fbf1c 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -26,7 +26,8 @@
 
 class AccountMissingError(frappe.ValidationError): pass
 
-force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate", "pricing_rules")
+force_item_fields = ("item_group", "brand", "stock_uom", "is_fixed_asset", "item_tax_rate",
+	"pricing_rules", "weight_per_unit", "weight_uom", "total_weight")
 
 class AccountsController(TransactionBase):
 	def __init__(self, *args, **kwargs):
@@ -516,6 +517,7 @@
 		frappe.db.sql("""delete from `tab%s` where parentfield=%s and parent = %s
 			and allocated_amount = 0""" % (childtype, '%s', '%s'), (parentfield, self.name))
 
+	@frappe.whitelist()
 	def apply_shipping_rule(self):
 		if self.shipping_rule:
 			shipping_rule = frappe.get_doc("Shipping Rule", self.shipping_rule)
@@ -536,6 +538,7 @@
 
 		return {}
 
+	@frappe.whitelist()
 	def set_advances(self):
 		"""Returns list of advances against Account, Party, Reference"""
 
@@ -656,6 +659,7 @@
 					'dr_or_cr': dr_or_cr,
 					'unadjusted_amount': flt(d.advance_amount),
 					'allocated_amount': flt(d.allocated_amount),
+					'precision': d.precision('advance_amount'),
 					'exchange_rate': (self.conversion_rate
 						if self.party_account_currency != self.company_currency else 1),
 					'grand_total': (self.base_grand_total
@@ -920,7 +924,8 @@
 		else:
 			for d in self.get("payment_schedule"):
 				if d.invoice_portion:
-					d.payment_amount = flt(grand_total * flt(d.invoice_portion) / 100, d.precision('payment_amount'))
+					d.payment_amount = flt(grand_total * flt(d.invoice_portion / 100), d.precision('payment_amount'))
+					d.outstanding = d.payment_amount
 
 	def set_due_date(self):
 		due_dates = [d.due_date for d in self.get("payment_schedule") if d.due_date]
@@ -1235,18 +1240,24 @@
 	term_details.description = term.description
 	term_details.invoice_portion = term.invoice_portion
 	term_details.payment_amount = flt(term.invoice_portion) * flt(grand_total) / 100
+	term_details.discount_type = term.discount_type
+	term_details.discount = term.discount
+	# term_details.discounted_amount = flt(grand_total) * (term.discount / 100) if term.discount_type == 'Percentage' else discount
+	term_details.outstanding = term_details.payment_amount
+	term_details.mode_of_payment = term.mode_of_payment
+
 	if bill_date:
 		term_details.due_date = get_due_date(term, bill_date)
+		term_details.discount_date = get_discount_date(term, bill_date)
 	elif posting_date:
 		term_details.due_date = get_due_date(term, posting_date)
+		term_details.discount_date = get_discount_date(term, posting_date)
 
 	if getdate(term_details.due_date) < getdate(posting_date):
 		term_details.due_date = posting_date
-	term_details.mode_of_payment = term.mode_of_payment
 
 	return term_details
 
-
 def get_due_date(term, posting_date=None, bill_date=None):
 	due_date = None
 	date = bill_date or posting_date
@@ -1258,6 +1269,16 @@
 		due_date = add_months(get_last_day(date), term.credit_months)
 	return due_date
 
+def get_discount_date(term, posting_date=None, bill_date=None):
+	discount_validity = None
+	date = bill_date or posting_date
+	if term.discount_validity_based_on == "Day(s) after invoice date":
+		discount_validity = add_days(date, term.discount_validity)
+	elif term.discount_validity_based_on == "Day(s) after the end of the invoice month":
+		discount_validity = add_days(get_last_day(date), term.discount_validity)
+	elif term.discount_validity_based_on == "Month(s) after the end of the invoice month":
+		discount_validity = add_months(get_last_day(date), term.discount_validity)
+	return discount_validity
 
 def get_supplier_block_status(party_name):
 	"""
@@ -1316,25 +1337,63 @@
 	p_doc = frappe.get_doc(parent_doctype, parent_doctype_name)
 	child_item = frappe.new_doc(child_doctype, p_doc, child_docname)
 	item = frappe.get_doc("Item", trans_item.get('item_code'))
+
 	for field in ("item_code", "item_name", "description", "item_group"):
-	    child_item.update({field: item.get(field)})
+		child_item.update({field: item.get(field)})
+
 	date_fieldname = "delivery_date" if child_doctype == "Sales Order Item" else "schedule_date"
 	child_item.update({date_fieldname: trans_item.get(date_fieldname) or p_doc.get(date_fieldname)})
+	child_item.stock_uom = item.stock_uom
 	child_item.uom = trans_item.get("uom") or item.stock_uom
+	child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
 	conversion_factor = flt(get_conversion_factor(item.item_code, child_item.uom).get("conversion_factor"))
 	child_item.conversion_factor = flt(trans_item.get('conversion_factor')) or conversion_factor
+
 	if child_doctype == "Purchase Order Item":
-		child_item.base_rate = 1 # Initiallize value will update in parent validation
-		child_item.base_amount = 1 # Initiallize value will update in parent validation
+		# Initialized value will update in parent validation
+		child_item.base_rate = 1
+		child_item.base_amount = 1
 	if child_doctype == "Sales Order Item":
 		child_item.warehouse = get_item_warehouse(item, p_doc, overwrite_warehouse=True)
 		if not child_item.warehouse:
 			frappe.throw(_("Cannot find {} for item {}. Please set the same in Item Master or Stock Settings.")
 				.format(frappe.bold("default warehouse"), frappe.bold(item.item_code)))
+
 	set_child_tax_template_and_map(item, child_item, p_doc)
 	add_taxes_from_tax_template(child_item, p_doc)
 	return child_item
 
+def validate_child_on_delete(row, parent):
+	"""Check if partially transacted item (row) is being deleted."""
+	if parent.doctype == "Sales Order":
+		if flt(row.delivered_qty):
+			frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(row.idx, row.item_code))
+		if flt(row.work_order_qty):
+			frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(row.idx, row.item_code))
+		if flt(row.ordered_qty):
+			frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(row.idx, row.item_code))
+
+	if parent.doctype == "Purchase Order" and flt(row.received_qty):
+		frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(row.idx, row.item_code))
+
+	if flt(row.billed_amt):
+		frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(row.idx, row.item_code))
+
+def update_bin_on_delete(row, doctype):
+	"""Update bin for deleted item (row)."""
+	from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty, get_ordered_qty, get_indented_qty
+	qty_dict = {}
+
+	if doctype == "Sales Order":
+		qty_dict["reserved_qty"] = get_reserved_qty(row.item_code, row.warehouse)
+	else:
+		if row.material_request_item:
+			qty_dict["indented_qty"] = get_indented_qty(row.item_code, row.warehouse)
+
+		qty_dict["ordered_qty"] = get_ordered_qty(row.item_code, row.warehouse)
+
+	update_bin_qty(row.item_code, row.warehouse, qty_dict)
+
 def validate_and_delete_children(parent, data):
 	deleted_children = []
 	updated_item_names = [d.get("docname") for d in data]
@@ -1343,23 +1402,17 @@
 			deleted_children.append(item)
 
 	for d in deleted_children:
-		if parent.doctype == "Sales Order":
-			if flt(d.delivered_qty):
-				frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been delivered").format(d.idx, d.item_code))
-			if flt(d.work_order_qty):
-				frappe.throw(_("Row #{0}: Cannot delete item {1} which has work order assigned to it.").format(d.idx, d.item_code))
-			if flt(d.ordered_qty):
-				frappe.throw(_("Row #{0}: Cannot delete item {1} which is assigned to customer's purchase order.").format(d.idx, d.item_code))
-
-		if parent.doctype == "Purchase Order" and flt(d.received_qty):
-			frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been received").format(d.idx, d.item_code))
-
-		if flt(d.billed_amt):
-			frappe.throw(_("Row #{0}: Cannot delete item {1} which has already been billed.").format(d.idx, d.item_code))
-
+		validate_child_on_delete(d, parent)
 		d.cancel()
 		d.delete()
 
+	# need to update ordered qty in Material Request first
+	# bin uses Material Request Items to recalculate & update
+	parent.update_prevdoc_status()
+
+	for d in deleted_children:
+		update_bin_on_delete(d, parent.doctype)
+
 @frappe.whitelist()
 def update_child_qty_rate(parent_doctype, trans_items, parent_doctype_name, child_docname="items"):
 	def check_doc_permissions(doc, perm_type='create'):
@@ -1394,7 +1447,7 @@
 			)
 
 	def get_new_child_item(item_row):
-		child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item" 
+		child_doctype = "Sales Order Item" if parent_doctype == "Sales Order" else "Purchase Order Item"
 		return set_order_defaults(parent_doctype, parent_doctype_name, child_doctype, child_docname, item_row)
 
 	def validate_quantity(child_item, d):
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index fb52c1f..edc40c4 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -144,7 +144,7 @@
 
 			if sales_person.commission_rate:
 				sales_person.incentives = flt(
-					sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0, 
+					sales_person.allocated_amount * flt(sales_person.commission_rate) / 100.0,
 					self.precision("incentives", sales_person))
 
 			total += sales_person.allocated_percentage
@@ -502,4 +502,4 @@
 	for d in obj.get("items"):
 		if d.item_code:
 			if getattr(d, "income_account", None):
-				set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
\ No newline at end of file
+				set_item_default(d.item_code, obj.company, 'income_account', d.income_account)
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 11ac703..2049957 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -406,8 +406,7 @@
 	def set_rate_of_stock_uom(self):
 		if self.doctype in ["Purchase Receipt", "Purchase Invoice", "Purchase Order", "Sales Invoice", "Sales Order", "Delivery Note", "Quotation"]:
 			for d in self.get("items"):
-				if d.conversion_factor:
-					d.stock_uom_rate = d.rate / d.conversion_factor
+				d.stock_uom_rate = d.rate / (d.conversion_factor or 1)
 
 	def validate_internal_transfer(self):
 		if self.doctype in ('Sales Invoice', 'Delivery Note', 'Purchase Invoice', 'Purchase Receipt') \
@@ -495,7 +494,7 @@
 			"voucher_no": self.name,
 			"company": self.company
 		})
-		if check_if_future_sle_exists(args):
+		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,
@@ -506,37 +505,42 @@
 		{'docstatus': 1, 'status': ['in', ['Queued','In Progress']]})
 
 
-def check_if_future_sle_exists(args):
-	sl_entries = frappe.db.get_all("Stock Ledger Entry",
+def future_sle_exists(args):
+	sl_entries = frappe.get_all("Stock Ledger Entry",
 		filters={"voucher_type": args.voucher_type, "voucher_no": args.voucher_no},
 		fields=["item_code", "warehouse"],
 		order_by="creation asc")
 
-	distinct_item_warehouses = list(set([(d.item_code, d.warehouse) for d in sl_entries]))
+	if not sl_entries:
+		return
 
-	sle_exists = False
-	for item_code, warehouse in distinct_item_warehouses:
-		args.update({
-			"item_code": item_code,
-			"warehouse": warehouse
-		})
-		if get_sle(args):
-			sle_exists = True
-			break
-	return sle_exists
+	warehouse_items_map = {}
+	for entry in sl_entries:
+		if entry.warehouse not in warehouse_items_map:
+			warehouse_items_map[entry.warehouse] = set()
 
-def get_sle(args):
+		warehouse_items_map[entry.warehouse].add(entry.item_code)
+
+	or_conditions = []
+	for warehouse, items in warehouse_items_map.items():
+		or_conditions.append(
+			"warehouse = '{}' and item_code in ({})".format(
+				warehouse,
+				", ".join(frappe.db.escape(item) for item in items)
+			)
+		)
+
 	return frappe.db.sql("""
 		select name
 		from `tabStock Ledger Entry`
 		where
-			item_code=%(item_code)s
-			and warehouse=%(warehouse)s
-			and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
+			({})
+			and timestamp(posting_date, posting_time)
+				>= timestamp(%(posting_date)s, %(posting_time)s)
 			and voucher_no != %(voucher_no)s
 			and is_cancelled = 0
 		limit 1
-	""", args)
+		""".format(" or ".join(or_conditions)), args)
 
 def create_repost_item_valuation_entry(args):
 	args = frappe._dict(args)
@@ -554,4 +558,4 @@
 	repost_entry.allow_zero_rate = args.allow_zero_rate
 	repost_entry.flags.ignore_links = True
 	repost_entry.save()
-	repost_entry.submit()
\ No newline at end of file
+	repost_entry.submit()
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index aab5770..7653a5d 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -113,7 +113,10 @@
 					item.rate_with_margin, item.base_rate_with_margin = self.calculate_margin(item)
 					if flt(item.rate_with_margin) > 0:
 						item.rate = flt(item.rate_with_margin * (1.0 - (item.discount_percentage / 100.0)), item.precision("rate"))
-						item.discount_amount = item.rate_with_margin - item.rate
+						if item.discount_amount and not item.discount_percentage:
+							item.rate -= item.discount_amount
+						else:
+							item.discount_amount = item.rate_with_margin - item.rate
 					elif flt(item.price_list_rate) > 0:
 						item.discount_amount = item.price_list_rate - item.rate
 				elif flt(item.price_list_rate) > 0 and not item.discount_amount:
@@ -144,7 +147,9 @@
 				validate_taxes_and_charges(tax)
 				validate_inclusive_tax(tax, self.doc)
 
-			tax.item_wise_tax_detail = {}
+			if not self.doc.get('is_consolidated'):
+				tax.item_wise_tax_detail = {}
+
 			tax_fields = ["total", "tax_amount_after_discount_amount",
 				"tax_amount_for_current_item", "grand_total_for_current_item",
 				"tax_fraction_for_current_item", "grand_total_fraction_for_current_item"]
@@ -335,7 +340,9 @@
 			current_tax_amount = tax_rate * item.qty
 
 		current_tax_amount = self.get_final_current_tax_amount(tax, current_tax_amount)
-		self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
+
+		if not self.doc.get("is_consolidated"):
+			self.set_item_wise_tax(item, tax, tax_rate, current_tax_amount)
 
 		return current_tax_amount
 
@@ -437,8 +444,9 @@
 			self._set_in_company_currency(self.doc, ["rounding_adjustment", "rounded_total"])
 
 	def _cleanup(self):
-		for tax in self.doc.get("taxes"):
-			tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
+		if not self.doc.get('is_consolidated'):
+			for tax in self.doc.get("taxes"):
+				tax.item_wise_tax_detail = json.dumps(tax.item_wise_tax_detail, separators=(',', ':'))
 
 	def set_discount_amount(self):
 		if self.doc.additional_discount_percentage:
@@ -795,7 +803,7 @@
 		for d in self.doc.get(self.tax_field):
 			if d.account_currency == company_currency:
 				d.exchange_rate = 1
-			elif not d.exchange_rate or d.exchange_rate == 1 or self.doc.posting_date:
+			elif not d.exchange_rate:
 				d.exchange_rate = get_exchange_rate(self.doc.posting_date, account=d.expense_account,
 					account_currency=d.account_currency, company=self.doc.company)
 
@@ -805,4 +813,4 @@
 	def set_amounts_in_company_currency(self):
 		for d in self.doc.get(self.tax_field):
 			d.amount = flt(d.amount, d.precision("amount"))
-			d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
\ No newline at end of file
+			d.base_amount = flt(d.amount * flt(d.exchange_rate), d.precision("base_amount"))
diff --git a/erpnext/selling/doctype/lead_source/__init__.py b/erpnext/crm/doctype/lead_source/__init__.py
similarity index 100%
rename from erpnext/selling/doctype/lead_source/__init__.py
rename to erpnext/crm/doctype/lead_source/__init__.py
diff --git a/erpnext/crm/doctype/lead_source/lead_source.js b/erpnext/crm/doctype/lead_source/lead_source.js
new file mode 100644
index 0000000..3cbe649
--- /dev/null
+++ b/erpnext/crm/doctype/lead_source/lead_source.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Lead Source', {
+	// refresh: function(frm) {
+
+	// }
+});
diff --git a/erpnext/crm/doctype/lead_source/lead_source.json b/erpnext/crm/doctype/lead_source/lead_source.json
new file mode 100644
index 0000000..723c6d9
--- /dev/null
+++ b/erpnext/crm/doctype/lead_source/lead_source.json
@@ -0,0 +1,62 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:source_name",
+ "creation": "2016-09-16 01:47:47.382372",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "source_name",
+  "details"
+ ],
+ "fields": [
+  {
+   "fieldname": "source_name",
+   "fieldtype": "Data",
+   "in_list_view": 1,
+   "label": "Source Name",
+   "reqd": 1,
+   "unique": 1
+  },
+  {
+   "fieldname": "details",
+   "fieldtype": "Text Editor",
+   "label": "Details"
+  }
+ ],
+ "links": [],
+ "modified": "2021-02-08 12:51:48.971517",
+ "modified_by": "Administrator",
+ "module": "CRM",
+ "name": "Lead Source",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Sales Manager",
+   "share": 1,
+   "write": 1
+  },
+  {
+   "create": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Sales User",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/lead_source/lead_source.py b/erpnext/crm/doctype/lead_source/lead_source.py
similarity index 71%
rename from erpnext/selling/doctype/lead_source/lead_source.py
rename to erpnext/crm/doctype/lead_source/lead_source.py
index d2d7558..5c64fb8 100644
--- a/erpnext/selling/doctype/lead_source/lead_source.py
+++ b/erpnext/crm/doctype/lead_source/lead_source.py
@@ -1,9 +1,9 @@
 # -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
 from __future__ import unicode_literals
-import frappe
+# import frappe
 from frappe.model.document import Document
 
 class LeadSource(Document):
diff --git a/erpnext/crm/doctype/lead_source/test_lead_source.py b/erpnext/crm/doctype/lead_source/test_lead_source.py
new file mode 100644
index 0000000..b5bc649
--- /dev/null
+++ b/erpnext/crm/doctype/lead_source/test_lead_source.py
@@ -0,0 +1,10 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from __future__ import unicode_literals
+
+# import frappe
+import unittest
+
+class TestLeadSource(unittest.TestCase):
+	pass
diff --git a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
index 377e061..d8c6fb4 100644
--- a/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
+++ b/erpnext/crm/doctype/linkedin_settings/linkedin_settings.py
@@ -11,7 +11,8 @@
 from six.moves.urllib.parse import urlencode
 
 class LinkedInSettings(Document):
-	def get_authorization_url(self):	
+	@frappe.whitelist()
+	def get_authorization_url(self):
 		params = urlencode({
 			"response_type":"code",
 			"client_id": self.consumer_key,
@@ -35,7 +36,7 @@
 		headers = {
 			"Content-Type": "application/x-www-form-urlencoded"
 		}
-		
+
 		response = self.http_post(url=url, data=body, headers=headers)
 		response = frappe.parse_json(response.content.decode())
 		self.db_set("access_token", response["access_token"])
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 47b05f3..23ad98a 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -85,6 +85,7 @@
 			self.opportunity_from = "Lead"
 			self.party_name = lead_name
 
+	@frappe.whitelist()
 	def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
 		if not self.has_active_quotation():
 			frappe.db.set(self, 'status', 'Lost')
@@ -248,7 +249,6 @@
 			"doctype": "Quotation",
 			"field_map": {
 				"opportunity_from": "quotation_to",
-				"opportunity_type": "order_type",
 				"name": "enq_no",
 			}
 		},
diff --git a/erpnext/crm/doctype/twitter_settings/twitter_settings.py b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
index 976a23d..1e1beab 100644
--- a/erpnext/crm/doctype/twitter_settings/twitter_settings.py
+++ b/erpnext/crm/doctype/twitter_settings/twitter_settings.py
@@ -11,6 +11,7 @@
 from tweepy.error import TweepError
 
 class TwitterSettings(Document):
+	@frappe.whitelist()
 	def get_authorize_url(self):
 		callback_url = "{0}/api/method/erpnext.crm.doctype.twitter_settings.twitter_settings.callback?".format(frappe.utils.get_url())
 		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"), callback_url)
@@ -21,12 +22,12 @@
 			frappe.msgprint(_("Error! Failed to get request token."))
 			frappe.throw(_('Invalid {0} or {1}').format(frappe.bold("Consumer Key"), frappe.bold("Consumer Secret Key")))
 
-	
+
 	def get_access_token(self, oauth_token, oauth_verifier):
 		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
-		auth.request_token = { 
+		auth.request_token = {
 			'oauth_token' : oauth_token,
-			'oauth_token_secret' : oauth_verifier 
+			'oauth_token_secret' : oauth_verifier
 		}
 
 		try:
@@ -50,10 +51,10 @@
 			frappe.throw(_('Invalid Consumer Key or Consumer Secret Key'))
 
 	def get_api(self, access_token, access_token_secret):
-		# authentication of consumer key and secret 
-		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret")) 
-		# authentication of access token and secret 
-		auth.set_access_token(access_token, access_token_secret) 
+		# authentication of consumer key and secret
+		auth = tweepy.OAuthHandler(self.consumer_key, self.get_password(fieldname="consumer_secret"))
+		# authentication of access token and secret
+		auth.set_access_token(access_token, access_token_secret)
 
 		return tweepy.API(auth)
 
@@ -64,7 +65,7 @@
 		if media:
 			media_id = self.upload_image(media)
 			return self.send_tweet(text, media_id)
-	
+
 	def upload_image(self, media):
 		media = get_file_path(media)
 		api = self.get_api(self.access_token, self.access_token_secret)
diff --git a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
index 97c29ab..6a0dcf4 100644
--- a/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
+++ b/erpnext/education/doctype/course_scheduling_tool/course_scheduling_tool.py
@@ -13,6 +13,7 @@
 
 class CourseSchedulingTool(Document):
 
+	@frappe.whitelist()
 	def schedule_course(self):
 		"""Creates course schedules as per specified parameters"""
 
diff --git a/erpnext/education/doctype/fee_schedule/fee_schedule.py b/erpnext/education/doctype/fee_schedule/fee_schedule.py
index 1543acd..0b025c7 100644
--- a/erpnext/education/doctype/fee_schedule/fee_schedule.py
+++ b/erpnext/education/doctype/fee_schedule/fee_schedule.py
@@ -52,6 +52,7 @@
 		self.grand_total = no_of_students*self.total_amount
 		self.grand_total_in_words = money_in_words(self.grand_total)
 
+	@frappe.whitelist()
 	def create_fees(self):
 		self.db_set("fee_creation_status", "In Process")
 		frappe.publish_realtime("fee_schedule_progress",
diff --git a/erpnext/education/doctype/program_enrollment/program_enrollment.py b/erpnext/education/doctype/program_enrollment/program_enrollment.py
index d18c0f9..b282bab 100644
--- a/erpnext/education/doctype/program_enrollment/program_enrollment.py
+++ b/erpnext/education/doctype/program_enrollment/program_enrollment.py
@@ -91,6 +91,8 @@
 				(fee, fee) for fee in fee_list]
 			msgprint(_("Fee Records Created - {0}").format(comma_and(fee_list)))
 
+
+	@frappe.whitelist()
 	def get_courses(self):
 		return frappe.db.sql('''select course from `tabProgram Course` where parent = %s and required = 1''', (self.program), as_dict=1)
 
diff --git a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
index 8180102..5833b67 100644
--- a/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
+++ b/erpnext/education/doctype/program_enrollment_tool/program_enrollment_tool.py
@@ -14,6 +14,7 @@
 		academic_term_reqd = cint(frappe.db.get_single_value('Education Settings', 'academic_term_reqd'))
 		self.set_onload("academic_term_reqd", academic_term_reqd)
 
+	@frappe.whitelist()
 	def get_students(self):
 		students = []
 		if not self.get_students_from:
@@ -49,6 +50,7 @@
 		else:
 			frappe.throw(_("No students Found"))
 
+	@frappe.whitelist()
 	def enroll_students(self):
 		total = len(self.students)
 		for i, stud in enumerate(self.students):
diff --git a/erpnext/education/doctype/student_attendance/student_attendance.json b/erpnext/education/doctype/student_attendance/student_attendance.json
index 55384b9..e6e46d1 100644
--- a/erpnext/education/doctype/student_attendance/student_attendance.json
+++ b/erpnext/education/doctype/student_attendance/student_attendance.json
@@ -10,6 +10,7 @@
   "naming_series",
   "student",
   "student_name",
+  "student_mobile_number",
   "course_schedule",
   "student_group",
   "column_break_3",
@@ -93,11 +94,19 @@
    "options": "Student Attendance",
    "print_hide": 1,
    "read_only": 1
+  },
+  {
+   "fetch_from": "student.student_mobile_number",
+   "fieldname": "student_mobile_number",
+   "fieldtype": "Read Only",
+   "label": "Student Mobile Number",
+   "options": "Phone"
   }
  ],
+ "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-07-08 13:55:42.580181",
+ "modified": "2021-03-24 00:02:11.005895",
  "modified_by": "Administrator",
  "module": "Education",
  "name": "Student Attendance",
diff --git a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py
index d7645e3..dc8667e 100644
--- a/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py
+++ b/erpnext/education/doctype/student_group_creation_tool/student_group_creation_tool.py
@@ -9,6 +9,7 @@
 from erpnext.education.doctype.student_group.student_group import get_students
 
 class StudentGroupCreationTool(Document):
+	@frappe.whitelist()
 	def get_courses(self):
 		group_list = []
 
@@ -42,6 +43,7 @@
 
 		return group_list
 
+	@frappe.whitelist()
 	def create_student_groups(self):
 		if not self.courses:
 			frappe.throw(_("""No Student Groups created."""))
diff --git a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
index b571802..fdfaa1b 100644
--- a/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
+++ b/erpnext/erpnext_integrations/doctype/mpesa_settings/mpesa_settings.py
@@ -59,9 +59,10 @@
 				request_amounts.append(amount)
 		else:
 			request_amounts = [request_amount]
-		
+
 		return request_amounts
 
+	@frappe.whitelist()
 	def get_account_balance_info(self):
 		payload = dict(
 			reference_doctype="Mpesa Settings",
@@ -198,7 +199,7 @@
 		completed_mpesa_receipt = fetch_param_value(item_response, "MpesaReceiptNumber", "Name")
 		completed_payments.append(completed_amount)
 		mpesa_receipts.append(completed_mpesa_receipt)
-	
+
 	return mpesa_receipts, completed_payments
 
 def get_account_balance(request_payload):
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index 21f6fee..16c6573 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -15,6 +15,7 @@
 
 class PlaidSettings(Document):
 	@staticmethod
+	@frappe.whitelist()
 	def get_link_token():
 		plaid = PlaidConnector()
 		return plaid.get_link_token()
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
index 3c90637..e2243ea 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/test_plaid_settings.py
@@ -23,14 +23,9 @@
 			doc.cancel()
 			doc.delete()
 
-		for ba in frappe.get_all("Bank Account"):
-			frappe.get_doc("Bank Account", ba.name).delete()
-
-		for at in frappe.get_all("Bank Account Type"):
-			frappe.get_doc("Bank Account Type", at.name).delete()
-
-		for ast in frappe.get_all("Bank Account Subtype"):
-			frappe.get_doc("Bank Account Subtype", ast.name).delete()
+		for doctype in ("Bank Account", "Bank Account Type", "Bank Account Subtype"):
+			for d in frappe.get_all(doctype):
+				frappe.delete_doc(doctype, d.name, force=True)
 
 	def test_plaid_disabled(self):
 		frappe.db.set_value("Plaid Settings", None, "enabled", 0)
diff --git a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
index 96a533e..866ea66 100644
--- a/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
+++ b/erpnext/erpnext_integrations/doctype/quickbooks_migrator/quickbooks_migrator.py
@@ -54,6 +54,7 @@
 			self.authorization_url = self.oauth.authorization_url(self.authorization_endpoint)[0]
 
 
+	@frappe.whitelist()
 	def migrate(self):
 		frappe.enqueue_doc("QuickBooks Migrator", "QuickBooks Migrator", "_migrate", queue="long")
 
diff --git a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
index 74ad456..5f471ab 100644
--- a/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
+++ b/erpnext/erpnext_integrations/doctype/shopify_settings/test_shopify_settings.py
@@ -5,7 +5,7 @@
 import frappe
 
 import unittest, os, json
-from frappe.utils import cstr
+from frappe.utils import cstr, cint
 from erpnext.erpnext_integrations.connectors.shopify_connection import create_order
 from erpnext.erpnext_integrations.doctype.shopify_settings.sync_product import make_item
 from erpnext.erpnext_integrations.doctype.shopify_settings.sync_customer import create_customer
@@ -13,9 +13,14 @@
 
 
 class ShopifySettings(unittest.TestCase):
-	def setUp(self):
+	@classmethod
+	def setUpClass(cls):
 		frappe.set_user("Administrator")
 
+		cls.allow_negative_stock = cint(frappe.db.get_value('Stock Settings', None, 'allow_negative_stock'))
+		if not cls.allow_negative_stock:
+			frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 1)
+
 		# use the fixture data
 		import_doc(frappe.get_app_path("erpnext", "erpnext_integrations/doctype/shopify_settings/test_data/custom_field.json"))
 
@@ -24,9 +29,15 @@
 		frappe.reload_doctype("Delivery Note")
 		frappe.reload_doctype("Sales Invoice")
 
-		self.setup_shopify()
+		cls.setup_shopify()
 
-	def setup_shopify(self):
+	@classmethod
+	def tearDownClass(cls):
+		if not cls.allow_negative_stock:
+			frappe.db.set_value('Stock Settings', None, 'allow_negative_stock', 0)
+
+	@classmethod
+	def setup_shopify(cls):
 		shopify_settings = frappe.get_doc("Shopify Settings")
 		shopify_settings.taxes = []
 
@@ -56,21 +67,20 @@
 			"delivery_note_series": "DN-"
 		}).save(ignore_permissions=True)
 
-		self.shopify_settings = shopify_settings
+		cls.shopify_settings = shopify_settings
 
 	def test_order(self):
-		### Create Customer ###
+		# Create Customer
 		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_customer.json")) as shopify_customer:
 			shopify_customer = json.load(shopify_customer)
 		create_customer(shopify_customer.get("customer"), self.shopify_settings)
 
-		### Create Item ###
+		# Create Item
 		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_item.json")) as shopify_item:
 			shopify_item = json.load(shopify_item)
 		make_item("_Test Warehouse - _TC", shopify_item.get("product"))
 
-
-		### Create Order ###
+		# Create Order
 		with open (os.path.join(os.path.dirname(__file__), "test_data", "shopify_order.json")) as shopify_order:
 			shopify_order = json.load(shopify_order)
 
@@ -80,17 +90,17 @@
 
 		self.assertEqual(cstr(shopify_order.get("order").get("id")), sales_order.shopify_order_id)
 
-		#check for customer
+		# Check for customer
 		shopify_order_customer_id = cstr(shopify_order.get("order").get("customer").get("id"))
 		sales_order_customer_id = frappe.get_value("Customer", sales_order.customer, "shopify_customer_id")
 
 		self.assertEqual(shopify_order_customer_id, sales_order_customer_id)
 
-		#check sales invoice
+		# Check sales invoice
 		sales_invoice = frappe.get_doc("Sales Invoice", {"shopify_order_id": sales_order.shopify_order_id})
 		self.assertEqual(sales_invoice.rounded_total, sales_order.rounded_total)
 
-		#check delivery note
+		# Check delivery note
 		delivery_note_count = frappe.db.sql("""select count(*) from `tabDelivery Note`
 			where shopify_order_id = %s""", sales_order.shopify_order_id)[0][0]
 
diff --git a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
index 462685f..907a223 100644
--- a/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
+++ b/erpnext/erpnext_integrations/doctype/tally_migration/tally_migration.py
@@ -594,18 +594,22 @@
 			frappe.db.set_value("Price List", "Tally Price List", "enabled", 0)
 		frappe.flags.in_migrate = False
 
+	@frappe.whitelist()
 	def process_master_data(self):
 		self.set_status("Processing Master Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_process_master_data", queue="long", timeout=3600)
 
+	@frappe.whitelist()
 	def import_master_data(self):
 		self.set_status("Importing Master Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_import_master_data", queue="long", timeout=3600)
 
+	@frappe.whitelist()
 	def process_day_book_data(self):
 		self.set_status("Processing Day Book Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_process_day_book_data", queue="long", timeout=3600)
 
+	@frappe.whitelist()
 	def import_day_book_data(self):
 		self.set_status("Importing Day Book Data")
 		frappe.enqueue_doc(self.doctype, self.name, "_import_day_book_data", queue="long", timeout=3600)
diff --git a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
index 325c209..cbf89ee 100644
--- a/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
+++ b/erpnext/healthcare/doctype/clinical_procedure/clinical_procedure.py
@@ -54,6 +54,7 @@
 	def set_title(self):
 		self.title = _('{0} - {1}').format(self.patient_name or self.patient, self.procedure_template)[:100]
 
+	@frappe.whitelist()
 	def complete_procedure(self):
 		if self.consume_stock and self.items:
 			stock_entry = make_stock_entry(self)
@@ -96,6 +97,7 @@
 		if self.consume_stock and self.items:
 			return stock_entry
 
+	@frappe.whitelist()
 	def start_procedure(self):
 		allow_start = self.set_actual_qty()
 		if allow_start:
@@ -116,6 +118,7 @@
 
 		return allow_start
 
+	@frappe.whitelist()
 	def make_material_receipt(self, submit=False):
 		stock_entry = frappe.new_doc('Stock Entry')
 
diff --git a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
index e731908..3a299ed 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_entry/inpatient_medication_entry.py
@@ -14,6 +14,7 @@
 	def validate(self):
 		self.validate_medication_orders()
 
+	@frappe.whitelist()
 	def get_medication_orders(self):
 		# pull inpatient medication orders based on selected filters
 		orders = get_pending_medication_orders(self)
diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py
index 33cbbec..b379e98 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_order/inpatient_medication_order.py
@@ -57,6 +57,7 @@
 
 		self.db_set('status', status)
 
+	@frappe.whitelist()
 	def add_order_entries(self, order):
 		if order.get('drug_code'):
 			dosage = frappe.get_doc('Prescription Dosage', order.get('dosage'))
diff --git a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
index a21caca..21776d2 100644
--- a/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
+++ b/erpnext/healthcare/doctype/inpatient_medication_order/test_inpatient_medication_order.py
@@ -81,15 +81,8 @@
 			self.ip_record.reload()
 			discharge_patient(self.ip_record)
 
-		for entry in frappe.get_all('Inpatient Medication Entry'):
-			doc = frappe.get_doc('Inpatient Medication Entry', entry.name)
-			doc.cancel()
-			doc.delete()
-
-		for entry in frappe.get_all('Inpatient Medication Order'):
-			doc = frappe.get_doc('Inpatient Medication Order', entry.name)
-			doc.cancel()
-			doc.delete()
+		for doctype in ["Inpatient Medication Entry", "Inpatient Medication Order"]:
+			frappe.db.sql("delete from `tab{doctype}`".format(doctype=doctype))
 
 def create_dosage_form():
 	if not frappe.db.exists('Dosage Form', 'Tablet'):
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
index 5ced845..aaf0e85 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.json
@@ -53,7 +53,7 @@
   "discharge_ordered_date",
   "discharge_practitioner",
   "discharge_encounter",
-  "discharge_date",
+  "discharge_datetime",
   "cb_discharge",
   "discharge_instructions",
   "followup_date",
@@ -404,14 +404,15 @@
    "permlevel": 1
   },
   {
-   "fieldname": "discharge_date",
-   "fieldtype": "Date",
+   "fieldname": "discharge_datetime",
+   "fieldtype": "Datetime",
    "label": "Discharge Date",
    "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2020-05-21 02:26:22.144575",
+ "modified": "2021-03-18 14:44:11.689956",
  "modified_by": "Administrator",
  "module": "Healthcare",
  "name": "Inpatient Record",
diff --git a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
index 88d7f0b..f4d1eaf 100644
--- a/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
+++ b/erpnext/healthcare/doctype/inpatient_record/inpatient_record.py
@@ -53,12 +53,15 @@
 				+ """ <b><a href="/app/Form/Inpatient Record/{0}">{0}</a></b>""".format(ip_record[0].name))
 			frappe.throw(msg)
 
+	@frappe.whitelist()
 	def admit(self, service_unit, check_in, expected_discharge=None):
 		admit_patient(self, service_unit, check_in, expected_discharge)
 
+	@frappe.whitelist()
 	def discharge(self):
 		discharge_patient(self)
 
+	@frappe.whitelist()
 	def transfer(self, service_unit, check_in, leave_from):
 		if leave_from:
 			patient_leave_service_unit(self, check_in, leave_from)
@@ -151,7 +154,7 @@
 
 def discharge_patient(inpatient_record):
 	validate_inpatient_invoicing(inpatient_record)
-	inpatient_record.discharge_date = today()
+	inpatient_record.discharge_datetime = now_datetime()
 	inpatient_record.status = "Discharged"
 
 	inpatient_record.save(ignore_permissions = True)
diff --git a/erpnext/healthcare/doctype/patient/patient.py b/erpnext/healthcare/doctype/patient/patient.py
index 8603f97..789d452 100644
--- a/erpnext/healthcare/doctype/patient/patient.py
+++ b/erpnext/healthcare/doctype/patient/patient.py
@@ -111,6 +111,7 @@
 			age_str = str(age.years) + ' ' + _("Years(s)") + ' ' + str(age.months) + ' ' + _("Month(s)") + ' ' + str(age.days) + ' ' + _("Day(s)")
 		return age_str
 
+	@frappe.whitelist()
 	def invoice_patient_registration(self):
 		if frappe.db.get_single_value('Healthcare Settings', 'registration_fee'):
 			company = frappe.defaults.get_user_default('company')
diff --git a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
index 1f76cd6..cdd4ad3 100755
--- a/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
+++ b/erpnext/healthcare/doctype/patient_appointment/patient_appointment.py
@@ -113,6 +113,7 @@
 		if fee_validity:
 			frappe.msgprint(_('{0} has fee validity till {1}').format(self.patient, fee_validity.valid_till))
 
+	@frappe.whitelist()
 	def get_therapy_types(self):
 		if not self.therapy_plan:
 			return
diff --git a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js
index c7074e8..f28d32c 100644
--- a/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js
+++ b/erpnext/healthcare/doctype/patient_assessment/patient_assessment.js
@@ -39,11 +39,13 @@
 	},
 
 	set_score_range: function(frm) {
-		let options = [];
+		let options = [''];
 		for(let i = frm.doc.scale_min; i <= frm.doc.scale_max; i++) {
 			options.push(i);
 		}
-		frappe.meta.get_docfield('Patient Assessment Sheet', 'score', frm.doc.name).options = [''].concat(options);
+		frm.fields_dict.assessment_sheet.grid.update_docfield_property(
+			'score', 'options', options
+		);
 	},
 
 	calculate_total_score: function(frm, cdt, cdn) {
@@ -83,4 +85,4 @@
 	score: function(frm, cdt, cdn) {
 		frm.events.calculate_total_score(frm, cdt, cdn);
 	}
-});
\ No newline at end of file
+});
diff --git a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
index 2e8c994..887d58a 100644
--- a/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
+++ b/erpnext/healthcare/doctype/patient_history_settings/patient_history_settings.py
@@ -34,6 +34,7 @@
 				frappe.throw(_('Row #{0}: Field {1} in Document Type {2} is not a Date / Datetime field.').format(
 					entry.idx, frappe.bold(entry.date_fieldname), frappe.bold(entry.document_type)))
 
+	@frappe.whitelist()
 	def get_doctype_fields(self, document_type, fields):
 		multicheck_fields = []
 		doc_fields = frappe.get_meta(document_type).fields
@@ -49,6 +50,7 @@
 
 		return multicheck_fields
 
+	@frappe.whitelist()
 	def get_date_field_for_dt(self, document_type):
 		meta = frappe.get_meta(document_type)
 		date_fields = meta.get('fields', {
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
index d1f72d6..42e231d 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.js
@@ -58,8 +58,12 @@
 		}
 
 		if (frm.doc.therapy_plan_template) {
-			frappe.meta.get_docfield('Therapy Plan Detail', 'therapy_type', frm.doc.name).read_only = 1;
-			frappe.meta.get_docfield('Therapy Plan Detail', 'no_of_sessions', frm.doc.name).read_only = 1;
+			frm.fields_dict.therapy_plan_details.grid.update_docfield_property(
+				'therapy_type', 'read_only', 1
+			);
+			frm.fields_dict.therapy_plan_details.grid.update_docfield_property(
+				'no_of_sessions', 'read_only', 1
+			);
 		}
 	},
 
@@ -126,4 +130,4 @@
 		frm.set_value('total_sessions', total);
 		refresh_field('total_sessions');
 	}
-});
\ No newline at end of file
+});
diff --git a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
index ac01c60..e209660 100644
--- a/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
+++ b/erpnext/healthcare/doctype/therapy_plan/therapy_plan.py
@@ -33,6 +33,7 @@
 		self.db_set('total_sessions', total_sessions)
 		self.db_set('total_sessions_completed', total_sessions_completed)
 
+	@frappe.whitelist()
 	def set_therapy_details_from_template(self):
 		# Add therapy types in the child table
 		self.set('therapy_plan_details', [])
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 4b3597a..98d5966 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -195,6 +195,10 @@
 	{"name": "call-disconnect", "src": "/assets/erpnext/sounds/call-disconnect.mp3", "volume": 0.2},
 ]
 
+has_upload_permission = {
+	"Employee": "erpnext.hr.doctype.employee.employee.has_upload_permission"
+}
+
 has_website_permission = {
 	"Sales Order": "erpnext.controllers.website_list_for_contact.has_website_permission",
 	"Quotation": "erpnext.controllers.website_list_for_contact.has_website_permission",
@@ -256,7 +260,11 @@
 			"erpnext.regional.italy.utils.sales_invoice_on_cancel",
 			"erpnext.erpnext_integrations.taxjar_integration.delete_transaction"
 		],
-		"on_trash": "erpnext.regional.check_deletion_permission"
+		"on_trash": "erpnext.regional.check_deletion_permission",
+		"validate": [
+			"erpnext.regional.india.utils.validate_document_name",
+			"erpnext.regional.india.utils.update_taxable_values"
+		]
 	},
 	"Purchase Invoice": {
 		"validate": [
@@ -278,9 +286,6 @@
 	('Sales Invoice', 'Sales Order', 'Delivery Note', 'Purchase Invoice', 'Purchase Order', 'Purchase Receipt'): {
 		'validate': ['erpnext.regional.india.utils.set_place_of_supply']
 	},
-	('Sales Invoice', 'Purchase Invoice'): {
-		'validate': ['erpnext.regional.india.utils.validate_document_name']
-	},
 	"Contact": {
 		"on_trash": "erpnext.support.doctype.issue.issue.update_issue",
 		"after_insert": "erpnext.telephony.doctype.call_log.call_log.link_existing_conversations",
@@ -324,6 +329,7 @@
 		"erpnext.hr.doctype.shift_type.shift_type.process_auto_attendance_for_all_shifts",
 		"erpnext.support.doctype.issue.issue.set_service_level_agreement_variance",
 		"erpnext.erpnext_integrations.connectors.shopify_connection.sync_old_orders",
+		"erpnext.stock.doctype.repost_item_valuation.repost_item_valuation.repost_entries"
 	],
 	"daily": [
 		"erpnext.stock.reorder_item.reorder_item",
diff --git a/erpnext/hr/doctype/attendance_request/test_attendance_request.py b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
index 92b1eae..3c42bd9 100644
--- a/erpnext/hr/doctype/attendance_request/test_attendance_request.py
+++ b/erpnext/hr/doctype/attendance_request/test_attendance_request.py
@@ -8,6 +8,8 @@
 from frappe.utils import nowdate
 from datetime import date
 
+test_dependencies = ["Employee"]
+
 class TestAttendanceRequest(unittest.TestCase):
 	def setUp(self):
 		for doctype in ["Attendance Request", "Attendance"]:
@@ -56,4 +58,4 @@
 		self.assertEqual(attendance.docstatus, 2)
 
 def get_employee():
-	return frappe.get_doc("Employee", "_T-Employee-00001")
\ No newline at end of file
+	return frappe.get_doc("Employee", "_T-Employee-00001")
diff --git a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
index 7a9727f..aa5a67f 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/compensatory_leave_request.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe
 from frappe import _
-from frappe.utils import date_diff, add_days, getdate, cint
+from frappe.utils import date_diff, add_days, getdate, cint, format_date
 from frappe.model.document import Document
 from erpnext.hr.utils import validate_dates, validate_overlap, get_leave_period, \
 	get_holidays_for_employee, create_additional_leave_ledger_entry
@@ -40,7 +40,12 @@
 	def validate_holidays(self):
 		holidays = get_holidays_for_employee(self.employee, self.work_from_date, self.work_end_date)
 		if len(holidays) < date_diff(self.work_end_date, self.work_from_date) + 1:
-			frappe.throw(_("Compensatory leave request days not in valid holidays"))
+			if date_diff(self.work_end_date, self.work_from_date):
+				msg = _("The days between {0} to {1} are not valid holidays.").format(frappe.bold(format_date(self.work_from_date)), frappe.bold(format_date(self.work_end_date)))
+			else:
+				msg = _("{0} is not a holiday.").format(frappe.bold(format_date(self.work_from_date)))
+
+			frappe.throw(msg)
 
 	def on_submit(self):
 		company = frappe.db.get_value("Employee", self.employee, "company")
@@ -63,7 +68,7 @@
 				leave_allocation = self.create_leave_allocation(leave_period, date_difference)
 			self.leave_allocation=leave_allocation.name
 		else:
-			frappe.throw(_("There is no leave period in between {0} and {1}").format(self.work_from_date, self.work_end_date))
+			frappe.throw(_("There is no leave period in between {0} and {1}").format(format_date(self.work_from_date), format_date(self.work_end_date)))
 
 	def on_cancel(self):
 		if self.leave_allocation:
diff --git a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
index 1615ab3..74ce301 100644
--- a/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
+++ b/erpnext/hr/doctype/compensatory_leave_request/test_compensatory_leave_request.py
@@ -10,6 +10,8 @@
 from erpnext.hr.doctype.leave_period.test_leave_period import create_leave_period
 from erpnext.hr.doctype.leave_application.leave_application import get_leave_balance_on
 
+test_dependencies = ["Employee"]
+
 class TestCompensatoryLeaveRequest(unittest.TestCase):
 	def setUp(self):
 		frappe.db.sql(''' delete from `tabCompensatory Leave Request`''')
@@ -129,4 +131,4 @@
 		],
 		"holiday_list_name": "_Test Compensatory Leave"
 	})
-	holiday_list.save()
\ No newline at end of file
+	holiday_list.save()
diff --git a/erpnext/hr/doctype/employee/employee.py b/erpnext/hr/doctype/employee/employee.py
index d0e7d05..ed7d588 100755
--- a/erpnext/hr/doctype/employee/employee.py
+++ b/erpnext/hr/doctype/employee/employee.py
@@ -8,7 +8,7 @@
 from frappe.model.naming import set_name_by_naming_series
 from frappe import throw, _, scrub
 from frappe.permissions import add_user_permission, remove_user_permission, \
-	set_user_permission_if_allowed, has_permission
+	set_user_permission_if_allowed, has_permission, get_doc_permissions
 from frappe.model.document import Document
 from erpnext.utilities.transaction_base import delete_events
 from frappe.utils.nestedset import NestedSet
@@ -66,7 +66,7 @@
 	def validate_user_details(self):
 		data = frappe.db.get_value('User',
 			self.user_id, ['enabled', 'user_image'], as_dict=1)
-		if data.get("user_image"):
+		if data.get("user_image") and self.image == '':
 			self.image = data.get("user_image")
 		self.validate_for_enabled_user_id(data.get("enabled", 0))
 		self.validate_duplicate_user_id()
@@ -80,6 +80,7 @@
 			self.update_user()
 			self.update_user_permissions()
 		self.reset_employee_emails_cache()
+		self.update_approver_role()
 
 	def update_user_permissions(self):
 		if not self.create_user_permission: return
@@ -145,6 +146,17 @@
 
 		user.save()
 
+	def update_approver_role(self):
+		if self.leave_approver:
+			user = frappe.get_doc("User", self.leave_approver)
+			user.flags.ignore_permissions = True
+			user.add_roles("Leave Approver")
+
+		if self.expense_approver:
+			user = frappe.get_doc("User", self.expense_approver)
+			user.flags.ignore_permissions = True
+			user.add_roles("Expense Approver")
+
 	def validate_date(self):
 		if self.date_of_birth and getdate(self.date_of_birth) > getdate(today()):
 			throw(_("Date of Birth cannot be greater than today."))
@@ -501,3 +513,10 @@
 		'allow': 'Employee',
 		'for_value': employee_name
 	})
+
+def has_upload_permission(doc, ptype='read', user=None):
+	if not user:
+		user = frappe.session.user
+	if get_doc_permissions(doc, user=user, ptype=ptype).get(ptype):
+		return True
+	return doc.user_id == user
\ No newline at end of file
diff --git a/erpnext/hr/doctype/employee_advance/employee_advance.json b/erpnext/hr/doctype/employee_advance/employee_advance.json
index cf6b540..04f98d1 100644
--- a/erpnext/hr/doctype/employee_advance/employee_advance.json
+++ b/erpnext/hr/doctype/employee_advance/employee_advance.json
@@ -181,7 +181,6 @@
    "read_only": 1
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
    "fieldname": "currency",
    "fieldtype": "Link",
@@ -201,7 +200,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-25 12:01:55.980721",
+ "modified": "2021-03-31 14:42:47.321368",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Employee Advance",
diff --git a/erpnext/hr/doctype/expense_claim/expense_claim.py b/erpnext/hr/doctype/expense_claim/expense_claim.py
index bf893d5..5010fc3 100644
--- a/erpnext/hr/doctype/expense_claim/expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/expense_claim.py
@@ -6,7 +6,7 @@
 from frappe import _
 from frappe.utils import get_fullname, flt, cstr, get_link_to_form
 from frappe.model.document import Document
-from erpnext.hr.utils import set_employee_name
+from erpnext.hr.utils import set_employee_name, share_doc_with_approver
 from erpnext.accounts.party import get_party_account
 from erpnext.accounts.general_ledger import make_gl_entries
 from erpnext.accounts.doctype.sales_invoice.sales_invoice import get_bank_cash_account
@@ -53,6 +53,9 @@
 		elif self.docstatus == 1 and self.approval_status == 'Rejected':
 			self.status = 'Rejected'
 
+	def on_update(self):
+		share_doc_with_approver(self, self.expense_approver)
+
 	def set_payable_account(self):
 		if not self.payable_account and not self.is_paid:
 			self.payable_account = frappe.get_cached_value('Company', self.company, 'default_expense_claim_payable_account')
@@ -211,6 +214,7 @@
 			self.total_claimed_amount += flt(d.amount)
 			self.total_sanctioned_amount += flt(d.sanctioned_amount)
 
+	@frappe.whitelist()
 	def calculate_taxes(self):
 		self.total_taxes_and_charges = 0
 		for tax in self.taxes:
diff --git a/erpnext/hr/doctype/expense_claim/test_expense_claim.py b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
index f9e3a44..3f22ca2 100644
--- a/erpnext/hr/doctype/expense_claim/test_expense_claim.py
+++ b/erpnext/hr/doctype/expense_claim/test_expense_claim.py
@@ -95,12 +95,12 @@
 	def test_rejected_expense_claim(self):
 		payable_account = get_payable_account(company_name)
 		expense_claim = frappe.get_doc({
-			 "doctype": "Expense Claim",
-			 "employee": "_T-Employee-00001",
-			 "payable_account": payable_account,
-			 "approval_status": "Rejected",
-			 "expenses":
-			 	[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
+			"doctype": "Expense Claim",
+			"employee": "_T-Employee-00001",
+			"payable_account": payable_account,
+			"approval_status": "Rejected",
+			"expenses":
+				[{ "expense_type": "Travel", "default_account": "Travel Expenses - _TC4", "amount": 300, "sanctioned_amount": 200 }]
 		})
 		expense_claim.submit()
 
@@ -110,6 +110,34 @@
 		gl_entry = frappe.get_all('GL Entry', {'voucher_type': 'Expense Claim', 'voucher_no': expense_claim.name})
 		self.assertEquals(len(gl_entry), 0)
 
+	def test_expense_approver_perms(self):
+		user = "test_approver_perm_emp@example.com"
+		make_employee(user, "_Test Company")
+
+		# check doc shared
+		payable_account = get_payable_account("_Test Company")
+		expense_claim = make_expense_claim(payable_account, 300, 200, "_Test Company", "Travel Expenses - _TC", do_not_submit=True)
+		expense_claim.expense_approver = user
+		expense_claim.save()
+		self.assertTrue(expense_claim.name in frappe.share.get_shared("Expense Claim", user))
+
+		# check shared doc revoked
+		expense_claim.reload()
+		expense_claim.expense_approver = "test@example.com"
+		expense_claim.save()
+		self.assertTrue(expense_claim.name not in frappe.share.get_shared("Expense Claim", user))
+
+		expense_claim.reload()
+		expense_claim.expense_approver = user
+		expense_claim.save()
+
+		frappe.set_user(user)
+		expense_claim.reload()
+		expense_claim.status = "Approved"
+		expense_claim.submit()
+		frappe.set_user("Administrator")
+
+
 def get_payable_account(company):
 	return frappe.get_cached_value('Company', company, 'default_payable_account')
 
@@ -133,21 +161,21 @@
 
 	currency, cost_center = frappe.db.get_value('Company', company, ['default_currency', 'cost_center'])
 	expense_claim = {
-		 "doctype": "Expense Claim",
-		 "employee": employee,
-		 "payable_account": payable_account,
-		 "approval_status": "Approved",
-		 "company": company,
-		'currency': currency,
-		 "expenses": [{
+		"doctype": "Expense Claim",
+		"employee": employee,
+		"payable_account": payable_account,
+		"approval_status": "Approved",
+		"company": company,
+		"currency": currency,
+		"expenses": [{
 			"expense_type": "Travel",
 			"default_account": account,
 			"currency": currency,
 			"amount": amount,
 			"sanctioned_amount": sanctioned_amount,
 			"cost_center": cost_center
-			}]
-		}
+		}]
+	}
 	if taxes:
 		expense_claim.update(taxes)
 
diff --git a/erpnext/hr/doctype/job_applicant/test_job_applicant.py b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
index 6d275c8..8728342 100644
--- a/erpnext/hr/doctype/job_applicant/test_job_applicant.py
+++ b/erpnext/hr/doctype/job_applicant/test_job_applicant.py
@@ -13,11 +13,21 @@
 
 def create_job_applicant(**args):
 	args = frappe._dict(args)
-	job_applicant = frappe.get_doc({
-		"doctype": "Job Applicant",
+
+	filters = {
 		"applicant_name": args.applicant_name or "_Test Applicant",
 		"email_id": args.email_id or "test_applicant@example.com",
+	}
+
+	if frappe.db.exists("Job Applicant", filters):
+		return frappe.get_doc("Job Applicant", filters)
+
+	job_applicant = frappe.get_doc({
+		"doctype": "Job Applicant",
 		"status": args.status or "Open"
 	})
+
+	job_applicant.update(filters)
 	job_applicant.save()
-	return job_applicant
\ No newline at end of file
+
+	return job_applicant
diff --git a/erpnext/hr/doctype/job_offer/test_job_offer.py b/erpnext/hr/doctype/job_offer/test_job_offer.py
index 8886596..690a692 100644
--- a/erpnext/hr/doctype/job_offer/test_job_offer.py
+++ b/erpnext/hr/doctype/job_offer/test_job_offer.py
@@ -13,14 +13,15 @@
 
 class TestJobOffer(unittest.TestCase):
 	def test_job_offer_creation_against_vacancies(self):
-		create_staffing_plan(staffing_details=[{
-			"designation": "Designer",
+		frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
+		job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
+		job_offer = create_job_offer(job_applicant=job_applicant.name, designation="UX Designer")
+
+		create_staffing_plan(name='Test No Vacancies', staffing_details=[{
+			"designation": "UX Designer",
 			"vacancies": 0,
 			"estimated_cost_per_position": 5000
 		}])
-		frappe.db.set_value("HR Settings", None, "check_vacancies", 1)
-		job_applicant = create_job_applicant(email_id="test_job_offer@example.com")
-		job_offer = create_job_offer(job_applicant=job_applicant.name, designation="Researcher")
 		self.assertRaises(frappe.ValidationError, job_offer.submit)
 
 		# test creation of job offer when vacancies are not present
diff --git a/erpnext/hr/doctype/leave_allocation/leave_allocation.py b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
index 69d605d..11302ca 100755
--- a/erpnext/hr/doctype/leave_allocation/leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/leave_allocation.py
@@ -99,6 +99,7 @@
 				.format(formatdate(future_allocation[0].from_date), future_allocation[0].name),
 					BackDatedAllocationError)
 
+	@frappe.whitelist()
 	def set_total_leaves_allocated(self):
 		self.unused_leaves = get_carry_forwarded_leaves(self.employee,
 			self.leave_type, self.from_date, self.carry_forward)
diff --git a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
index 26f077a..0b71036 100644
--- a/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
+++ b/erpnext/hr/doctype/leave_allocation/test_leave_allocation.py
@@ -6,6 +6,10 @@
 from erpnext.hr.doctype.leave_ledger_entry.leave_ledger_entry import process_expired_allocation, expire_allocation
 
 class TestLeaveAllocation(unittest.TestCase):
+	@classmethod
+	def setUpClass(cls):
+		frappe.db.sql("delete from `tabLeave Period`")
+
 	def test_overlapping_allocation(self):
 		frappe.db.sql("delete from `tabLeave Allocation`")
 
@@ -177,4 +181,4 @@
 	})
 	return leave_allocation
 
-test_dependencies = ["Employee", "Leave Type"]
\ No newline at end of file
+test_dependencies = ["Employee", "Leave Type"]
diff --git a/erpnext/hr/doctype/leave_application/leave_application.py b/erpnext/hr/doctype/leave_application/leave_application.py
index 350cead..0bf551e 100755
--- a/erpnext/hr/doctype/leave_application/leave_application.py
+++ b/erpnext/hr/doctype/leave_application/leave_application.py
@@ -6,7 +6,7 @@
 from frappe import _
 from frappe.utils import cint, cstr, date_diff, flt, formatdate, getdate, get_link_to_form, \
 	comma_or, get_fullname, add_days, nowdate, get_datetime_str
-from erpnext.hr.utils import set_employee_name, get_leave_period
+from erpnext.hr.utils import set_employee_name, get_leave_period, share_doc_with_approver
 from erpnext.hr.doctype.leave_block_list.leave_block_list import get_applicable_block_dates
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 from erpnext.buying.doctype.supplier_scorecard.supplier_scorecard import daterange
@@ -43,6 +43,8 @@
 			if frappe.db.get_single_value("HR Settings", "send_leave_notification"):
 				self.notify_leave_approver()
 
+		share_doc_with_approver(self, self.leave_approver)
+
 	def on_submit(self):
 		if self.status == "Open":
 			frappe.throw(_("Only Leave Applications with status 'Approved' and 'Rejected' can be submitted"))
@@ -417,6 +419,7 @@
 			))
 			create_leave_ledger_entry(self, args, submit)
 
+
 def get_allocation_expiry(employee, leave_type, to_date, from_date):
 	''' Returns expiry of carry forward allocation in leave ledger entry '''
 	expiry =  frappe.get_all("Leave Ledger Entry",
diff --git a/erpnext/hr/doctype/leave_application/test_leave_application.py b/erpnext/hr/doctype/leave_application/test_leave_application.py
index 53b7a39..b54c971 100644
--- a/erpnext/hr/doctype/leave_application/test_leave_application.py
+++ b/erpnext/hr/doctype/leave_application/test_leave_application.py
@@ -11,8 +11,9 @@
 from erpnext.hr.doctype.leave_type.test_leave_type import create_leave_type
 from erpnext.hr.doctype.leave_allocation.test_leave_allocation import create_leave_allocation
 from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
+from erpnext.hr.doctype.employee.test_employee import make_employee
 
-test_dependencies = ["Leave Allocation", "Leave Block List"]
+test_dependencies = ["Leave Allocation", "Leave Block List", "Employee"]
 
 _test_records = [
  {
@@ -56,6 +57,7 @@
 	@classmethod
 	def setUpClass(cls):
 		set_leave_approver()
+		frappe.db.sql("delete from tabAttendance where employee='_T-Employee-00001'")
 
 	def tearDown(self):
 		frappe.set_user("Administrator")
@@ -230,8 +232,9 @@
 	def test_optional_leave(self):
 		leave_period = get_leave_period()
 		today = nowdate()
-		from datetime import date
 		holiday_list = 'Test Holiday List for Optional Holiday'
+		optional_leave_date = add_days(today, 7)
+
 		if not frappe.db.exists('Holiday List', holiday_list):
 			frappe.get_doc(dict(
 				doctype = 'Holiday List',
@@ -239,7 +242,7 @@
 				from_date = add_months(today, -6),
 				to_date = add_months(today, 6),
 				holidays = [
-					dict(holiday_date = today, description = 'Test')
+					dict(holiday_date = optional_leave_date, description = 'Test')
 				]
 			)).insert()
 		employee = get_employee()
@@ -255,7 +258,7 @@
 
 		allocate_leaves(employee, leave_period, leave_type, 10)
 
-		date = add_days(today, - 1)
+		date = add_days(today, 6)
 
 		leave_application = frappe.get_doc(dict(
 			doctype = 'Leave Application',
@@ -270,14 +273,14 @@
 		# can only apply on optional holidays
 		self.assertRaises(NotAnOptionalHoliday, leave_application.insert)
 
-		leave_application.from_date = today
-		leave_application.to_date = today
+		leave_application.from_date = optional_leave_date
+		leave_application.to_date = optional_leave_date
 		leave_application.status = "Approved"
 		leave_application.insert()
 		leave_application.submit()
 
 		# check leave balance is reduced
-		self.assertEqual(get_leave_balance_on(employee.name, leave_type, today), 9)
+		self.assertEqual(get_leave_balance_on(employee.name, leave_type, optional_leave_date), 9)
 
 	def test_leaves_allowed(self):
 		employee = get_employee()
@@ -341,7 +344,7 @@
 			to_date = add_days(date, 4),
 			company = "_Test Company",
 			docstatus = 1,
-            status = "Approved"
+			status = "Approved"
 		))
 
 		self.assertRaises(frappe.ValidationError, leave_application.insert)
@@ -363,7 +366,7 @@
 			to_date = add_days(date, 4),
 			company = "_Test Company",
 			docstatus = 1,
-            status = "Approved"
+			status = "Approved"
 		))
 
 		self.assertTrue(leave_application.insert())
@@ -393,7 +396,7 @@
 			to_date = add_days(date, 4),
 			company = "_Test Company",
 			docstatus = 1,
-            status = "Approved"
+			status = "Approved"
 		))
 
 		self.assertRaises(frappe.ValidationError, leave_application.insert)
@@ -508,7 +511,7 @@
 			description = "_Test Reason",
 			company = "_Test Company",
 			docstatus = 1,
-            status = "Approved"
+			status = "Approved"
 		))
 		leave_application.submit()
 		leave_ledger_entry = frappe.get_all('Leave Ledger Entry', fields='*', filters=dict(transaction_name=leave_application.name))
@@ -540,7 +543,7 @@
 			description = "_Test Reason",
 			company = "_Test Company",
 			docstatus = 1,
-            status = "Approved"
+			status = "Approved"
 		))
 		leave_application.submit()
 
@@ -565,6 +568,48 @@
 
 		self.assertEquals(get_leave_balance_on(employee.name, leave_type.name, add_days(nowdate(), -85), add_days(nowdate(), -84)), 0)
 
+	def test_leave_approver_perms(self):
+		employee = get_employee()
+		user = "test_approver_perm_emp@example.com"
+		make_employee(user, "_Test Company")
+
+		# set approver for employee
+		employee.reload()
+		employee.leave_approver = user
+		employee.save()
+		self.assertTrue("Leave Approver" in frappe.get_roles(user))
+
+		make_allocation_record(employee.name)
+
+		application = self.get_application(_test_records[0])
+		application.from_date = '2018-01-01'
+		application.to_date = '2018-01-03'
+		application.leave_approver = user
+		application.insert()
+		self.assertTrue(application.name in frappe.share.get_shared("Leave Application", user))
+
+		# check shared doc revoked
+		application.reload()
+		application.leave_approver = "test@example.com"
+		application.save()
+		self.assertTrue(application.name not in frappe.share.get_shared("Leave Application", user))
+
+		application.reload()
+		application.leave_approver = user
+		application.save()
+
+		frappe.set_user(user)
+		application.reload()
+		application.status = "Approved"
+		application.submit()
+
+		# unset leave approver
+		frappe.set_user("Administrator")
+		employee.reload()
+		employee.leave_approver = ""
+		employee.save()
+
+
 def create_carry_forwarded_allocation(employee, leave_type):
 		# initial leave allocation
 		leave_allocation = create_leave_allocation(
@@ -639,4 +684,4 @@
 		"docstatus": 1
 	}).insert()
 
-	allocate_leave.submit()
\ No newline at end of file
+	allocate_leave.submit()
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.json b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
index 83eeae3..dcb5874 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.json
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.json
@@ -130,7 +130,6 @@
    "read_only": 1
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
    "fieldname": "currency",
    "fieldtype": "Link",
@@ -155,7 +154,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-25 11:56:06.777241",
+ "modified": "2021-03-31 14:45:27.948207",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "Leave Encashment",
diff --git a/erpnext/hr/doctype/leave_encashment/leave_encashment.py b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
index 4c1a465..e041b7f 100644
--- a/erpnext/hr/doctype/leave_encashment/leave_encashment.py
+++ b/erpnext/hr/doctype/leave_encashment/leave_encashment.py
@@ -63,6 +63,7 @@
 				frappe.db.get_value('Leave Allocation', self.leave_allocation, 'total_leaves_encashed') - self.encashable_days)
 		self.create_leave_ledger_entry(submit=False)
 
+	@frappe.whitelist()
 	def get_leave_details_for_encashment(self):
 		salary_structure = get_assigned_salary_structure(self.employee, self.encashment_date or getdate(nowdate()))
 		if not salary_structure:
diff --git a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
index 63559c4..cf13036 100644
--- a/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
+++ b/erpnext/hr/doctype/leave_ledger_entry/leave_ledger_entry.py
@@ -34,8 +34,8 @@
 	""", (ledger.employee, ledger.leave_type, ledger.from_date, ledger.to_date))
 
 	if leave_application_records:
-		frappe.throw(_("Leave allocation %s is linked with leave application %s"
-			% (ledger.transaction_name, ', '.join(leave_application_records))))
+		frappe.throw(_("Leave allocation {0} is linked with the Leave Application {1}").format(
+			ledger.transaction_name, ', '.join(leave_application_records)))
 
 def create_leave_ledger_entry(ref_doc, args, submit=True):
 	ledger = frappe._dict(
@@ -52,7 +52,9 @@
 	ledger.update(args)
 
 	if submit:
-		frappe.get_doc(ledger).submit()
+		doc = frappe.get_doc(ledger)
+		doc.flags.ignore_permissions = 1
+		doc.submit()
 	else:
 		delete_ledger_entry(ledger)
 
diff --git a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
index 4064c56..462b81d 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/leave_policy_assignment.py
@@ -36,6 +36,7 @@
 			frappe.throw(_("Leave Policy: {0} already assigned for Employee {1} for period {2} to {3}")
 				.format(bold(self.leave_policy), bold(self.employee), bold(formatdate(self.effective_from)), bold(formatdate(self.effective_to))))
 
+	@frappe.whitelist()
 	def grant_leave_alloc_for_employee(self):
 		if self.leaves_allocated:
 			frappe.throw(_("Leave already have been assigned for this Leave Policy Assignment"))
diff --git a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
index c7bc6fb..838e794 100644
--- a/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
+++ b/erpnext/hr/doctype/leave_policy_assignment/test_leave_policy_assignment.py
@@ -9,6 +9,8 @@
 from erpnext.hr.doctype.leave_policy_assignment.leave_policy_assignment import create_assignment_for_multiple_employees
 from erpnext.hr.doctype.leave_policy.test_leave_policy import create_leave_policy
 
+test_dependencies = ["Employee"]
+
 class TestLeavePolicyAssignment(unittest.TestCase):
 
 	def setUp(self):
diff --git a/erpnext/hr/doctype/shift_request/shift_request.py b/erpnext/hr/doctype/shift_request/shift_request.py
index 473193d..177c45e 100644
--- a/erpnext/hr/doctype/shift_request/shift_request.py
+++ b/erpnext/hr/doctype/shift_request/shift_request.py
@@ -7,6 +7,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.utils import formatdate, getdate
+from erpnext.hr.utils import share_doc_with_approver
 
 class OverlapError(frappe.ValidationError): pass
 
@@ -17,6 +18,9 @@
 		self.validate_approver()
 		self.validate_default_shift()
 
+	def on_update(self):
+		share_doc_with_approver(self, self.approver)
+
 	def on_submit(self):
 		if self.status not in ["Approved", "Rejected"]:
 			frappe.throw(_("Only Shift Request with status 'Approved' and 'Rejected' can be submitted"))
@@ -29,6 +33,7 @@
 			if self.to_date:
 				assignment_doc.end_date = self.to_date
 			assignment_doc.shift_request = self.name
+			assignment_doc.flags.ignore_permissions = 1
 			assignment_doc.insert()
 			assignment_doc.submit()
 
diff --git a/erpnext/hr/doctype/shift_request/test_shift_request.py b/erpnext/hr/doctype/shift_request/test_shift_request.py
index 3dcfcbf..9c0d8e3 100644
--- a/erpnext/hr/doctype/shift_request/test_shift_request.py
+++ b/erpnext/hr/doctype/shift_request/test_shift_request.py
@@ -6,6 +6,9 @@
 import frappe
 import unittest
 from frappe.utils import nowdate, add_days
+from erpnext.hr.doctype.employee.test_employee import make_employee
+
+test_dependencies = ["Shift Type"]
 
 class TestShiftRequest(unittest.TestCase):
 	def setUp(self):
@@ -17,19 +20,8 @@
 		set_shift_approver(department)
 		approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
 
-		shift_request = frappe.get_doc({
-			"doctype": "Shift Request",
-			"shift_type": "Day Shift",
-			"company": "_Test Company",
-			"employee": "_T-Employee-00001",
-			"employee_name": "_Test Employee",
-			"from_date": nowdate(),
-			"to_date": add_days(nowdate(), 10),
-			"approver": approver,
-			"status": "Approved"
-		})
-		shift_request.insert()
-		shift_request.submit()
+		shift_request = make_shift_request(approver)
+
 		shift_assignments = frappe.db.sql('''
 				SELECT shift_request, employee
 				FROM `tabShift Assignment`
@@ -42,8 +34,65 @@
 			shift_assignment_doc = frappe.get_doc("Shift Assignment", {"shift_request": d.get('shift_request')})
 			self.assertEqual(shift_assignment_doc.docstatus, 2)
 
+	def test_shift_request_approver_perms(self):
+		employee = frappe.get_doc("Employee", "_T-Employee-00001")
+		user = "test_approver_perm_emp@example.com"
+		make_employee(user, "_Test Company")
+
+		# set approver for employee
+		employee.reload()
+		employee.shift_request_approver = user
+		employee.save()
+
+		shift_request = make_shift_request(user, do_not_submit=True)
+		self.assertTrue(shift_request.name in frappe.share.get_shared("Shift Request", user))
+
+		# check shared doc revoked
+		shift_request.reload()
+		department = frappe.get_value("Employee", "_T-Employee-00001", "department")
+		set_shift_approver(department)
+		department_approver = frappe.db.sql("""select approver from `tabDepartment Approver` where parent= %s and parentfield = 'shift_request_approver'""", (department))[0][0]
+		shift_request.approver = department_approver
+		shift_request.save()
+		self.assertTrue(shift_request.name not in frappe.share.get_shared("Shift Request", user))
+
+		shift_request.reload()
+		shift_request.approver = user
+		shift_request.save()
+
+		frappe.set_user(user)
+		shift_request.reload()
+		shift_request.status = "Approved"
+		shift_request.submit()
+
+		# unset approver
+		frappe.set_user("Administrator")
+		employee.reload()
+		employee.shift_request_approver = ""
+		employee.save()
+
+
 def set_shift_approver(department):
 	department_doc = frappe.get_doc("Department", department)
 	department_doc.append('shift_request_approver',{'approver': "test1@example.com"})
 	department_doc.save()
-	department_doc.reload()
\ No newline at end of file
+	department_doc.reload()
+
+def make_shift_request(approver, do_not_submit=0):
+	shift_request = frappe.get_doc({
+		"doctype": "Shift Request",
+		"shift_type": "Day Shift",
+		"company": "_Test Company",
+		"employee": "_T-Employee-00001",
+		"employee_name": "_Test Employee",
+		"from_date": nowdate(),
+		"to_date": add_days(nowdate(), 10),
+		"approver": approver,
+		"status": "Approved"
+	}).insert()
+
+	if do_not_submit:
+		return shift_request
+
+	shift_request.submit()
+	return shift_request
\ No newline at end of file
diff --git a/erpnext/hr/doctype/shift_type/shift_type.py b/erpnext/hr/doctype/shift_type/shift_type.py
index 054e7e3..d5fdda8 100644
--- a/erpnext/hr/doctype/shift_type/shift_type.py
+++ b/erpnext/hr/doctype/shift_type/shift_type.py
@@ -15,6 +15,7 @@
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 
 class ShiftType(Document):
+	@frappe.whitelist()
 	def process_auto_attendance(self):
 		if not cint(self.enable_auto_attendance) or not self.process_attendance_after or not self.last_sync_of_checkin:
 			return
diff --git a/erpnext/hr/doctype/shift_type/test_records.json b/erpnext/hr/doctype/shift_type/test_records.json
new file mode 100644
index 0000000..9040b91
--- /dev/null
+++ b/erpnext/hr/doctype/shift_type/test_records.json
@@ -0,0 +1,8 @@
+[
+  {
+    "doctype": "Shift Type",
+    "name": "Day Shift",
+    "start_time": "9:00:00",
+    "end_time": "18:00:00"
+  }
+]
diff --git a/erpnext/hr/doctype/shift_type/test_shift_type.py b/erpnext/hr/doctype/shift_type/test_shift_type.py
index 535072a..bc4f0ea 100644
--- a/erpnext/hr/doctype/shift_type/test_shift_type.py
+++ b/erpnext/hr/doctype/shift_type/test_shift_type.py
@@ -7,14 +7,4 @@
 import unittest
 
 class TestShiftType(unittest.TestCase):
-	def test_make_shift_type(self):
-		if frappe.db.exists("Shift Type", "Day Shift"):
-			return
-		shift_type = frappe.get_doc({
-			"doctype": "Shift Type",
-			"name": "Day Shift",
-			"start_time": "9:00:00",
-			"end_time": "18:00:00"
-		})
-		shift_type.insert()
- 
\ No newline at end of file
+	pass
diff --git a/erpnext/hr/doctype/staffing_plan/staffing_plan.py b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
index 5b84d00..533149a 100644
--- a/erpnext/hr/doctype/staffing_plan/staffing_plan.py
+++ b/erpnext/hr/doctype/staffing_plan/staffing_plan.py
@@ -39,6 +39,7 @@
 			detail.current_count = designation_counts['employee_count']
 			detail.current_openings = designation_counts['job_openings']
 
+			detail.total_estimated_cost = 0
 			if detail.number_of_positions > 0:
 				if detail.vacancies > 0 and detail.estimated_cost_per_position:
 					detail.total_estimated_cost = cint(detail.vacancies) * flt(detail.estimated_cost_per_position)
diff --git a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
index e961114..f5fece8 100644
--- a/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
+++ b/erpnext/hr/report/recruitment_analytics/recruitment_analytics.py
@@ -31,7 +31,7 @@
 			"fieldtype": "Link",
 			"fieldname": "job_opening",
 			"options": "Job Opening",
-			"width": 100
+			"width": 105
 		},
 		{
 			"label": _("Job Applicant"),
@@ -44,13 +44,13 @@
 			"label": _("Applicant name"),
 			"fieldtype": "data",
 			"fieldname": "applicant_name",
-			"width": 120
+			"width": 130
 		},
 		{
 			"label": _("Application Status"),
 			"fieldtype": "Data",
 			"fieldname": "application_status",
-			"width": 100
+			"width": 150
 		},
 		{
 			"label": _("Job Offer"),
diff --git a/erpnext/hr/utils.py b/erpnext/hr/utils.py
index 0c4c1ca..190eb4f 100644
--- a/erpnext/hr/utils.py
+++ b/erpnext/hr/utils.py
@@ -504,3 +504,25 @@
 		lpa = frappe.db.get_all("Leave Policy Assignment", filters={"effective_from": getdate(), "docstatus": 1, "leaves_allocated":0})
 		for assignment in lpa:
 			frappe.get_doc("Leave Policy Assignment", assignment.name).grant_leave_alloc_for_employee()
+
+def share_doc_with_approver(doc, user):
+	# if approver does not have permissions, share
+	if not frappe.has_permission(doc=doc, ptype="submit", user=user):
+		frappe.share.add(doc.doctype, doc.name, user, submit=1,
+			flags={"ignore_share_permission": True})
+
+		frappe.msgprint(_("Shared with the user {0} with {1} access").format(
+			user, frappe.bold("submit"), alert=True))
+
+	# remove shared doc if approver changes
+	doc_before_save = doc.get_doc_before_save()
+	if doc_before_save:
+		approvers = {
+			"Leave Application": "leave_approver",
+			"Expense Claim": "expense_approver",
+			"Shift Request": "approver"
+		}
+
+		approver = approvers.get(doc.doctype)
+		if doc_before_save.get(approver) != doc.get(approver):
+			frappe.share.remove(doc.doctype, doc.name, doc_before_save.get(approver))
diff --git a/erpnext/hr/workspace/hr/hr.json b/erpnext/hr/workspace/hr/hr.json
index f650b24..f4b56a0 100644
--- a/erpnext/hr/workspace/hr/hr.json
+++ b/erpnext/hr/workspace/hr/hr.json
@@ -15,6 +15,7 @@
  "hide_custom": 0,
  "icon": "hr",
  "idx": 0,
+ "is_default": 0,
  "is_standard": 1,
  "label": "HR",
  "links": [
@@ -227,41 +228,11 @@
    "type": "Card Break"
   },
   {
-   "dependencies": "Employee",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Leave Application",
-   "link_to": "Leave Application",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "Employee",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Leave Allocation",
-   "link_to": "Leave Allocation",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "Leave Type",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Leave Policy",
-   "link_to": "Leave Policy",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Leave Period",
-   "link_to": "Leave Period",
+   "label": "Holiday List",
+   "link_to": "Holiday List",
    "link_type": "DocType",
    "onboard": 0,
    "type": "Link"
@@ -280,8 +251,28 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Holiday List",
-   "link_to": "Holiday List",
+   "label": "Leave Period",
+   "link_to": "Leave Period",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Leave Type",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Leave Policy",
+   "link_to": "Leave Policy",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Leave Policy",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Leave Policy Assignment",
+   "link_to": "Leave Policy Assignment",
    "link_type": "DocType",
    "onboard": 0,
    "type": "Link"
@@ -290,8 +281,18 @@
    "dependencies": "Employee",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Compensatory Leave Request",
-   "link_to": "Compensatory Leave Request",
+   "label": "Leave Application",
+   "link_to": "Leave Application",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Employee",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Leave Allocation",
+   "link_to": "Leave Allocation",
    "link_type": "DocType",
    "onboard": 0,
    "type": "Link"
@@ -317,12 +318,12 @@
    "type": "Link"
   },
   {
-   "dependencies": "Leave Application",
+   "dependencies": "Employee",
    "hidden": 0,
-   "is_query_report": 1,
-   "label": "Employee Leave Balance",
-   "link_to": "Employee Leave Balance",
-   "link_type": "Report",
+   "is_query_report": 0,
+   "label": "Compensatory Leave Request",
+   "link_to": "Compensatory Leave Request",
+   "link_type": "DocType",
    "onboard": 0,
    "type": "Link"
   },
@@ -384,16 +385,6 @@
    "type": "Link"
   },
   {
-   "dependencies": "Attendance",
-   "hidden": 0,
-   "is_query_report": 1,
-   "label": "Monthly Attendance Sheet",
-   "link_to": "Monthly Attendance Sheet",
-   "link_type": "Report",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Expense Claims",
@@ -423,6 +414,15 @@
   {
    "hidden": 0,
    "is_query_report": 0,
+   "label": "Travel Request",
+   "link_to": "Travel Request",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
    "label": "Settings",
    "onboard": 0,
    "type": "Card Break"
@@ -465,6 +465,15 @@
    "type": "Card Break"
   },
   {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Driver",
+   "link_to": "Driver",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
@@ -544,6 +553,24 @@
   {
    "hidden": 0,
    "is_query_report": 0,
+   "label": "Appointment Letter",
+   "link_to": "Appointment Letter",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Appointment Letter Template",
+   "link_to": "Appointment Letter Template",
+   "link_type": "DocType",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
    "label": "Loans",
    "onboard": 0,
    "type": "Card Break"
@@ -628,33 +655,6 @@
   {
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Reports",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "Employee",
-   "hidden": 0,
-   "is_query_report": 1,
-   "label": "Employee Birthday",
-   "link_to": "Employee Birthday",
-   "link_type": "Report",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "Employee",
-   "hidden": 0,
-   "is_query_report": 1,
-   "label": "Employees working on a holiday",
-   "link_to": "Employees working on a holiday",
-   "link_type": "Report",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
    "label": "Performance",
    "onboard": 0,
    "type": "Card Break"
@@ -702,7 +702,74 @@
   {
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Employee Tax and Benefits",
+   "label": "Key Reports",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "Attendance",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Monthly Attendance Sheet",
+   "link_to": "Monthly Attendance Sheet",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Staffing Plan",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Recruitment Analytics",
+   "link_to": "Recruitment Analytics",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Employee",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Employee Analytics",
+   "link_to": "Employee Analytics",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Employee",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Employee Leave Balance",
+   "link_to": "Employee Leave Balance",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Employee",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Employee Leave Balance Summary",
+   "link_to": "Employee Leave Balance Summary",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "dependencies": "Employee Advance",
+   "hidden": 0,
+   "is_query_report": 1,
+   "label": "Employee Advance Summary",
+   "link_to": "Employee Advance Summary",
+   "link_type": "Report",
+   "onboard": 0,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Other Reports",
    "onboard": 0,
    "type": "Card Break"
   },
@@ -710,74 +777,44 @@
    "dependencies": "Employee",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Employee Tax Exemption Declaration",
-   "link_to": "Employee Tax Exemption Declaration",
-   "link_type": "DocType",
+   "label": "Employee Information",
+   "link_to": "Employee Information",
+   "link_type": "Report",
    "onboard": 0,
    "type": "Link"
   },
   {
    "dependencies": "Employee",
    "hidden": 0,
-   "is_query_report": 0,
-   "label": "Employee Tax Exemption Proof Submission",
-   "link_to": "Employee Tax Exemption Proof Submission",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "Employee, Payroll Period",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Employee Other Income",
-   "link_to": "Employee Other Income",
-   "link_type": "DocType",
+   "is_query_report": 1,
+   "label": "Employee Birthday",
+   "link_to": "Employee Birthday",
+   "link_type": "Report",
    "onboard": 0,
    "type": "Link"
   },
   {
    "dependencies": "Employee",
    "hidden": 0,
-   "is_query_report": 0,
-   "label": "Employee Benefit Application",
-   "link_to": "Employee Benefit Application",
-   "link_type": "DocType",
+   "is_query_report": 1,
+   "label": "Employees Working on a Holiday",
+   "link_to": "Employees working on a holiday",
+   "link_type": "Report",
    "onboard": 0,
    "type": "Link"
   },
   {
-   "dependencies": "Employee",
+   "dependencies": "Daily Work Summary",
    "hidden": 0,
-   "is_query_report": 0,
-   "label": "Employee Benefit Claim",
-   "link_to": "Employee Benefit Claim",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "Employee",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Employee Tax Exemption Category",
-   "link_to": "Employee Tax Exemption Category",
-   "link_type": "DocType",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "Employee",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Employee Tax Exemption Sub Category",
-   "link_to": "Employee Tax Exemption Sub Category",
-   "link_type": "DocType",
+   "is_query_report": 1,
+   "label": "Daily Work Summary Replies",
+   "link_to": "Daily Work Summary Replies",
+   "link_type": "Report",
    "onboard": 0,
    "type": "Link"
   }
  ],
- "modified": "2021-01-21 13:38:38.941001",
+ "modified": "2021-03-24 17:35:21.483297",
  "modified_by": "Administrator",
  "module": "HR",
  "name": "HR",
diff --git a/erpnext/loan_management/doctype/loan/loan.json b/erpnext/loan_management/doctype/loan/loan.json
index acf09f5..4f8ceb0 100644
--- a/erpnext/loan_management/doctype/loan/loan.json
+++ b/erpnext/loan_management/doctype/loan/loan.json
@@ -23,6 +23,7 @@
   "rate_of_interest",
   "is_secured_loan",
   "disbursement_date",
+  "closure_date",
   "disbursed_amount",
   "column_break_11",
   "maximum_loan_amount",
@@ -348,12 +349,18 @@
    "no_copy": 1,
    "options": "Company:company:default_currency",
    "read_only": 1
+  },
+  {
+   "fieldname": "closure_date",
+   "fieldtype": "Date",
+   "label": "Closure Date",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-24 12:27:23.208240",
+ "modified": "2021-04-10 09:28:21.946972",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan",
diff --git a/erpnext/loan_management/doctype/loan/test_loan.py b/erpnext/loan_management/doctype/loan/test_loan.py
index 13a2094..6f8da31 100644
--- a/erpnext/loan_management/doctype/loan/test_loan.py
+++ b/erpnext/loan_management/doctype/loan/test_loan.py
@@ -275,6 +275,11 @@
 		frappe.db.sql(""" UPDATE `tabLoan Security Price` SET loan_security_price = 250
 			where loan_security='Test Security 2'""")
 
+		create_process_loan_security_shortfall()
+		loan_security_shortfall = frappe.get_doc("Loan Security Shortfall", {"loan": loan.name})
+		self.assertEquals(loan_security_shortfall.status, "Completed")
+		self.assertEquals(loan_security_shortfall.shortfall_amount, 0)
+
 	def test_loan_security_unpledge(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
@@ -518,33 +523,7 @@
 		self.assertEqual(flt(repayment_entry.total_interest_paid, 0), flt(interest_amount, 0))
 
 	def test_penalty(self):
-		pledge = [{
-			"loan_security": "Test Security 1",
-			"qty": 4000.00
-		}]
-
-		loan_application = create_loan_application('_Test Company', self.applicant2, 'Demand Loan', pledge)
-		create_pledge(loan_application)
-
-		loan = create_demand_loan(self.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
-		loan.submit()
-
-		self.assertEquals(loan.loan_amount, 1000000)
-
-		first_date = '2019-10-01'
-		last_date = '2019-10-30'
-
-		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
-		process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
-
-		amounts = calculate_amounts(loan.name, add_days(last_date, 1))
-		paid_amount = amounts['interest_amount']/2
-
-		repayment_entry = create_repayment_entry(loan.name, self.applicant2, add_days(last_date, 5),
-			paid_amount)
-
-		repayment_entry.submit()
-
+		loan, amounts = create_loan_scenario_for_penalty(self)
 		# 30 days - grace period
 		penalty_days = 30 - 4
 		penalty_applicable_amount = flt(amounts['interest_amount']/2)
@@ -554,8 +533,28 @@
 		calculated_penalty_amount = frappe.db.get_value('Loan Interest Accrual',
 			{'process_loan_interest_accrual': process, 'loan': loan.name}, 'penalty_amount')
 
+		self.assertEquals(loan.loan_amount, 1000000)
 		self.assertEquals(calculated_penalty_amount, penalty_amount)
 
+	def test_penalty_repayment(self):
+		loan, dummy = create_loan_scenario_for_penalty(self)
+		amounts = calculate_amounts(loan.name, '2019-11-30 00:00:00')
+
+		first_penalty = 10000
+		second_penalty = amounts['penalty_amount'] - 10000
+
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:00', 10000)
+		repayment_entry.submit()
+
+		amounts = calculate_amounts(loan.name, '2019-11-30 00:00:01')
+		self.assertEquals(amounts['penalty_amount'], second_penalty)
+
+		repayment_entry = create_repayment_entry(loan.name, self.applicant2, '2019-11-30 00:00:01', second_penalty)
+		repayment_entry.submit()
+
+		amounts = calculate_amounts(loan.name, '2019-11-30 00:00:02')
+		self.assertEquals(amounts['penalty_amount'], 0)
+
 	def test_loan_write_off_limit(self):
 		pledge = [{
 			"loan_security": "Test Security 1",
@@ -646,6 +645,32 @@
 		amounts = calculate_amounts(loan.name, add_days(last_date, 5))
 		self.assertEquals(flt(amounts['pending_principal_amount'], 0), 0)
 
+def create_loan_scenario_for_penalty(doc):
+	pledge = [{
+		"loan_security": "Test Security 1",
+		"qty": 4000.00
+	}]
+
+	loan_application = create_loan_application('_Test Company', doc.applicant2, 'Demand Loan', pledge)
+	create_pledge(loan_application)
+	loan = create_demand_loan(doc.applicant2, "Demand Loan", loan_application, posting_date='2019-10-01')
+	loan.submit()
+
+	first_date = '2019-10-01'
+	last_date = '2019-10-30'
+
+	make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=first_date)
+	process_loan_interest_accrual_for_demand_loans(posting_date = last_date)
+
+	amounts = calculate_amounts(loan.name, add_days(last_date, 1))
+	paid_amount = amounts['interest_amount']/2
+
+	repayment_entry = create_repayment_entry(loan.name, doc.applicant2, add_days(last_date, 5),
+		paid_amount)
+
+	repayment_entry.submit()
+
+	return loan, amounts
 
 def create_loan_accounts():
 	if not frappe.db.exists("Account", "Loans and Advances (Assets) - _TC"):
diff --git a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
index cd5df4d..662c626 100644
--- a/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
+++ b/erpnext/loan_management/doctype/loan_disbursement/loan_disbursement.json
@@ -20,6 +20,10 @@
   "cost_center",
   "customer_details_section",
   "bank_account",
+  "disbursement_references_section",
+  "reference_date",
+  "column_break_17",
+  "reference_number",
   "amended_from"
  ],
  "fields": [
@@ -126,12 +130,31 @@
   {
    "fieldname": "column_break_8",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "disbursement_references_section",
+   "fieldtype": "Section Break",
+   "label": "Disbursement References"
+  },
+  {
+   "fieldname": "reference_date",
+   "fieldtype": "Date",
+   "label": "Reference Date"
+  },
+  {
+   "fieldname": "column_break_17",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "reference_number",
+   "fieldtype": "Data",
+   "label": "Reference Number"
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-06 10:04:30.882322",
+ "modified": "2021-04-10 10:03:41.502210",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Disbursement",
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
index 2b5df4b..8fbf233 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.json
@@ -21,6 +21,7 @@
   "interest_payable",
   "payable_amount",
   "column_break_9",
+  "shortfall_amount",
   "payable_principal_amount",
   "penalty_amount",
   "amount_paid",
@@ -31,6 +32,7 @@
   "column_break_21",
   "reference_date",
   "principal_amount_paid",
+  "total_penalty_paid",
   "total_interest_paid",
   "repayment_details",
   "amended_from"
@@ -226,12 +228,27 @@
    "fieldtype": "Percent",
    "label": "Rate Of Interest",
    "read_only": 1
+  },
+  {
+   "fieldname": "shortfall_amount",
+   "fieldtype": "Currency",
+   "label": "Shortfall Amount",
+   "options": "Company:company:default_currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "total_penalty_paid",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Total Penalty Paid",
+   "options": "Company:company:default_currency",
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-05 10:06:58.792841",
+ "modified": "2021-04-10 10:00:31.859076",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Repayment",
diff --git a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
index bac06c4..728eadf 100644
--- a/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
+++ b/erpnext/loan_management/doctype/loan_repayment/loan_repayment.py
@@ -21,6 +21,7 @@
 	def validate(self):
 		amounts = calculate_amounts(self.against_loan, self.posting_date)
 		self.set_missing_values(amounts)
+		self.check_future_entries()
 		self.validate_amount()
 		self.allocate_amounts(amounts)
 
@@ -60,19 +61,28 @@
 		if not self.payable_amount:
 			self.payable_amount = flt(amounts['payable_amount'], precision)
 
+		shortfall_amount = flt(frappe.db.get_value('Loan Security Shortfall', {'loan': self.against_loan, 'status': 'Pending'},
+			'shortfall_amount'))
+
+		if shortfall_amount:
+			self.shortfall_amount = shortfall_amount
+
 		if amounts.get('due_date'):
 			self.due_date = amounts.get('due_date')
 
+	def check_future_entries(self):
+		future_repayment_date = frappe.db.get_value("Loan Repayment", {"posting_date": (">", self.posting_date),
+			"docstatus": 1, "against_loan": self.against_loan}, 'posting_date')
+
+		if future_repayment_date:
+			frappe.throw("Repayment already made till date {0}".format(get_datetime(future_repayment_date)))
+
 	def validate_amount(self):
 		precision = cint(frappe.db.get_default("currency_precision")) or 2
 
 		if not self.amount_paid:
 			frappe.throw(_("Amount paid cannot be zero"))
 
-		if self.amount_paid < self.penalty_amount:
-			msg = _("Paid amount cannot be less than {0}").format(self.penalty_amount)
-			frappe.throw(msg)
-
 	def book_unaccrued_interest(self):
 		precision = cint(frappe.db.get_default("currency_precision")) or 2
 		if self.total_interest_paid > self.interest_payable:
@@ -148,11 +158,28 @@
 	def allocate_amounts(self, repayment_details):
 		self.set('repayment_details', [])
 		self.principal_amount_paid = 0
-		total_interest_paid = 0
-		interest_paid = self.amount_paid - self.penalty_amount
+		self.total_penalty_paid = 0
+		interest_paid = self.amount_paid
 
-		if self.amount_paid - self.penalty_amount > 0:
-			interest_paid = self.amount_paid - self.penalty_amount
+		if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
+			self.principal_amount_paid = self.shortfall_amount
+		elif self.shortfall_amount:
+			self.principal_amount_paid = self.amount_paid
+
+		interest_paid -= self.principal_amount_paid
+
+		if interest_paid > 0:
+			if self.penalty_amount and interest_paid > self.penalty_amount:
+				self.total_penalty_paid = self.penalty_amount
+			elif self.penalty_amount:
+				self.total_penalty_paid = interest_paid
+
+			interest_paid -= self.total_penalty_paid
+
+		total_interest_paid = 0
+		# interest_paid = self.amount_paid - self.principal_amount_paid - self.penalty_amount
+
+		if interest_paid > 0:
 			for lia, amounts in iteritems(repayment_details.get('pending_accrual_entries', [])):
 				if amounts['interest_amount'] + amounts['payable_principal_amount'] <= interest_paid:
 					interest_amount = amounts['interest_amount']
@@ -177,7 +204,7 @@
 					'paid_principal_amount': paid_principal
 				})
 
-		if repayment_details['unaccrued_interest'] and interest_paid:
+		if repayment_details['unaccrued_interest'] and interest_paid > 0:
 			# no of days for which to accrue interest
 			# Interest can only be accrued for an entire day and not partial
 			if interest_paid > repayment_details['unaccrued_interest']:
@@ -193,20 +220,28 @@
 				interest_paid -= no_of_days * per_day_interest
 
 		self.total_interest_paid = total_interest_paid
-		if interest_paid:
+		if interest_paid > 0:
 			self.principal_amount_paid += interest_paid
 
 	def make_gl_entries(self, cancel=0, adv_adj=0):
 		gle_map = []
 		loan_details = frappe.get_doc("Loan", self.against_loan)
 
-		if self.penalty_amount:
+		if self.shortfall_amount and self.amount_paid > self.shortfall_amount:
+			remarks = _("Shortfall Repayment of {0}.\nRepayment against Loan: {1}").format(self.shortfall_amount,
+				self.against_loan)
+		elif self.shortfall_amount:
+			remarks = _("Shortfall Repayment of {0}").format(self.shortfall_amount)
+		else:
+			remarks = _("Repayment against Loan: ") + self.against_loan
+
+		if self.total_penalty_paid:
 			gle_map.append(
 				self.get_gl_dict({
 					"account": loan_details.loan_account,
 					"against": loan_details.payment_account,
-					"debit": self.penalty_amount,
-					"debit_in_account_currency": self.penalty_amount,
+					"debit": self.total_penalty_paid,
+					"debit_in_account_currency": self.total_penalty_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
 					"remarks": _("Penalty against loan:") + self.against_loan,
@@ -221,8 +256,8 @@
 				self.get_gl_dict({
 					"account": loan_details.penalty_income_account,
 					"against": loan_details.payment_account,
-					"credit": self.penalty_amount,
-					"credit_in_account_currency": self.penalty_amount,
+					"credit": self.total_penalty_paid,
+					"credit_in_account_currency": self.total_penalty_paid,
 					"against_voucher_type": "Loan",
 					"against_voucher": self.against_loan,
 					"remarks": _("Penalty against loan:") + self.against_loan,
@@ -240,7 +275,7 @@
 				"debit_in_account_currency": self.amount_paid,
 				"against_voucher_type": "Loan",
 				"against_voucher": self.against_loan,
-				"remarks": _("Repayment against Loan: ") + self.against_loan,
+				"remarks": remarks,
 				"cost_center": self.cost_center,
 				"posting_date": getdate(self.posting_date)
 			})
@@ -256,7 +291,7 @@
 				"credit_in_account_currency": self.amount_paid,
 				"against_voucher_type": "Loan",
 				"against_voucher": self.against_loan,
-				"remarks": _("Repayment against Loan: ") + self.against_loan,
+				"remarks": remarks,
 				"cost_center": self.cost_center,
 				"posting_date": getdate(self.posting_date)
 			})
@@ -284,7 +319,9 @@
 
 	return lr
 
-def get_accrued_interest_entries(against_loan):
+def get_accrued_interest_entries(against_loan, posting_date=None):
+	if not posting_date:
+		posting_date = getdate()
 
 	unpaid_accrued_entries = frappe.db.sql(
 		"""
@@ -295,15 +332,28 @@
 				`tabLoan Interest Accrual`
 			WHERE
 				loan = %s
+			AND posting_date <= %s
 			AND (interest_amount - paid_interest_amount > 0 OR
 				payable_principal_amount - paid_principal_amount > 0)
 			AND
 				docstatus = 1
 			ORDER BY posting_date
-		""", (against_loan), as_dict=1)
+		""", (against_loan, posting_date), as_dict=1)
 
 	return unpaid_accrued_entries
 
+def get_penalty_details(against_loan):
+	penalty_details = frappe.db.sql("""
+		SELECT posting_date, (penalty_amount - total_penalty_paid) as pending_penalty_amount
+		FROM `tabLoan Repayment` where posting_date >= (SELECT MAX(posting_date) from `tabLoan Repayment`
+		where against_loan = %s) and docstatus = 1 and against_loan = %s
+	""", (against_loan, against_loan))
+
+	if penalty_details:
+		return penalty_details[0][0], flt(penalty_details[0][1])
+	else:
+		return None, 0
+
 # This function returns the amounts that are payable at the time of loan repayment based on posting date
 # So it pulls all the unpaid Loan Interest Accrual Entries and calculates the penalty if applicable
 
@@ -312,8 +362,9 @@
 
 	against_loan_doc = frappe.get_doc("Loan", against_loan)
 	loan_type_details = frappe.get_doc("Loan Type", against_loan_doc.loan_type)
-	accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name)
+	accrued_interest_entries = get_accrued_interest_entries(against_loan_doc.name, posting_date)
 
+	computed_penalty_date, pending_penalty_amount = get_penalty_details(against_loan)
 	pending_accrual_entries = {}
 
 	total_pending_interest = 0
@@ -328,8 +379,13 @@
 		# and if no_of_late days are positive then penalty is levied
 
 		due_date = add_days(entry.posting_date, 1)
-		no_of_late_days = date_diff(posting_date,
-			add_days(due_date, loan_type_details.grace_period_in_days)) + 1
+		due_date_after_grace_period = add_days(due_date, loan_type_details.grace_period_in_days)
+
+		# Consider one day after already calculated penalty
+		if computed_penalty_date and getdate(computed_penalty_date) >= due_date_after_grace_period:
+			due_date_after_grace_period = add_days(computed_penalty_date, 1)
+
+		no_of_late_days = date_diff(posting_date, due_date_after_grace_period) + 1
 
 		if no_of_late_days > 0 and (not against_loan_doc.repay_from_salary) and entry.accrual_type == 'Regular':
 			penalty_amount += (entry.interest_amount * (loan_type_details.penalty_interest_rate / 100) * no_of_late_days)
@@ -367,7 +423,7 @@
 	amounts["pending_principal_amount"] = flt(pending_principal_amount, precision)
 	amounts["payable_principal_amount"] = flt(payable_principal_amount, precision)
 	amounts["interest_amount"] = flt(total_pending_interest, precision)
-	amounts["penalty_amount"] = flt(penalty_amount, precision)
+	amounts["penalty_amount"] = flt(penalty_amount + pending_penalty_amount, precision)
 	amounts["payable_amount"] = flt(payable_principal_amount + total_pending_interest + penalty_amount, precision)
 	amounts["pending_accrual_entries"] = pending_accrual_entries
 	amounts["unaccrued_interest"] = flt(unaccrued_interest, precision)
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json
index 102bc0d..99b5c72 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "LM-LSS-.#####",
  "creation": "2019-09-06 11:33:34.709540",
  "doctype": "DocType",
@@ -14,6 +15,7 @@
   "shortfall_amount",
   "column_break_8",
   "security_value",
+  "shortfall_percentage",
   "section_break_8",
   "process_loan_security_shortfall"
  ],
@@ -85,10 +87,18 @@
   {
    "fieldname": "column_break_8",
    "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "shortfall_percentage",
+   "fieldtype": "Percent",
+   "label": "Shortfall Percentage",
+   "read_only": 1
   }
  ],
  "in_create": 1,
- "modified": "2019-10-24 06:24:26.128997",
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2021-04-01 08:13:43.263772",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Loan Security Shortfall",
diff --git a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
index 6469806..8233b7b 100644
--- a/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
+++ b/erpnext/loan_management/doctype/loan_security_shortfall/loan_security_shortfall.py
@@ -12,7 +12,7 @@
 class LoanSecurityShortfall(Document):
 	pass
 
-def update_shortfall_status(loan, security_value):
+def update_shortfall_status(loan, security_value, on_cancel=0):
 	loan_security_shortfall = frappe.db.get_value("Loan Security Shortfall",
 		{"loan": loan, "status": "Pending"}, ['name', 'shortfall_amount'], as_dict=1)
 
@@ -22,7 +22,9 @@
 	if security_value >= loan_security_shortfall.shortfall_amount:
 		frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name, {
 			"status": "Completed",
-			"shortfall_amount": loan_security_shortfall.shortfall_amount})
+			"shortfall_amount": loan_security_shortfall.shortfall_amount,
+			"shortfall_percentage": 0
+		})
 	else:
 		frappe.db.set_value("Loan Security Shortfall", loan_security_shortfall.name,
 			"shortfall_amount", loan_security_shortfall.shortfall_amount - security_value)
@@ -55,6 +57,9 @@
 		'total_interest_payable', 'disbursed_amount', 'status'],
 		filters={'status': ('in',['Disbursed','Partially Disbursed']), 'is_secured_loan': 1})
 
+	loan_shortfall_map = frappe._dict(frappe.get_all("Loan Security Shortfall",
+		fields=["loan", "name"], filters={"status": "Pending"}, as_list=1))
+
 	loan_security_map = {}
 
 	for loan in loans:
@@ -62,7 +67,8 @@
 			outstanding_amount = flt(loan.total_payment) - flt(loan.total_interest_payable) \
 				- flt(loan.total_principal_paid)
 		else:
-			outstanding_amount = loan.disbursed_amount
+			outstanding_amount = flt(loan.disbursed_amount) - flt(loan.total_interest_payable) \
+				- flt(loan.total_principal_paid)
 
 		pledged_securities = get_pledged_security_qty(loan.name)
 		ltv_ratio = ''
@@ -71,16 +77,22 @@
 		for security, qty in pledged_securities.items():
 			if not ltv_ratio:
 				ltv_ratio = get_ltv_ratio(security)
-			security_value += loan_security_price_map.get(security) * qty
+			security_value += flt(loan_security_price_map.get(security)) * flt(qty)
 
-		current_ratio = (outstanding_amount/security_value) * 100
+		current_ratio = (outstanding_amount/security_value) * 100 if security_value else 0
 
 		if current_ratio > ltv_ratio:
 			shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
 			create_loan_security_shortfall(loan.name, outstanding_amount, security_value, shortfall_amount,
-				process_loan_security_shortfall)
+				current_ratio, process_loan_security_shortfall)
+		elif loan_shortfall_map.get(loan.name):
+			shortfall_amount = outstanding_amount - ((security_value * ltv_ratio) / 100)
+			if shortfall_amount <= 0:
+				shortfall = loan_shortfall_map.get(loan.name)
+				update_pending_shortfall(shortfall)
 
-def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, process_loan_security_shortfall):
+def create_loan_security_shortfall(loan, loan_amount, security_value, shortfall_amount, shortfall_ratio,
+	process_loan_security_shortfall):
 	existing_shortfall = frappe.db.get_value("Loan Security Shortfall", {"loan": loan, "status": "Pending"}, "name")
 
 	if existing_shortfall:
@@ -93,6 +105,7 @@
 	ltv_shortfall.loan_amount = loan_amount
 	ltv_shortfall.security_value = security_value
 	ltv_shortfall.shortfall_amount = shortfall_amount
+	ltv_shortfall.shortfall_percentage = shortfall_ratio
 	ltv_shortfall.process_loan_security_shortfall = process_loan_security_shortfall
 	ltv_shortfall.save()
 
@@ -101,3 +114,12 @@
 	ltv_ratio = frappe.db.get_value('Loan Security Type', loan_security_type, 'loan_to_value_ratio')
 	return ltv_ratio
 
+def update_pending_shortfall(shortfall):
+	# Get all pending loan security shortfall
+	frappe.db.set_value("Loan Security Shortfall", shortfall,
+		{
+			"status": "Completed",
+			"shortfall_amount": 0,
+			"shortfall_percentage": 0
+		})
+
diff --git a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
index c4c2d68..b24dc2f 100644
--- a/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
+++ b/erpnext/loan_management/doctype/loan_security_unpledge/loan_security_unpledge.py
@@ -6,7 +6,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import get_datetime, flt
+from frappe.utils import get_datetime, flt, getdate
 import json
 from six import iteritems
 from erpnext.loan_management.doctype.loan_security_price.loan_security_price import get_loan_security_price
@@ -113,7 +113,11 @@
 				pledged_qty += qty
 
 			if not pledged_qty:
-				frappe.db.set_value('Loan', self.loan, 'status', 'Closed')
+				frappe.db.set_value('Loan', self.loan,
+					{
+						'status': 'Closed',
+						'closure_date': getdate()
+					})
 
 @frappe.whitelist()
 def get_pledged_security_qty(loan):
diff --git a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
index 2f4fe24..3d07081 100644
--- a/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
+++ b/erpnext/loan_management/doctype/salary_slip_loan/salary_slip_loan.json
@@ -70,7 +70,9 @@
   {
    "fieldname": "loan_repayment_entry",
    "fieldtype": "Link",
+   "hidden": 1,
    "label": "Loan Repayment Entry",
+   "no_copy": 1,
    "options": "Loan Repayment",
    "read_only": 1
   },
@@ -83,9 +85,10 @@
    "read_only": 1
   }
  ],
+ "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-04-16 13:17:04.798335",
+ "modified": "2021-03-14 20:47:11.725818",
  "modified_by": "Administrator",
  "module": "Loan Management",
  "name": "Salary Slip Loan",
diff --git a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
index 0f72c3c..2a74a1e 100644
--- a/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
+++ b/erpnext/loan_management/report/loan_interest_report/loan_interest_report.py
@@ -63,9 +63,11 @@
 	currency = erpnext.get_company_currency(filters.get('company'))
 
 	for loan in loan_details:
+		total_payment = loan.total_payment if loan.status == 'Disbursed' else loan.disbursed_amount
+
 		loan.update({
 			"sanctioned_amount": flt(sanctioned_amount_map.get(loan.applicant_name)),
-			"principal_outstanding": flt(loan.total_payment) - flt(loan.total_principal_paid) \
+			"principal_outstanding": flt(total_payment) - flt(loan.total_principal_paid) \
 				- flt(loan.total_interest_payable) - flt(loan.written_off_amount),
 			"total_repayment": flt(payments.get(loan.loan)),
 			"accrued_interest": flt(accrual_map.get(loan.loan, {}).get("accrued_interest")),
diff --git a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
index cba6a2d..0aefe19 100644
--- a/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
+++ b/erpnext/maintenance/doctype/maintenance_schedule/maintenance_schedule.py
@@ -12,6 +12,7 @@
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
 
 class MaintenanceSchedule(TransactionBase):
+	@frappe.whitelist()
 	def generate_schedule(self):
 		self.set('schedules', [])
 		frappe.db.sql("""delete from `tabMaintenance Schedule Detail`
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 03beedb..979f7ca 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -113,6 +113,7 @@
 
 		return item
 
+	@frappe.whitelist()
 	def get_routing(self):
 		if self.routing:
 			self.set("operations", [])
@@ -145,6 +146,7 @@
 				if not item.get(r):
 					item.set(r, ret[r])
 
+	@frappe.whitelist()
 	def get_bom_material_detail(self, args=None):
 		""" Get raw material details like uom, desc and rate"""
 		if not args:
@@ -210,6 +212,7 @@
 								.format(self.rm_cost_as_per, arg["item_code"]), alert=True)
 		return flt(rate) * flt(self.plc_conversion_rate or 1) / (self.conversion_rate or 1)
 
+	@frappe.whitelist()
 	def update_cost(self, update_parent=True, from_child_bom=False, save=True):
 		if self.docstatus == 2:
 			return
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 3239478..7108338 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import unittest
 import frappe
-from frappe.utils import cstr
+from frappe.utils import cstr, flt
 from frappe.test_runner import make_test_records
 from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import create_stock_reconciliation
 from erpnext.manufacturing.doctype.bom_update_tool.bom_update_tool import update_cost
@@ -81,15 +81,27 @@
 		bom = frappe.copy_doc(test_records[2])
 		bom.insert()
 
-		# test amounts in selected currency
-		self.assertEqual(bom.operating_cost, 100)
-		self.assertEqual(bom.raw_material_cost, 351.68)
-		self.assertEqual(bom.total_cost, 451.68)
+		raw_material_cost = 0.0
+		op_cost = 0.0
+
+		for op_row in bom.operations:
+			op_cost += op_row.operating_cost
+
+		for row in bom.items:
+			raw_material_cost += row.amount
+
+		base_raw_material_cost = raw_material_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
+		base_op_cost = op_cost * flt(bom.conversion_rate, bom.precision("conversion_rate"))
+
+		# test amounts in selected currency, almostEqual checks for 7 digits by default
+		self.assertAlmostEqual(bom.operating_cost, op_cost)
+		self.assertAlmostEqual(bom.raw_material_cost, raw_material_cost)
+		self.assertAlmostEqual(bom.total_cost, raw_material_cost + op_cost)
 
 		# test amounts in selected currency
-		self.assertEqual(bom.base_operating_cost, 6000)
-		self.assertEqual(bom.base_raw_material_cost, 21100.80)
-		self.assertEqual(bom.base_total_cost, 27100.80)
+		self.assertAlmostEqual(bom.base_operating_cost, base_op_cost)
+		self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
+		self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
 
 	def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
 		frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
@@ -134,7 +146,13 @@
 		bom.items[0].conversion_factor = 6
 		bom.insert()
 
-		reset_item_valuation_rate(item_code='_Test Item', qty=200, rate=200)
+		reset_item_valuation_rate(
+			item_code='_Test Item',
+			warehouse_list=frappe.get_all("Warehouse",
+				{"is_group":0, "company": bom.company}, pluck="name"),
+			qty=200,
+			rate=200
+		)
 
 		bom.update_cost()
 
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 662a06b..92074c6 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -47,6 +47,8 @@
 				if d.completed_qty:
 					self.total_completed_qty += d.completed_qty
 
+			self.total_completed_qty = flt(self.total_completed_qty, self.precision("total_completed_qty"))
+
 	def get_overlap_for(self, args, check_next_available_slot=False):
 		production_capacity = 1
 
@@ -164,6 +166,7 @@
 			"time_in_mins": time_diff_in_minutes(row.planned_end_time, row.planned_start_time),
 		})
 
+	@frappe.whitelist()
 	def get_required_items(self):
 		if not self.get('work_order'):
 			return
@@ -255,6 +258,9 @@
 				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
+				if data.get("workstation") != self.workstation:
+					# workstations can change in a job card
+					data.workstation = self.workstation
 
 		wo.flags.ignore_validate_update_after_submit = True
 		wo.update_operation_status()
diff --git a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
index f93b244..6c60bbd 100644
--- a/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
+++ b/erpnext/manufacturing/doctype/material_request_plan_item/material_request_plan_item.json
@@ -11,10 +11,14 @@
   "from_warehouse",
   "warehouse",
   "column_break_4",
+  "required_bom_qty",
   "quantity",
   "uom",
   "projected_qty",
   "actual_qty",
+  "ordered_qty",
+  "reserved_qty_for_production",
+  "safety_stock",
   "item_details",
   "description",
   "min_order_qty",
@@ -129,11 +133,40 @@
    "fieldtype": "Link",
    "label": "From Warehouse",
    "options": "Warehouse"
+  },
+  {
+   "fetch_from": "item_code.safety_stock",
+   "fieldname": "safety_stock",
+   "fieldtype": "Float",
+   "label": "Safety Stock",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "ordered_qty",
+   "fieldtype": "Float",
+   "label": "Ordered Qty",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "reserved_qty_for_production",
+   "fieldtype": "Float",
+   "label": "Reserved Qty for Production",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "required_bom_qty",
+   "fieldtype": "Float",
+   "label": "Required Qty as per BOM",
+   "no_copy": 1,
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-02-03 12:22:29.913302",
+ "modified": "2021-03-26 12:41:13.013149",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Material Request Plan Item",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index b723387..288c1d0 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -25,6 +25,16 @@
 			}
 		});
 
+		frm.set_query('material_request', 'material_requests', function() {
+			return {
+				filters: {
+					material_request_type: "Manufacture",
+					docstatus: 1,
+					status: ["!=", "Stopped"],
+				}
+			};
+		});
+
 		frm.fields_dict['po_items'].grid.get_field('item_code').get_query = function(doc) {
 			return {
 				query: "erpnext.controllers.queries.item_query",
@@ -251,7 +261,8 @@
 
 	get_items_for_material_requests: function(frm, warehouses) {
 		const set_fields = ['actual_qty', 'item_code','item_name', 'description', 'uom', 'from_warehouse',
-			'min_order_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'material_request_type'];
+			'min_order_qty', 'required_bom_qty', 'quantity', 'sales_order', 'warehouse', 'projected_qty', 'ordered_qty',
+			'reserved_qty_for_production', 'material_request_type'];
 
 		frappe.call({
 			method: "erpnext.manufacturing.doctype.production_plan.production_plan.get_items_for_material_requests",
@@ -369,4 +380,4 @@
 			['Sales Order','docstatus', '=' ,1]
 		]
 	}
-};
\ No newline at end of file
+};
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 7daf706..f114700 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -32,6 +32,7 @@
   "material_request_planning",
   "include_non_stock_items",
   "include_subcontracted_items",
+  "include_safety_stock",
   "ignore_existing_ordered_qty",
   "column_break_25",
   "for_warehouse",
@@ -309,13 +310,19 @@
    "fieldtype": "Select",
    "label": "Sales Order Status",
    "options": "\nTo Deliver and Bill\nTo Bill\nTo Deliver"
+  },
+  {
+   "default": "0",
+   "fieldname": "include_safety_stock",
+   "fieldtype": "Check",
+   "label": "Include Safety Stock in Required Qty Calculation"
   }
  ],
  "icon": "fa fa-calendar",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-10 18:01:54.991970",
+ "modified": "2021-03-08 11:17:25.470147",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 8f9dd05..cef2d8b 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -29,6 +29,7 @@
 			if not flt(d.planned_qty):
 				frappe.throw(_("Please enter Planned Qty for Item {0} at row {1}").format(d.item_code, d.idx))
 
+	@frappe.whitelist()
 	def get_open_sales_orders(self):
 		""" Pull sales orders  which are pending to deliver based on criteria selected"""
 		open_so = get_sales_orders(self)
@@ -50,6 +51,7 @@
 				'grand_total': data.base_grand_total
 			})
 
+	@frappe.whitelist()
 	def get_pending_material_requests(self):
 		""" Pull Material Requests that are pending based on criteria selected"""
 		mr_filter = item_filter = ""
@@ -68,7 +70,7 @@
 			from `tabMaterial Request` mr, `tabMaterial Request Item` mr_item
 			where mr_item.parent = mr.name
 				and mr.material_request_type = "Manufacture"
-				and mr.docstatus = 1 and mr.company = %(company)s
+				and mr.docstatus = 1 and mr.status != "Stopped" and mr.company = %(company)s
 				and mr_item.qty > ifnull(mr_item.ordered_qty,0) {0} {1}
 				and (exists (select name from `tabBOM` bom where bom.item=mr_item.item_code
 					and bom.is_active = 1))
@@ -92,6 +94,7 @@
 				'material_request_date': data.transaction_date
 			})
 
+	@frappe.whitelist()
 	def get_items(self):
 		if self.get_items_from == "Sales Order":
 			self.get_so_items()
@@ -219,6 +222,7 @@
 			filters = {'docstatus': 0, 'production_plan': ("=", self.name)}):
 			frappe.delete_doc('Work Order', d.name)
 
+	@frappe.whitelist()
 	def set_status(self, close=None):
 		self.status = {
 			0: 'Draft',
@@ -302,6 +306,7 @@
 
 		return item_dict
 
+	@frappe.whitelist()
 	def make_work_order(self):
 		wo_list = []
 		self.validate_data()
@@ -367,6 +372,7 @@
 		except OverProductionError:
 			pass
 
+	@frappe.whitelist()
 	def make_material_request(self):
 		'''Create Material Requests grouped by Sales Order and Material Request Type'''
 		material_request_list = []
@@ -434,12 +440,14 @@
 	if isinstance(doc, string_types):
 		doc = frappe._dict(json.loads(doc))
 
-	item_list = [['Item Code', 'Description', 'Stock UOM', 'Required Qty', 'Warehouse',
-		'projected Qty', 'Actual Qty']]
+	item_list = [['Item Code', 'Description', 'Stock UOM', 'Warehouse', 'Required Qty as per BOM',
+		'Projected Qty', 'Actual Qty', 'Ordered Qty', 'Reserved Qty for Production',
+		'Safety Stock', 'Required Qty']]
 
 	for d in get_items_for_material_requests(doc):
-		item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('quantity'),
-			d.get('warehouse'), d.get('projected_qty'), d.get('actual_qty')])
+		item_list.append([d.get('item_code'), d.get('description'), d.get('stock_uom'), d.get('warehouse'),
+			d.get('required_bom_qty'), d.get('projected_qty'), d.get('actual_qty'), d.get('ordered_qty'),
+			d.get('reserved_qty_for_production'), d.get('safety_stock'), d.get('quantity')])
 
 		if not doc.get('for_warehouse'):
 			row = {'item_code': d.get('item_code')}
@@ -447,8 +455,9 @@
 				if d.get("warehouse") == bin_dict.get('warehouse'):
 					continue
 
-				item_list.append(['', '', '', '', bin_dict.get('warehouse'),
-					bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0)])
+				item_list.append(['', '', '', bin_dict.get('warehouse'), '',
+					bin_dict.get('projected_qty', 0), bin_dict.get('actual_qty', 0),
+					bin_dict.get('ordered_qty', 0), bin_dict.get('reserved_qty_for_production', 0)])
 
 	build_csv_response(item_list, doc.name)
 
@@ -482,7 +491,7 @@
 			ifnull(%(parent_qty)s * sum(bom_item.stock_qty/ifnull(bom.quantity, 1)) * %(planned_qty)s, 0) as qty,
 			item.is_sub_contracted_item as is_sub_contracted, bom_item.source_warehouse,
 			item.default_bom as default_bom, bom_item.description as description,
-			bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty,
+			bom_item.stock_uom as stock_uom, item.min_order_qty as min_order_qty, item.safety_stock as safety_stock,
 			item_default.default_warehouse, item.purchase_uom, item_uom.conversion_factor
 		FROM
 			`tabBOM Item` bom_item
@@ -518,8 +527,8 @@
 						include_non_stock_items, include_subcontracted_items, d.qty)
 	return item_details
 
-def get_material_request_items(row, sales_order,
-	company, ignore_existing_ordered_qty, warehouse, bin_dict):
+def get_material_request_items(row, sales_order, company,
+	ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict):
 	total_qty = row['qty']
 
 	required_qty = 0
@@ -543,17 +552,24 @@
 	if frappe.db.get_value("UOM", row['purchase_uom'], "must_be_whole_number"):
 		required_qty = ceil(required_qty)
 
+	if include_safety_stock:
+		required_qty += flt(row['safety_stock'])
+
 	if required_qty > 0:
 		return {
 			'item_code': row.item_code,
 			'item_name': row.item_name,
 			'quantity': required_qty,
+			'required_bom_qty': total_qty,
 			'description': row.description,
 			'stock_uom': row.get("stock_uom"),
 			'warehouse': warehouse or row.get('source_warehouse') \
 				or row.get('default_warehouse') or item_group_defaults.get("default_warehouse"),
+			'safety_stock': row.safety_stock,
 			'actual_qty': bin_dict.get("actual_qty", 0),
 			'projected_qty': bin_dict.get("projected_qty", 0),
+			'ordered_qty': bin_dict.get("ordered_qty", 0),
+			'reserved_qty_for_production': bin_dict.get("reserved_qty_for_production", 0),
 			'min_order_qty': row['min_order_qty'],
 			'material_request_type': row.get("default_material_request_type"),
 			'sales_order': sales_order,
@@ -620,7 +636,8 @@
 		""".format(lft, rgt, company)
 
 	return frappe.db.sql(""" select ifnull(sum(projected_qty),0) as projected_qty,
-		ifnull(sum(actual_qty),0) as actual_qty, warehouse from `tabBin`
+		ifnull(sum(actual_qty),0) as actual_qty, ifnull(sum(ordered_qty),0) as ordered_qty,
+		ifnull(sum(reserved_qty_for_production),0) as reserved_qty_for_production, warehouse from `tabBin`
 		where item_code = %(item_code)s {conditions}
 		group by item_code, warehouse
 	""".format(conditions=conditions), { "item_code": row['item_code'] }, as_dict=1)
@@ -660,6 +677,7 @@
 
 	company = doc.get('company')
 	ignore_existing_ordered_qty = doc.get('ignore_existing_ordered_qty')
+	include_safety_stock = doc.get('include_safety_stock')
 
 	so_item_details = frappe._dict()
 	for data in po_items:
@@ -711,6 +729,7 @@
 					'description' : item_master.description,
 					'stock_uom' : item_master.stock_uom,
 					'conversion_factor' : conversion_factor,
+					'safety_stock': item_master.safety_stock
 				}
 			)
 
@@ -732,7 +751,7 @@
 
 			if details.qty > 0:
 				items = get_material_request_items(details, sales_order, company,
-					ignore_existing_ordered_qty, warehouse, bin_dict)
+					ignore_existing_ordered_qty, include_safety_stock, warehouse, bin_dict)
 				if items:
 					mr_items.append(items)
 
diff --git a/erpnext/manufacturing/doctype/routing/routing.js b/erpnext/manufacturing/doctype/routing/routing.js
index 9b1a8ca..032c9cd 100644
--- a/erpnext/manufacturing/doctype/routing/routing.js
+++ b/erpnext/manufacturing/doctype/routing/routing.js
@@ -11,10 +11,9 @@
 	},
 
 	display_sequence_id_column: function(frm) {
-		frappe.meta.get_docfield("BOM Operation", "sequence_id",
-			frm.doc.name).in_list_view = true;
-
-		frm.fields_dict.operations.grid.refresh();
+		frm.fields_dict.operations.grid.update_docfield_property(
+			'sequence_id', 	'in_list_view', 1
+		);
 	},
 
 	calculate_operating_cost: function(frm, child) {
@@ -69,4 +68,4 @@
 		const d = locals[cdt][cdn];
 		frm.events.calculate_operating_cost(frm, d);
 	}
-});
\ No newline at end of file
+});
diff --git a/erpnext/manufacturing/doctype/routing/test_routing.py b/erpnext/manufacturing/doctype/routing/test_routing.py
index 73d05a6..6a38dcf 100644
--- a/erpnext/manufacturing/doctype/routing/test_routing.py
+++ b/erpnext/manufacturing/doctype/routing/test_routing.py
@@ -13,8 +13,15 @@
 from erpnext.manufacturing.doctype.work_order.test_work_order import make_wo_order_test_record
 
 class TestRouting(unittest.TestCase):
+	@classmethod
+	def setUpClass(cls):
+		cls.item_code = "Test Routing Item - A"
+
+	@classmethod
+	def tearDownClass(cls):
+		frappe.db.sql('delete from tabBOM where item=%s', cls.item_code)
+
 	def test_sequence_id(self):
-		item_code = "Test Routing Item - A"
 		operations = [{"operation": "Test Operation A", "workstation": "Test Workstation A", "time_in_mins": 30},
 			{"operation": "Test Operation B", "workstation": "Test Workstation A", "time_in_mins": 20}]
 
@@ -22,8 +29,8 @@
 
 		setup_operations(operations)
 		routing_doc = create_routing(routing_name="Testing Route", operations=operations)
-		bom_doc = setup_bom(item_code=item_code, routing=routing_doc.name)
-		wo_doc = make_wo_order_test_record(production_item = item_code, bom_no=bom_doc.name)
+		bom_doc = setup_bom(item_code=self.item_code, routing=routing_doc.name)
+		wo_doc = make_wo_order_test_record(production_item = self.item_code, bom_no=bom_doc.name)
 
 		for row in routing_doc.operations:
 			self.assertEqual(row.sequence_id, row.idx)
@@ -74,7 +81,7 @@
 		})
 
 	if not args.raw_materials:
-		if not frappe.db.exists('Item', "Test Extra Item 1"):
+		if not frappe.db.exists('Item', "Test Extra Item N-1"):
 			make_item("Test Extra Item N-1", {
 				'is_stock_item': 1,
 			})
@@ -88,4 +95,4 @@
 	else:
 		bom_doc = frappe.get_doc("BOM", name)
 
-	return bom_doc
\ No newline at end of file
+	return bom_doc
diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py
index 00e8c54..6b1fafe 100644
--- a/erpnext/manufacturing/doctype/work_order/test_work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py
@@ -82,7 +82,7 @@
 		wo_order.set_work_order_operations()
 		self.assertEqual(wo_order.planned_operating_cost, cost*2)
 
-	def test_resered_qty_for_partial_completion(self):
+	def test_reserved_qty_for_partial_completion(self):
 		item = "_Test Item"
 		warehouse = create_warehouse("Test Warehouse for reserved_qty - _TC")
 
@@ -109,7 +109,7 @@
 		s.submit()
 
 		bin1_at_completion = get_bin(item, warehouse)
-		
+
 		self.assertEqual(cint(bin1_at_completion.reserved_qty_for_production),
 			reserved_qty_on_submission - 1)
 
@@ -371,14 +371,14 @@
 
 	def test_job_card(self):
 		stock_entries = []
-		data = frappe.get_cached_value('BOM',
-			{'docstatus': 1, 'with_operations': 1, 'company': '_Test Company'}, ['name', 'item'])
+		bom = frappe.get_doc('BOM', {
+			'docstatus': 1,
+			'with_operations': 1,
+			'company': '_Test Company'
+		})
 
-		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")
+		work_order = make_wo_order_test_record(item=bom.item, qty=1,
+			bom_no=bom.name, source_warehouse="_Test Warehouse - _TC")
 
 		for row in work_order.required_items:
 			stock_entry_doc = test_stock_entry.make_stock_entry(item_code=row.item_code,
@@ -390,14 +390,14 @@
 		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))
+		self.assertEqual(len(job_cards), len(bom.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),
+				"from_time": add_to_date(None, i),
+				"hours": 1,
+				"to_time": add_to_date(None, i + 1),
 				"completed_qty": doc.for_quantity
 			})
 			doc.submit()
@@ -592,6 +592,55 @@
 
 		frappe.db.set_value("Manufacturing Settings", None, "backflush_raw_materials_based_on", "BOM")
 
+	def test_make_stock_entry_for_customer_provided_item(self):
+		finished_item = 'Test Item for Make Stock Entry 1'
+		make_item(finished_item, {
+				"include_item_in_manufacturing": 1,
+				"is_stock_item": 1
+			})
+
+		customer_provided_item = 'CUST-0987'
+		make_item(customer_provided_item, {
+			'is_purchase_item': 0,
+			'is_customer_provided_item': 1,
+			"is_stock_item": 1,
+			"include_item_in_manufacturing": 1,
+			'customer': '_Test Customer'
+		})
+
+		if not frappe.db.exists('BOM', {'item': finished_item}):
+			make_bom(item=finished_item, raw_materials=[customer_provided_item], rm_qty=1)
+
+		company = "_Test Company with perpetual inventory"
+		customer_warehouse = create_warehouse("Test Customer Provided Warehouse", company=company)
+		wo = make_wo_order_test_record(item=finished_item, qty=1, source_warehouse=customer_warehouse,
+			company=company)
+
+		ste = frappe.get_doc(make_stock_entry(wo.name, purpose='Material Transfer for Manufacture'))
+		ste.insert()
+
+		self.assertEqual(len(ste.items), 1)
+		for item in ste.items:
+			self.assertEqual(item.allow_zero_valuation_rate, 1)
+			self.assertEqual(item.valuation_rate, 0)
+
+	def test_valuation_rate_missing_on_make_stock_entry(self):
+		item_name = 'Test Valuation Rate Missing'
+		make_item(item_name, {
+			"is_stock_item": 1,
+			"include_item_in_manufacturing": 1,
+		})
+
+		if not frappe.db.get_value('BOM', {'item': item_name}):
+			make_bom(item=item_name, raw_materials=[item_name], rm_qty=1)
+
+		company = "_Test Company with perpetual inventory"
+		source_warehouse = create_warehouse("Test Valuation Rate Missing Warehouse", company=company)
+		wo = make_wo_order_test_record(item=item_name, qty=1, source_warehouse=source_warehouse,
+			company=company)
+
+		self.assertRaises(frappe.ValidationError, make_stock_entry, wo.name, 'Material Transfer for Manufacture')
+
 def get_scrap_item_details(bom_no):
 	scrap_items = {}
 	for item in frappe.db.sql("""select item_code, stock_qty from `tabBOM Scrap Item`
@@ -609,6 +658,15 @@
 
 def make_wo_order_test_record(**args):
 	args = frappe._dict(args)
+	if args.company and args.company != "_Test Company":
+		warehouse_map = {
+			"fg_warehouse": "_Test FG Warehouse",
+			"wip_warehouse": "_Test WIP Warehouse"
+		}
+
+		for attr, wh_name in warehouse_map.items():
+			if not args.get(attr):
+				args[attr] = create_warehouse(wh_name, company=args.company)
 
 	wo_order = frappe.new_doc("Work Order")
 	wo_order.production_item = args.production_item or args.item or args.item_code or "_Test FG Item"
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 585a09d..cd9edee 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -333,8 +333,7 @@
    "fieldname": "operations",
    "fieldtype": "Table",
    "label": "Operations",
-   "options": "Work Order Operation",
-   "read_only": 1
+   "options": "Work Order Operation"
   },
   {
    "depends_on": "operations",
@@ -496,7 +495,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2020-05-05 19:32:43.323054",
+ "modified": "2021-03-16 13:27:51.116484",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 3d64ad4..8507f5e 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -509,6 +509,7 @@
 				stock_bin = get_bin(d.item_code, d.source_warehouse)
 				stock_bin.update_reserved_qty_for_production()
 
+	@frappe.whitelist()
 	def get_items_and_operations_from_bom(self):
 		self.set_required_items()
 		self.set_work_order_operations()
@@ -613,6 +614,7 @@
 
 			item.db_set('consumed_qty', flt(consumed_qty), update_modified=False)
 
+	@frappe.whitelist()
 	def make_bom(self):
 		data = frappe.db.sql(""" select sed.item_code, sed.qty, sed.s_warehouse
 			from `tabStock Entry Detail` sed, `tabStock Entry` se
diff --git a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
index 2ca9f16..fc27d35 100644
--- a/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
+++ b/erpnext/manufacturing/report/exponential_smoothing_forecasting/exponential_smoothing_forecasting.py
@@ -61,7 +61,7 @@
 
 		from_date = add_years(self.filters.from_date, cint(self.filters.no_of_years) * -1)
 		self.period_list = get_period_list(from_date, self.filters.to_date,
-			from_date, self.filters.to_date, None, self.filters.periodicity, ignore_fiscal_year=True)
+			from_date, self.filters.to_date, "Date Range", self.filters.periodicity, ignore_fiscal_year=True)
 
 		order_data = self.get_data_for_forecast() or []
 
diff --git a/erpnext/non_profit/doctype/donation/donation.py b/erpnext/non_profit/doctype/donation/donation.py
index 6a2a06d..4fd1a30 100644
--- a/erpnext/non_profit/doctype/donation/donation.py
+++ b/erpnext/non_profit/doctype/donation/donation.py
@@ -42,7 +42,7 @@
 		self.load_from_db()
 		self.create_payment_entry()
 
-	def create_payment_entry(self):
+	def create_payment_entry(self, date=None):
 		settings = frappe.get_doc('Non Profit Settings')
 		if not settings.automate_donation_payment_entries:
 			return
@@ -58,8 +58,9 @@
 		frappe.flags.ignore_account_permission = False
 		pe.paid_from = settings.donation_debit_account
 		pe.paid_to = settings.donation_payment_account
+		pe.posting_date = date or getdate()
 		pe.reference_no = self.name
-		pe.reference_date = getdate()
+		pe.reference_date = date or getdate()
 		pe.flags.ignore_mandatory = True
 		pe.insert()
 		pe.submit()
diff --git a/erpnext/non_profit/doctype/member/member.py b/erpnext/non_profit/doctype/member/member.py
index 3ba2ee7..efc072e 100644
--- a/erpnext/non_profit/doctype/member/member.py
+++ b/erpnext/non_profit/doctype/member/member.py
@@ -53,6 +53,7 @@
 
 		return subscription
 
+	@frappe.whitelist()
 	def make_customer_and_link(self):
 		if self.customer:
 			frappe.msgprint(_("A customer is already linked to this Member"))
diff --git a/erpnext/non_profit/doctype/membership/membership.py b/erpnext/non_profit/doctype/membership/membership.py
index c41a2f5..e8ae618 100644
--- a/erpnext/non_profit/doctype/membership/membership.py
+++ b/erpnext/non_profit/doctype/membership/membership.py
@@ -48,7 +48,7 @@
 		last_membership = erpnext.get_last_membership(self.member)
 
 		# if person applied for offline membership
-		if last_membership and last_membership != self.name and not frappe.session.user == "Administrator":
+		if last_membership and last_membership.name != self.name and not frappe.session.user == "Administrator":
 			# if last membership does not expire in 30 days, then do not allow to renew
 			if getdate(add_days(last_membership.to_date, -30)) > getdate(nowdate()) :
 				frappe.throw(_("You can only renew if your membership expires within 30 days"))
@@ -74,6 +74,7 @@
 			self.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
 
 
+	@frappe.whitelist()
 	def generate_invoice(self, save=True, with_payment_entry=False):
 		if not (self.paid or self.currency or self.amount):
 			frappe.throw(_("The payment for this membership is not paid. To generate invoice fill the payment details"))
@@ -90,6 +91,7 @@
 		self.validate_membership_type_and_settings(plan, settings)
 
 		invoice = make_invoice(self, member, plan, settings)
+		self.reload()
 		self.invoice = invoice.name
 
 		if with_payment_entry:
@@ -129,6 +131,7 @@
 		pe.save()
 		pe.submit()
 
+	@frappe.whitelist()
 	def send_acknowlement(self):
 		settings = frappe.get_doc("Non Profit Settings")
 		if not settings.send_email:
@@ -284,6 +287,7 @@
 
 		settings = frappe.get_doc("Non Profit Settings")
 		if settings.allow_invoicing and settings.automate_membership_invoicing:
+			membership.reload()
 			membership.generate_invoice(with_payment_entry=settings.automate_membership_payment_entries, save=True)
 
 	except Exception as e:
diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
index cff92b4..4c4ca98 100644
--- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
+++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.js
@@ -19,7 +19,7 @@
 			};
 		});
 
-		frm.set_query("debit_account", function() {
+		frm.set_query("membership_debit_account", function() {
 			return {
 				filters: {
 					"account_type": "Receivable",
@@ -29,6 +29,16 @@
 			};
 		});
 
+		frm.set_query("donation_debit_account", function() {
+			return {
+				filters: {
+					"account_type": "Receivable",
+					"is_group": 0,
+					"company": frm.doc.donation_company
+				}
+			};
+		});
+
 		frm.set_query("membership_payment_account", function () {
 			var account_types = ["Bank", "Cash"];
 			return {
@@ -40,6 +50,17 @@
 			};
 		});
 
+		frm.set_query("donation_payment_account", function () {
+			var account_types = ["Bank", "Cash"];
+			return {
+				filters: {
+					"account_type": ["in", account_types],
+					"is_group": 0,
+					"company": frm.doc.donation_company
+				}
+			};
+		});
+
 		let docs_url = "https://docs.erpnext.com/docs/user/manual/en/non_profit/membership";
 
 		frm.set_intro(__("You can learn more about memberships in the manual. ") + `<a href='${docs_url}'>${__('ERPNext Docs')}</a>`, true);
diff --git a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
index 108554c..a84cc2c 100644
--- a/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
+++ b/erpnext/non_profit/doctype/non_profit_settings/non_profit_settings.py
@@ -9,6 +9,7 @@
 from frappe.model.document import Document
 
 class NonProfitSettings(Document):
+	@frappe.whitelist()
 	def generate_webhook_secret(self, field="membership_webhook_secret"):
 		key = frappe.generate_hash(length=20)
 		self.set(field, key)
@@ -21,6 +22,7 @@
 			_("Webhook Secret")
 		)
 
+	@frappe.whitelist()
 	def revoke_key(self, key):
 		self.set(key, None)
 		self.save()
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7016ecd..76d3c41 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -99,7 +99,7 @@
 execute:frappe.delete_doc("DocType", "Purchase Request Item")
 erpnext.patches.v4_2.recalculate_bom_cost
 erpnext.patches.v4_2.fix_gl_entries_for_stock_transactions
-erpnext.patches.v4_2.update_requested_and_ordered_qty
+erpnext.patches.v4_2.update_requested_and_ordered_qty #2021-03-31
 execute:frappe.rename_doc("DocType", "Support Ticket", "Issue", force=True)
 erpnext.patches.v4_4.make_email_accounts
 execute:frappe.delete_doc("DocType", "Contact Control")
@@ -208,7 +208,7 @@
 erpnext.patches.v5_7.item_template_attributes
 execute:frappe.delete_doc_if_exists("DocType", "Manage Variants")
 execute:frappe.delete_doc_if_exists("DocType", "Manage Variants Item")
-erpnext.patches.v4_2.repost_reserved_qty #2016-04-15
+erpnext.patches.v4_2.repost_reserved_qty #2021-03-31
 erpnext.patches.v5_4.update_purchase_cost_against_project
 erpnext.patches.v5_8.update_order_reference_in_return_entries
 erpnext.patches.v5_8.add_credit_note_print_heading
@@ -720,7 +720,7 @@
 erpnext.patches.v12_0.update_item_tax_template_company
 erpnext.patches.v13_0.move_branch_code_to_bank_account
 erpnext.patches.v13_0.healthcare_lab_module_rename_doctypes
-erpnext.patches.v13_0.add_standard_navbar_items #4
+erpnext.patches.v13_0.add_standard_navbar_items #2021-03-24
 erpnext.patches.v13_0.stock_entry_enhancements
 erpnext.patches.v12_0.update_state_code_for_daman_and_diu
 erpnext.patches.v12_0.rename_lost_reason_detail
@@ -752,11 +752,21 @@
 erpnext.patches.v13_0.convert_qi_parameter_to_link_field
 erpnext.patches.v13_0.setup_patient_history_settings_for_standard_doctypes
 erpnext.patches.v13_0.add_naming_series_to_old_projects # 1-02-2021
+erpnext.patches.v13_0.update_payment_terms_outstanding
 erpnext.patches.v12_0.add_state_code_for_ladakh
 erpnext.patches.v13_0.item_reposting_for_incorrect_sl_and_gl
 erpnext.patches.v13_0.delete_old_bank_reconciliation_doctypes
-erpnext.patches.v13_0.update_vehicle_no_reqd_condition
+erpnext.patches.v12_0.update_vehicle_no_reqd_condition
+erpnext.patches.v12_0.add_einvoice_status_field #2021-03-17
+erpnext.patches.v12_0.add_einvoice_summary_report_permissions
 erpnext.patches.v13_0.setup_fields_for_80g_certificate_and_donation
 erpnext.patches.v13_0.rename_membership_settings_to_non_profit_settings
 erpnext.patches.v13_0.setup_gratuity_rule_for_india_and_uae
+erpnext.patches.v13_0.setup_uae_vat_fields
 execute:frappe.db.set_value('System Settings', None, 'app_name', 'ERPNext')
+erpnext.patches.v12_0.add_company_link_to_einvoice_settings
+erpnext.patches.v13_0.rename_discharge_date_in_ip_record
+erpnext.patches.v12_0.create_taxable_value_field
+erpnext.patches.v12_0.add_gst_category_in_delivery_note
+erpnext.patches.v12_0.purchase_receipt_status
+erpnext.patches.v12_0.add_document_type_field_for_italy_einvoicing
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
new file mode 100644
index 0000000..3b560fd
--- /dev/null
+++ b/erpnext/patches/v12_0/add_company_link_to_einvoice_settings.py
@@ -0,0 +1,16 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company or not frappe.db.count('E Invoice User'):
+		return
+
+	frappe.reload_doc("regional", "doctype", "e_invoice_user")
+	for creds in frappe.db.get_all('E Invoice User', fields=['name', 'gstin']):
+		company_name = frappe.db.sql("""
+			select dl.link_name from `tabAddress` a, `tabDynamic Link` dl
+			where a.gstin = %s and dl.parent = a.name and dl.link_doctype = 'Company'
+		""", (creds.get('gstin')))
+		if company_name and len(company_name) == 1:
+			frappe.db.set_value('E Invoice User', creds.get('name'), 'company', company_name[0][0])
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py
new file mode 100644
index 0000000..4d649dd
--- /dev/null
+++ b/erpnext/patches/v12_0/add_document_type_field_for_italy_einvoicing.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+import frappe
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'Italy'})
+	if not company:
+		return
+
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='type_of_document', label='Type of Document',
+				fieldtype='Select', insert_after='customer_fiscal_code',
+				options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'),
+		]
+	}
+
+	create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_einvoice_status_field.py b/erpnext/patches/v12_0/add_einvoice_status_field.py
new file mode 100644
index 0000000..387e885
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_status_field.py
@@ -0,0 +1,69 @@
+from __future__ import unicode_literals
+import json
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	# move hidden einvoice fields to a different section
+	custom_fields = {
+		'Sales Invoice': [
+			dict(fieldname='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+			print_hide=1, hidden=1),
+		
+			dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+				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_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', 
+				no_copy=1, print_hide=1),
+
+			dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+				no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+				no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+				no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+				options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+			dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+				hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
+		]
+	}
+	create_custom_fields(custom_fields, update=True)
+
+	if frappe.db.exists('E Invoice Settings') and frappe.db.get_single_value('E Invoice Settings', 'enable'):
+		frappe.db.sql('''
+			UPDATE `tabSales Invoice` SET einvoice_status = 'Pending'
+			WHERE
+				posting_date >= '2021-04-01'
+				AND ifnull(irn, '') = ''
+				AND ifnull(`billing_address_gstin`, '') != ifnull(`company_gstin`, '')
+				AND ifnull(gst_category, '') in ('Registered Regular', 'SEZ', 'Overseas', 'Deemed Export')
+		''')
+
+		# set appropriate statuses
+		frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Generated'
+			WHERE ifnull(irn, '') != '' AND ifnull(irn_cancelled, 0) = 0''')
+
+		frappe.db.sql('''UPDATE `tabSales Invoice` SET einvoice_status = 'Cancelled'
+			WHERE ifnull(irn_cancelled, 0) = 1''')
+
+	# set correct acknowledgement in e-invoices
+	einvoices = frappe.get_all('Sales Invoice', {'irn': ['is', 'set']}, ['name', 'signed_einvoice'])
+
+	if einvoices:
+		for inv in einvoices:
+			signed_einvoice = inv.get('signed_einvoice')
+			if signed_einvoice:
+				signed_einvoice = json.loads(signed_einvoice)
+				frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_no', signed_einvoice.get('AckNo'), update_modified=False)
+				frappe.db.set_value('Sales Invoice', inv.get('name'), 'ack_date', signed_einvoice.get('AckDt'), update_modified=False)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
new file mode 100644
index 0000000..bf8f566
--- /dev/null
+++ b/erpnext/patches/v12_0/add_einvoice_summary_report_permissions.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	if frappe.db.exists('Report', 'E-Invoice Summary') and \
+		not frappe.db.get_value('Custom Role', dict(report='E-Invoice Summary')):
+		frappe.get_doc(dict(
+			doctype='Custom Role',
+			report='E-Invoice Summary',
+			roles= [
+				dict(role='Accounts User'),
+				dict(role='Accounts Manager')
+			]
+		)).insert()	
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py
new file mode 100644
index 0000000..1208222
--- /dev/null
+++ b/erpnext/patches/v12_0/add_gst_category_in_delivery_note.py
@@ -0,0 +1,19 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	custom_fields = {
+		'Delivery Note': [
+			dict(fieldname='gst_category', label='GST Category',
+				fieldtype='Select', insert_after='gst_vehicle_type', print_hide=1,
+				options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
+				fetch_from='customer.gst_category', fetch_if_empty=1),
+		]
+	}
+
+	create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/create_taxable_value_field.py b/erpnext/patches/v12_0/create_taxable_value_field.py
new file mode 100644
index 0000000..a0c9fcf
--- /dev/null
+++ b/erpnext/patches/v12_0/create_taxable_value_field.py
@@ -0,0 +1,18 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'India'})
+	if not company:
+		return
+
+	custom_fields = {
+		'Sales Invoice Item': [
+			dict(fieldname='taxable_value', label='Taxable Value',
+				fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
+				print_hide=1)
+		]
+	}
+
+	create_custom_fields(custom_fields, update=True)
\ No newline at end of file
diff --git a/erpnext/patches/v12_0/purchase_receipt_status.py b/erpnext/patches/v12_0/purchase_receipt_status.py
new file mode 100644
index 0000000..1a99b31
--- /dev/null
+++ b/erpnext/patches/v12_0/purchase_receipt_status.py
@@ -0,0 +1,30 @@
+""" This patch fixes old purchase receipts (PR) where even after submitting
+	the PR, the `status` remains "Draft". `per_billed` field was copied over from previous
+	doc (PO), hence it is recalculated for setting new correct status of PR.
+"""
+
+import frappe
+
+logger = frappe.logger("patch", allow_site=True, file_count=50)
+
+def execute():
+	affected_purchase_receipts = frappe.db.sql(
+		"""select name from `tabPurchase Receipt`
+		where status = 'Draft' and per_billed = 100 and docstatus = 1"""
+	)
+
+	if not affected_purchase_receipts:
+		return
+
+	logger.info("purchase_receipt_status: begin patch, PR count: {}"
+				.format(len(affected_purchase_receipts)))
+
+
+	for pr in affected_purchase_receipts:
+		pr_name = pr[0]
+		logger.info("purchase_receipt_status: patching PR - {}".format(pr_name))
+
+		pr_doc = frappe.get_doc("Purchase Receipt", pr_name)
+
+		pr_doc.update_billing_status(update_modified=False)
+		pr_doc.set_status(update=True, update_modified=False)
diff --git a/erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py b/erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py
similarity index 100%
rename from erpnext/patches/v13_0/update_vehicle_no_reqd_condition.py
rename to erpnext/patches/v12_0/update_vehicle_no_reqd_condition.py
diff --git a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
index 5920bf1..a78f802 100644
--- a/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
+++ b/erpnext/patches/v13_0/healthcare_lab_module_rename_doctypes.py
@@ -18,6 +18,7 @@
 
 		for old_dt, new_dt in doctypes.items():
 			if not frappe.db.table_exists(new_dt) and frappe.db.table_exists(old_dt):
+				frappe.reload_doc('healthcare', 'doctype', frappe.scrub(old_dt))
 				frappe.rename_doc('DocType', old_dt, new_dt, force=True)
 				frappe.reload_doc('healthcare', 'doctype', frappe.scrub(new_dt))
 				frappe.delete_doc_if_exists('DocType', old_dt)
@@ -36,6 +37,18 @@
 				SET parentfield = %(parentfield)s
 			""".format(doctype), {'parentfield': parentfield})
 
+		# copy renamed child table fields (fields were already renamed in old doctype json, hence sql)
+		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_name = test_name""")
+		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_event = test_event""")
+		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_uom = test_uom""")
+		frappe.db.sql("""UPDATE `tabNormal Test Result` SET lab_test_comment = test_comment""")
+		frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_event = test_event""")
+		frappe.db.sql("""UPDATE `tabNormal Test Template` SET lab_test_uom = test_uom""")
+		frappe.db.sql("""UPDATE `tabDescriptive Test Result` SET lab_test_particulars = test_particulars""")
+		frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_template = test_template""")
+		frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_description = test_description""")
+		frappe.db.sql("""UPDATE `tabLab Test Group Template` SET lab_test_rate = test_rate""")
+
 		# rename field
 		frappe.reload_doc('healthcare', 'doctype', 'lab_test')
 		if frappe.db.has_column('Lab Test', 'special_toggle'):
diff --git a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
index d968e1f..021bb72 100644
--- a/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
+++ b/erpnext/patches/v13_0/item_reposting_for_incorrect_sl_and_gl.py
@@ -20,9 +20,11 @@
 	frappe.clear_cache()
 	frappe.flags.warehouse_account_map = {}
 
+	company_list = []
+
 	data = frappe.db.sql('''
 		SELECT
-			name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time
+			name, item_code, warehouse, voucher_type, voucher_no, posting_date, posting_time, company
 		FROM
 			`tabStock Ledger Entry`
 		WHERE
@@ -36,6 +38,9 @@
 	total_sle = len(data)
 	i = 0
 	for d in data:
+		if d.company not in company_list:
+			company_list.append(d.company)
+
 		update_entries_after({
 			"item_code": d.item_code,
 			"warehouse": d.warehouse,
@@ -53,8 +58,10 @@
 
 	print("Reposting General Ledger Entries...")
 
-	for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
-		update_gl_entries_after(posting_date, posting_time, company=row.name)
+	if data:
+		for row in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
+			if row.name in company_list:
+				update_gl_entries_after(posting_date, posting_time, company=row.name)
 
 	frappe.db.auto_commit_on_many_writes = 0
 
diff --git a/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py
new file mode 100644
index 0000000..491dc82
--- /dev/null
+++ b/erpnext/patches/v13_0/rename_discharge_date_in_ip_record.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+import frappe
+from frappe.model.utils.rename_field import rename_field
+
+def execute():
+	frappe.reload_doc("Healthcare", "doctype", "Inpatient Record")
+	if frappe.db.has_column("Inpatient Record", "discharge_date"):
+		rename_field("Inpatient Record", "discharge_date", "discharge_datetime")
diff --git a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
index de08aa2..5316c01 100644
--- a/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
+++ b/erpnext/patches/v13_0/setup_patient_history_settings_for_standard_doctypes.py
@@ -6,6 +6,9 @@
 	if "Healthcare" not in frappe.get_active_domains():
 		return
 
+	frappe.reload_doc("healthcare", "doctype", "Therapy Session")
+	frappe.reload_doc("healthcare", "doctype", "Inpatient Medication Order")
+	frappe.reload_doc("healthcare", "doctype", "Clinical Procedure")
 	frappe.reload_doc("healthcare", "doctype", "Patient History Settings")
 	frappe.reload_doc("healthcare", "doctype", "Patient History Standard Document Type")
 	frappe.reload_doc("healthcare", "doctype", "Patient History Custom Document Type")
diff --git a/erpnext/patches/v13_0/setup_uae_vat_fields.py b/erpnext/patches/v13_0/setup_uae_vat_fields.py
new file mode 100644
index 0000000..d7a5c68
--- /dev/null
+++ b/erpnext/patches/v13_0/setup_uae_vat_fields.py
@@ -0,0 +1,12 @@
+# Copyright (c) 2019, Frappe and Contributors
+# License: GNU General Public License v3. See license.txt
+
+import frappe
+from erpnext.regional.united_arab_emirates.setup import  setup
+
+def execute():
+	company = frappe.get_all('Company', filters = {'country': 'United Arab Emirates'})
+	if not company:
+		return
+
+	setup()
\ No newline at end of file
diff --git a/erpnext/patches/v13_0/update_payment_terms_outstanding.py b/erpnext/patches/v13_0/update_payment_terms_outstanding.py
new file mode 100644
index 0000000..4816b40
--- /dev/null
+++ b/erpnext/patches/v13_0/update_payment_terms_outstanding.py
@@ -0,0 +1,15 @@
+# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	frappe.reload_doc("accounts", "doctype", "Payment Schedule")
+	if frappe.db.count('Payment Schedule'):
+		frappe.db.sql('''
+			UPDATE
+				`tabPayment Schedule` ps
+			SET
+				ps.outstanding = (ps.payment_amount - ps.paid_amount)
+		''')
diff --git a/erpnext/patches/v7_1/update_lead_source.py b/erpnext/patches/v7_1/update_lead_source.py
index 517e66c..a2a48a6 100644
--- a/erpnext/patches/v7_1/update_lead_source.py
+++ b/erpnext/patches/v7_1/update_lead_source.py
@@ -5,7 +5,7 @@
 def execute():
 	from erpnext.setup.setup_wizard.operations.install_fixtures import default_lead_sources
 
-	frappe.reload_doc('selling', 'doctype', 'lead_source')
+	frappe.reload_doc('crm', 'doctype', 'lead_source')
 
 	frappe.local.lang = frappe.db.get_default("lang") or 'en'
 
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.json b/erpnext/payroll/doctype/additional_salary/additional_salary.json
index 2b29f66..61ae7e4 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.json
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.json
@@ -163,7 +163,6 @@
    "read_only": 1
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
    "fieldname": "currency",
    "fieldtype": "Link",
@@ -176,7 +175,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-20 17:51:13.419716",
+ "modified": "2021-03-31 14:45:48.566756",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Additional Salary",
diff --git a/erpnext/payroll/doctype/additional_salary/additional_salary.py b/erpnext/payroll/doctype/additional_salary/additional_salary.py
index 029e11f..13b6c05 100644
--- a/erpnext/payroll/doctype/additional_salary/additional_salary.py
+++ b/erpnext/payroll/doctype/additional_salary/additional_salary.py
@@ -9,17 +9,10 @@
 from frappe.utils import getdate, date_diff, comma_and, formatdate
 
 class AdditionalSalary(Document):
-
 	def on_submit(self):
 		if self.ref_doctype == "Employee Advance" and self.ref_docname:
 			frappe.db.set_value("Employee Advance", self.ref_docname, "return_amount", self.amount)
 
-	def before_insert(self):
-		if frappe.db.exists("Additional Salary", {"employee": self.employee, "salary_component": self.salary_component,
-			"amount": self.amount, "payroll_date": self.payroll_date, "company": self.company, "docstatus": 1}):
-
-			frappe.throw(_("Additional Salary Component Exists."))
-
 	def validate(self):
 		self.validate_dates()
 		self.validate_salary_structure()
diff --git a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
index 4c45580..c6f764c 100644
--- a/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
+++ b/erpnext/payroll/doctype/employee_benefit_application/employee_benefit_application.json
@@ -124,7 +124,6 @@
    "read_only": 1
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
    "fieldname": "currency",
    "fieldtype": "Link",
@@ -148,7 +147,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-12-14 15:52:08.566418",
+ "modified": "2021-03-31 14:46:22.465521",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Application",
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
index ea9ccd5..e1f8431 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.js
@@ -21,7 +21,6 @@
 				callback: function(r) {
 					if (r.message) {
 						frm.set_value('currency', r.message);
-						frm.set_df_property('currency', 'hidden', 0);
 					}
 				}
 			});
diff --git a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
index da24aac..e331b7a 100644
--- a/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
+++ b/erpnext/payroll/doctype/employee_benefit_claim/employee_benefit_claim.json
@@ -125,10 +125,9 @@
    "label": "Attachments"
   },
   {
-   "default": "Company:company:default_currency",
+   "depends_on": "eval: doc.employee",
    "fieldname": "currency",
    "fieldtype": "Link",
-   "hidden": 1,
    "label": "Currency",
    "options": "Currency",
    "read_only": 1,
@@ -145,7 +144,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-25 11:49:56.097352",
+ "modified": "2021-03-31 15:51:51.489269",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Benefit Claim",
diff --git a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
index e5b1052..51346c6 100644
--- a/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
+++ b/erpnext/payroll/doctype/employee_incentive/employee_incentive.json
@@ -75,7 +75,6 @@
    "reqd": 1
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
    "fieldname": "currency",
    "fieldtype": "Link",
@@ -95,7 +94,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-20 17:22:16.468042",
+ "modified": "2021-03-31 14:48:00.919839",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Incentive",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
index 0e0c9b5..fb11875 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.js
@@ -47,5 +47,26 @@
 				});
 			}).addClass("btn-primary");
 		}
+	},
+
+	employee: function(frm) {
+		if (frm.doc.employee) {
+			frm.trigger('get_employee_currency');
+		}
+	},
+
+	get_employee_currency: function(frm) {
+		frappe.call({
+			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+			args: {
+				employee: frm.doc.employee,
+			},
+			callback: function(r) {
+				if (r.message) {
+					frm.set_value('currency', r.message);
+					frm.refresh_fields();
+				}
+			}
+		});
 	}
 });
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
index 83d4ae5..873bf88 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_declaration/employee_tax_exemption_declaration.json
@@ -108,7 +108,7 @@
    "read_only": 1
   },
   {
-   "default": "Company:company:default_currency",
+   "depends_on": "eval: doc.employee",
    "fieldname": "currency",
    "fieldtype": "Link",
    "label": "Currency",
@@ -119,7 +119,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-20 16:42:24.493761",
+ "modified": "2021-03-31 20:41:57.387749",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Declaration",
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
index 497f35c..4fb0a37 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.js
@@ -58,5 +58,26 @@
 
 	currency: function(frm) {
 		frm.refresh_fields();
-	}
+	},
+
+	employee: function(frm) {
+		if (frm.doc.employee) {
+			frm.trigger('get_employee_currency');
+		}
+	},
+
+	get_employee_currency: function(frm) {
+		frappe.call({
+			method: "erpnext.payroll.doctype.salary_structure_assignment.salary_structure_assignment.get_employee_currency",
+			args: {
+				employee: frm.doc.employee,
+			},
+			callback: function(r) {
+				if (r.message) {
+					frm.set_value('currency', r.message);
+					frm.refresh_fields();
+				}
+			}
+		});
+	},
 });
diff --git a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
index 53f18cb..f32202a 100644
--- a/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
+++ b/erpnext/payroll/doctype/employee_tax_exemption_proof_submission/employee_tax_exemption_proof_submission.json
@@ -131,7 +131,7 @@
    "read_only": 1
   },
   {
-   "default": "Company:company:default_currency",
+   "depends_on": "eval: doc.employee",
    "fieldname": "currency",
    "fieldtype": "Link",
    "label": "Currency",
@@ -142,7 +142,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-20 16:47:03.410020",
+ "modified": "2021-03-31 20:48:32.639885",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Employee Tax Exemption Proof Submission",
diff --git a/erpnext/payroll/doctype/gratuity/test_gratuity.py b/erpnext/payroll/doctype/gratuity/test_gratuity.py
index e89e3dd..7daea2d 100644
--- a/erpnext/payroll/doctype/gratuity/test_gratuity.py
+++ b/erpnext/payroll/doctype/gratuity/test_gratuity.py
@@ -15,9 +15,12 @@
 
 test_dependencies = ["Salary Component", "Salary Slip", "Account"]
 class TestGratuity(unittest.TestCase):
-	def setUp(self):
+	@classmethod
+	def setUpClass(cls):
 		make_earning_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
 		make_deduction_salary_component(setup=True, test_tax=True, company_list=['_Test Company'])
+
+	def setUp(self):
 		frappe.db.sql("DELETE FROM `tabGratuity`")
 		frappe.db.sql("DELETE FROM `tabAdditional Salary` WHERE ref_doctype = 'Gratuity'")
 
diff --git a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
index 9fa261d..c343a44 100644
--- a/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
+++ b/erpnext/payroll/doctype/income_tax_slab/income_tax_slab.json
@@ -93,7 +93,7 @@
    "options": "Income Tax Slab Other Charges"
   },
   {
-   "default": "Company:company:default_currency",
+   "fetch_from": "company.default_currency",
    "fieldname": "currency",
    "fieldtype": "Link",
    "label": "Currency",
@@ -104,7 +104,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-19 13:54:24.728075",
+ "modified": "2021-03-31 20:53:33.323712",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Income Tax Slab",
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
index 395e56f..85bb651 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.js
@@ -133,45 +133,59 @@
 				}
 			};
 		});
+
+		frm.set_query('employee', 'employees', () => {
+			if (!frm.doc.company) {
+				frappe.msgprint(__("Please set a Company"));
+				return [];
+			}
+			return {
+				query: "erpnext.payroll.doctype.payroll_entry.payroll_entry.employee_query",
+				filters: frm.events.get_employee_filters(frm)
+			};
+		});
+	},
+
+	get_employee_filters: function (frm) {
+		let filters = {};
+		filters['company'] = frm.doc.company;
+		filters['start_date'] = frm.doc.start_date;
+		filters['end_date'] = frm.doc.end_date;
+
+		if (frm.doc.department) {
+			filters['department'] = frm.doc.department;
+		}
+		if (frm.doc.branch) {
+			filters['branch'] = frm.doc.branch;
+		}
+		if (frm.doc.designation) {
+			filters['designation'] = frm.doc.designation;
+		}
+		if (frm.doc.employees) {
+			filters['employees'] = frm.doc.employees.filter(d => d.employee).map(d => d.employee);
+		}
+		return filters;
 	},
 
 	payroll_frequency: function (frm) {
 		frm.trigger("set_start_end_dates").then( ()=> {
 			frm.events.clear_employee_table(frm);
-			frm.events.get_employee_with_salary_slip_and_set_query(frm);
-		});
-	},
-
-	employee_filters: function (frm, emp_list) {
-		frm.set_query('employee', 'employees', () => {
-			return {
-				filters: {
-					name: ["not in", emp_list]
-				}
-			};
-		});
-	},
-
-	get_employee_with_salary_slip_and_set_query: function (frm) {
-		frappe.db.get_list('Salary Slip', {
-			filters: {
-				start_date: frm.doc.start_date,
-				end_date: frm.doc.end_date,
-				docstatus: 1,
-			},
-			fields: ['employee']
-		}).then((emp) => {
-			var emp_list = [];
-			emp.forEach((employee_data) => {
-				emp_list.push(Object.values(employee_data)[0]);
-			});
-			frm.events.employee_filters(frm, emp_list);
 		});
 	},
 
 	company: function (frm) {
 		frm.events.clear_employee_table(frm);
 		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+		frm.trigger("set_payable_account_and_currency");
+	},
+
+	set_payable_account_and_currency: function (frm) {
+		frappe.db.get_value("Company", {"name": frm.doc.company}, "default_currency", (r) => {
+			frm.set_value('currency', r.default_currency);
+		});
+		frappe.db.get_value("Company", {"name": frm.doc.company}, "default_payroll_payable_account", (r) => {
+			frm.set_value('payroll_payable_account', r.default_payroll_payable_account);
+		});
 	},
 
 	currency: function (frm) {
@@ -345,11 +359,3 @@
 		})
 	);
 };
-
-frappe.ui.form.on('Payroll Employee Detail', {
-	employee: function(frm) {
-		if (!frm.doc.payroll_frequency) {
-			frappe.throw(__("Please set a Payroll Frequency"));
-		}
-	}
-});
diff --git a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
index 6bcd4e0..fde2e07 100644
--- a/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/payroll_entry.py
@@ -10,16 +10,17 @@
 from frappe import _
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.hr.doctype.employee.employee import get_holiday_list_for_employee
+from frappe.desk.reportview import get_match_cond, get_filters_cond
 
 class PayrollEntry(Document):
 	def onload(self):
 		if not self.docstatus==1 or self.salary_slips_submitted:
-    			return
+			return
 
 		# check if salary slips were manually submitted
 		entries = frappe.db.count("Salary Slip", {'payroll_entry': self.name, 'docstatus': 1}, ['name'])
 		if cint(entries) == len(self.employees):
-				self.set_onload("submitted_ss", True)
+			self.set_onload("submitted_ss", True)
 
 	def validate(self):
 		self.number_of_employees = len(self.employees)
@@ -59,16 +60,16 @@
 			condition = """and payroll_frequency = '%(payroll_frequency)s'"""% {"payroll_frequency": self.payroll_frequency}
 
 		sal_struct = frappe.db.sql_list("""
-				select
-					name from `tabSalary Structure`
-				where
-					docstatus = 1 and
-					is_active = 'Yes'
-					and company = %(company)s
-					and currency = %(currency)s and
-					ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
-					{condition}""".format(condition=condition),
-				{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
+			select
+				name from `tabSalary Structure`
+			where
+				docstatus = 1 and
+				is_active = 'Yes'
+				and company = %(company)s
+				and currency = %(currency)s and
+				ifnull(salary_slip_based_on_timesheet,0) = %(salary_slip_based_on_timesheet)s
+				{condition}""".format(condition=condition),
+			{"company": self.company, "currency": self.currency, "salary_slip_based_on_timesheet":self.salary_slip_based_on_timesheet})
 
 		if sal_struct:
 			cond += "and t2.salary_structure IN %(sal_struct)s "
@@ -95,6 +96,7 @@
 
 		return emp_list
 
+	@frappe.whitelist()
 	def fill_employee_details(self):
 		self.set('employees', [])
 		employees = self.get_emp_list()
@@ -142,6 +144,7 @@
 			if not self.get(fieldname):
 				frappe.throw(_("Please set {0}").format(self.meta.get_label(fieldname)))
 
+	@frappe.whitelist()
 	def create_salary_slips(self):
 		"""
 			Creates salary slip for selected employees if already not created
@@ -174,15 +177,15 @@
 		"""
 			Returns list of salary slips based on selected criteria
 		"""
-		cond = self.get_filter_condition()
 
 		ss_list = frappe.db.sql("""
 			select t1.name, t1.salary_structure, t1.payroll_cost_center from `tabSalary Slip` t1
-			where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s
-			and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s %s
-		""" % ('%s', '%s', '%s','%s', cond), (ss_status, self.start_date, self.end_date, self.salary_slip_based_on_timesheet), as_dict=as_dict)
+			where t1.docstatus = %s and t1.start_date >= %s and t1.end_date <= %s and t1.payroll_entry = %s
+			and (t1.journal_entry is null or t1.journal_entry = "") and ifnull(salary_slip_based_on_timesheet,0) = %s
+		""", (ss_status, self.start_date, self.end_date, self.name, self.salary_slip_based_on_timesheet), as_dict=as_dict)
 		return ss_list
 
+	@frappe.whitelist()
 	def submit_salary_slips(self):
 		self.check_permission('write')
 		ss_list = self.get_sal_slip_list(ss_status=0)
@@ -268,26 +271,26 @@
 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount += flt(amount, precision)
 				accounts.append({
-						"account": acc_cc[0],
-						"debit_in_account_currency": flt(amt, precision),
-						"exchange_rate": flt(exchange_rate),
-						"party_type": '',
-						"cost_center": acc_cc[1] or self.cost_center,
-						"project": self.project
-					})
+					"account": acc_cc[0],
+					"debit_in_account_currency": flt(amt, precision),
+					"exchange_rate": flt(exchange_rate),
+					"party_type": '',
+					"cost_center": acc_cc[1] or self.cost_center,
+					"project": self.project
+				})
 
 			# Deductions
 			for acc_cc, amount in deductions.items():
 				exchange_rate, amt = self.get_amount_and_exchange_rate_for_journal_entry(acc_cc[0], amount, company_currency, currencies)
 				payable_amount -= flt(amount, precision)
 				accounts.append({
-						"account": acc_cc[0],
-						"credit_in_account_currency": flt(amt, precision),
-						"exchange_rate": flt(exchange_rate),
-						"cost_center": acc_cc[1] or self.cost_center,
-						"party_type": '',
-						"project": self.project
-					})
+					"account": acc_cc[0],
+					"credit_in_account_currency": flt(amt, precision),
+					"exchange_rate": flt(exchange_rate),
+					"cost_center": acc_cc[1] or self.cost_center,
+					"party_type": '',
+					"project": self.project
+				})
 
 			# Payable amount
 			exchange_rate, payable_amt = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, payable_amount, company_currency, currencies)
@@ -329,13 +332,13 @@
 		amount = flt(amount) * flt(conversion_rate)
 		return exchange_rate, amount
 
+	@frappe.whitelist()
 	def make_payment_entry(self):
 		self.check_permission('write')
 
-		cond = self.get_filter_condition()
 		salary_slip_name_list = frappe.db.sql(""" select t1.name from `tabSalary Slip` t1
-			where t1.docstatus = 1 and start_date >= %s and end_date <= %s %s
-			""" % ('%s', '%s', cond), (self.start_date, self.end_date), as_list = True)
+			where t1.docstatus = 1 and start_date >= %s and end_date <= %s and t1.payroll_entry = %s
+			""", (self.start_date, self.end_date, self.name), as_list = True)
 
 		if salary_slip_name_list and len(salary_slip_name_list) > 0:
 			salary_slip_total = 0
@@ -367,20 +370,20 @@
 
 		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(self.payment_account, je_payment_amount, company_currency, currencies)
 		accounts.append({
-				"account": self.payment_account,
-				"bank_account": self.bank_account,
-				"credit_in_account_currency": flt(amount, precision),
-				"exchange_rate": flt(exchange_rate),
-			})
+			"account": self.payment_account,
+			"bank_account": self.bank_account,
+			"credit_in_account_currency": flt(amount, precision),
+			"exchange_rate": flt(exchange_rate),
+		})
 
 		exchange_rate, amount = self.get_amount_and_exchange_rate_for_journal_entry(payroll_payable_account, je_payment_amount, company_currency, currencies)
 		accounts.append({
-				"account": payroll_payable_account,
-				"debit_in_account_currency": flt(amount, precision),
-				"exchange_rate": flt(exchange_rate),
-				"reference_type": self.doctype,
-				"reference_name": self.name
-			})
+			"account": payroll_payable_account,
+			"debit_in_account_currency": flt(amount, precision),
+			"exchange_rate": flt(exchange_rate),
+			"reference_type": self.doctype,
+			"reference_name": self.name
+		})
 
 		if len(currencies) > 1:
 				multi_currency = 1
@@ -406,6 +409,7 @@
 		self.update(get_start_end_dates(self.payroll_frequency,
 			self.start_date or self.posting_date, self.company))
 
+	@frappe.whitelist()
 	def validate_employee_attendance(self):
 		employees_to_mark_attendance = []
 		days_in_payroll, days_holiday, days_attendance_marked = 0, 0, 0
@@ -421,7 +425,7 @@
 				employees_to_mark_attendance.append({
 					"employee": employee_detail.employee,
 					"employee_name": employee_detail.employee_name
-					})
+				})
 		return employees_to_mark_attendance
 
 	def get_count_holidays_of_employee(self, employee, start_date):
@@ -438,11 +442,11 @@
 	def get_count_employee_attendance(self, employee, start_date):
 		marked_days = 0
 		attendances = frappe.get_all("Attendance",
-				fields = ["count(*)"],
-				filters = {
-					"employee": employee,
-					"attendance_date": ('between', [start_date, self.end_date])
-				}, as_list=1)
+			fields = ["count(*)"],
+			filters = {
+				"employee": employee,
+				"attendance_date": ('between', [start_date, self.end_date])
+			}, as_list=1)
 		if attendances and attendances[0][0]:
 			marked_days = attendances[0][0]
 		return marked_days
@@ -550,6 +554,7 @@
 def create_salary_slips_for_employees(employees, args, publish_progress=True):
 	salary_slips_exists_for = get_existing_salary_slips(employees, args)
 	count=0
+	salary_slips_not_created = []
 	for emp in employees:
 		if emp not in salary_slips_exists_for:
 			args.update({
@@ -563,33 +568,24 @@
 				frappe.publish_progress(count*100/len(set(employees) - set(salary_slips_exists_for)),
 					title = _("Creating Salary Slips..."))
 		else:
-			salary_slip_name = frappe.db.sql(
-				'''SELECT
-						name
-					FROM `tabSalary Slip`
-					WHERE company=%s
-					AND start_date >= %s
-					AND end_date <= %s
-					AND employee = %s
-				''', (args.company, args.start_date, args.end_date, emp), as_dict=True)
-
-			salary_slip_doc = frappe.get_doc('Salary Slip', salary_slip_name[0].name)
-			salary_slip_doc.exchange_rate = args.exchange_rate
-			salary_slip_doc.set_totals()
-			salary_slip_doc.db_update()
+			salary_slips_not_created.append(emp)
 
 	payroll_entry = frappe.get_doc("Payroll Entry", args.payroll_entry)
 	payroll_entry.db_set("salary_slips_created", 1)
 	payroll_entry.notify_update()
 
+	if salary_slips_not_created:
+		frappe.msgprint(_("Salary Slips already exists for employees {}, and will not be processed by this payroll.")
+			.format(frappe.bold(", ".join([emp for emp in salary_slips_not_created]))) , title=_("Message"), indicator="orange")
+
 def get_existing_salary_slips(employees, args):
 	return frappe.db.sql_list("""
 		select distinct employee from `tabSalary Slip`
-		where docstatus!= 2 and company = %s
+		where docstatus!= 2 and company = %s and payroll_entry = %s
 			and start_date >= %s and end_date <= %s
 			and employee in (%s)
-	""" % ('%s', '%s', '%s', ', '.join(['%s']*len(employees))),
-		[args.company, args.start_date, args.end_date] + employees)
+	""" % ('%s', '%s', '%s', '%s', ', '.join(['%s']*len(employees))),
+		[args.company, args.payroll_entry, args.start_date, args.end_date] + employees)
 
 def submit_salary_slips_for_employees(payroll_entry, salary_slips, publish_progress=True):
 	submitted_ss = []
@@ -641,3 +637,61 @@
 			'txt': "%%%s%%" % frappe.db.escape(txt),
 			'start': start, 'page_len': page_len
 		})
+
+def get_employee_with_existing_salary_slip(start_date, end_date, company):
+	return frappe.db.sql_list("""
+		select employee from `tabSalary Slip`
+		where
+			(start_date between %(start_date)s and %(end_date)s
+		or
+			end_date between %(start_date)s and %(end_date)s
+		or
+			%(start_date)s between start_date and end_date)
+		and company = %(company)s
+		and docstatus = 1
+	""", {'start_date': start_date, 'end_date': end_date, 'company': company})
+
+@frappe.whitelist()
+@frappe.validate_and_sanitize_search_inputs
+def employee_query(doctype, txt, searchfield, start, page_len, filters):
+	filters = frappe._dict(filters)
+	conditions = []
+	exclude_employees = []
+	emp_cond = ''
+	if filters.start_date and filters.end_date:
+		employee_list = get_employee_with_existing_salary_slip(filters.start_date, filters.end_date, filters.company)
+		emp = filters.get('employees')
+		filters.pop('start_date')
+		filters.pop('end_date')
+		if filters.employees is not None:
+			filters.pop('employees')
+		if employee_list:
+			exclude_employees.extend(employee_list)
+		if emp:
+			exclude_employees.extend(emp)
+		if exclude_employees:
+			emp_cond += 'and employee not in %(exclude_employees)s'
+
+	return frappe.db.sql("""select name, employee_name from `tabEmployee`
+		where status = 'Active'
+			and docstatus < 2
+			and ({key} like %(txt)s
+				or employee_name like %(txt)s)
+			{emp_cond}
+			{fcond} {mcond}
+		order by
+			if(locate(%(_txt)s, name), locate(%(_txt)s, name), 99999),
+			if(locate(%(_txt)s, employee_name), locate(%(_txt)s, employee_name), 99999),
+			idx desc,
+			name, employee_name
+		limit %(start)s, %(page_len)s""".format(**{
+			'key': searchfield,
+			'fcond': get_filters_cond(doctype, filters, conditions),
+			'mcond': get_match_cond(doctype),
+			'emp_cond': emp_cond
+		}), {
+			'txt': "%%%s%%" % txt,
+			'_txt': txt.replace("%", ""),
+			'start': start,
+			'page_len': page_len,
+			'exclude_employees': exclude_employees})
diff --git a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
index e098ec7..7528bf7 100644
--- a/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
+++ b/erpnext/payroll/doctype/payroll_entry/test_payroll_entry.py
@@ -12,7 +12,7 @@
 from erpnext.payroll.doctype.salary_slip.test_salary_slip import get_salary_component_account, \
 		make_earning_salary_component, make_deduction_salary_component, create_account, make_employee_salary_slip
 from erpnext.payroll.doctype.salary_structure.test_salary_structure import make_salary_structure, create_salary_structure_assignment
-from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry
+from erpnext.loan_management.doctype.loan.test_loan import create_loan, make_loan_disbursement_entry, create_loan_type, create_loan_accounts
 from erpnext.loan_management.doctype.process_loan_interest_accrual.process_loan_interest_accrual import process_loan_interest_accrual_for_term_loans
 
 class TestPayrollEntry(unittest.TestCase):
@@ -51,21 +51,22 @@
 
 		company_doc = frappe.get_doc('Company', company)
 		salary_structure = make_salary_structure("_Test Multi Currency Salary Structure", "Monthly", company=company, currency='USD')
-		create_salary_structure_assignment(employee, salary_structure.name, company=company)
+		create_salary_structure_assignment(employee, salary_structure.name, company=company, currency='USD')
 		frappe.db.sql("""delete from `tabSalary Slip` where employee=%s""",(frappe.db.get_value("Employee", {"user_id": "test_muti_currency_employee@payroll.com"})))
 		salary_slip = get_salary_slip("test_muti_currency_employee@payroll.com", "Monthly", "_Test Multi Currency Salary Structure")
 		dates = get_start_end_dates('Monthly', nowdate())
-		payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date, 
+		payroll_entry = make_payroll_entry(start_date=dates.start_date, end_date=dates.end_date,
 			payable_account=company_doc.default_payroll_payable_account, currency='USD', exchange_rate=70)
 		payroll_entry.make_payment_entry()
 
 		salary_slip.load_from_db()
 
 		payroll_je = salary_slip.journal_entry
-		payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
+		if payroll_je:
+			payroll_je_doc = frappe.get_doc('Journal Entry', payroll_je)
 
-		self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
-		self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
+			self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_debit)
+			self.assertEqual(salary_slip.base_gross_pay, payroll_je_doc.total_credit)
 
 		payment_entry = frappe.db.sql('''
 			Select ifnull(sum(je.total_debit),0) as total_debit, ifnull(sum(je.total_credit),0) as total_credit from `tabJournal Entry` je, `tabJournal Entry Account` jea
@@ -168,15 +169,23 @@
 		salary_structure = "Test Salary Structure for Loan"
 		make_salary_structure(salary_structure, "Monthly", employee=employee_doc.name, company="_Test Company", currency=company_doc.default_currency)
 
+		if not frappe.db.exists("Loan Type", "Car Loan"):
+			create_loan_accounts()
+			create_loan_type("Car Loan", 500000, 8.4,
+				is_term_loan=1,
+				mode_of_payment='Cash',
+				payment_account='Payment Account - _TC',
+				loan_account='Loan Account - _TC',
+				interest_income_account='Interest Income Account - _TC',
+				penalty_income_account='Penalty Income Account - _TC')
+
 		loan = create_loan(applicant, "Car Loan", 280000, "Repay Over Number of Periods", 20, posting_date=add_months(nowdate(), -1))
 		loan.repay_from_salary = 1
 		loan.submit()
 
 		make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=add_months(nowdate(), -1))
-
 		process_loan_interest_accrual_for_term_loans(posting_date=nowdate())
 
-
 		dates = get_start_end_dates('Monthly', nowdate())
 		make_payroll_entry(company="_Test Company", start_date=dates.start_date, payable_account=company_doc.default_payroll_payable_account,
 			currency=company_doc.default_currency, end_date=dates.end_date, branch=branch, cost_center="Main - _TC", payment_account="Cash - _TC")
@@ -267,4 +276,4 @@
 	salary_slip.calculate_net_pay()
 	salary_slip.db_update()
 
-	return salary_slip
\ No newline at end of file
+	return salary_slip
diff --git a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
index 6647230..cd563bc 100644
--- a/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
+++ b/erpnext/payroll/doctype/retention_bonus/retention_bonus.json
@@ -93,7 +93,6 @@
    "reqd": 1
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.employee)",
    "fieldname": "currency",
    "fieldtype": "Link",
@@ -106,7 +105,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-10-20 17:27:47.003134",
+ "modified": "2021-03-31 14:50:29.401020",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Retention Bonus",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.js b/erpnext/payroll/doctype/salary_slip/salary_slip.js
index 7460c75..e3993fa 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.js
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.js
@@ -39,7 +39,8 @@
 
 		frm.set_query("employee", function() {
 			return {
-				query: "erpnext.controllers.queries.employee_query"
+				query: "erpnext.controllers.queries.employee_query",
+				filters: frm.doc.company
 			};
 		});
 	},
@@ -74,17 +75,22 @@
 		if (!frm.doc.letter_head && company.default_letter_head) {
 			frm.set_value('letter_head', company.default_letter_head);
 		}
+	},
+
+	currency: function(frm) {
 		frm.trigger("set_dynamic_labels");
 	},
 
 	set_dynamic_labels: function(frm) {
 		var company_currency = frm.doc.company? erpnext.get_currency(frm.doc.company): frappe.defaults.get_default("currency");
-		frappe.run_serially([
-			() => 	frm.events.set_exchange_rate(frm, company_currency),
-			() => 	frm.events.change_form_labels(frm, company_currency),
-			() => 	frm.events.change_grid_labels(frm),
-			() => 	frm.refresh_fields()
-		]);
+		if (frm.doc.employee && frm.doc.currency) {
+			frappe.run_serially([
+				() => 	frm.events.set_exchange_rate(frm, company_currency),
+				() => 	frm.events.change_form_labels(frm, company_currency),
+				() => 	frm.events.change_grid_labels(frm),
+				() => 	frm.refresh_fields()
+			]);
+		}
 	},
 
 	set_exchange_rate: function(frm, company_currency) {
@@ -100,10 +106,12 @@
 							to_currency: company_currency,
 						},
 						callback: function(r) {
-							frm.set_value("exchange_rate", flt(r.message));
-							frm.set_df_property('exchange_rate', 'hidden', 0);
-							frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
-								+ " = [?] " + company_currency);
+							if (r.message) {
+								frm.set_value("exchange_rate", flt(r.message));
+								frm.set_df_property('exchange_rate', 'hidden', 0);
+								frm.set_df_property("exchange_rate", "description", "1 " + frm.doc.currency
+									+ " = [?] " + company_currency);
+							}
 						}
 					});
 				} else {
@@ -111,7 +119,6 @@
 					frm.set_df_property('exchange_rate', 'hidden', 1);
 					frm.set_df_property("exchange_rate", "description", "" );
 				}
-			}
 		}
 	},
 
@@ -213,7 +220,7 @@
 });
 
 var set_totals = function(frm) {
-	if (frm.doc.docstatus === 0) {
+	if (frm.doc.docstatus === 0 && frm.doc.doctype === "Salary Slip") {
 		if (frm.doc.earnings || frm.doc.deductions) {
 			frappe.call({
 				method: "set_totals",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.json b/erpnext/payroll/doctype/salary_slip/salary_slip.json
index 6688368..ec56076 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.json
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.json
@@ -500,7 +500,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
    "fetch_from": "salary_structure.currency",
    "fieldname": "currency",
@@ -632,7 +631,7 @@
  "idx": 9,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-02-19 11:48:05.383945",
+ "modified": "2021-03-31 15:39:28.817166",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Slip",
diff --git a/erpnext/payroll/doctype/salary_slip/salary_slip.py b/erpnext/payroll/doctype/salary_slip/salary_slip.py
index a04a635..f6d4c7b 100644
--- a/erpnext/payroll/doctype/salary_slip/salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/salary_slip.py
@@ -124,9 +124,12 @@
 
 	def check_existing(self):
 		if not self.salary_slip_based_on_timesheet:
+			cond = ""
+			if self.payroll_entry:
+				cond += "and payroll_entry = '{0}'".format(self.payroll_entry)
 			ret_exist = frappe.db.sql("""select name from `tabSalary Slip`
 						where start_date = %s and end_date = %s and docstatus != 2
-						and employee = %s and name != %s""",
+						and employee = %s and name != %s {0}""".format(cond),
 						(self.start_date, self.end_date, self.employee, self.name))
 			if ret_exist:
 				self.employee = ''
@@ -142,6 +145,7 @@
 			self.start_date = date_details.start_date
 			self.end_date = date_details.end_date
 
+	@frappe.whitelist()
 	def get_emp_and_working_day_details(self):
 		'''First time, load all the components from salary structure'''
 		if self.employee:
@@ -617,13 +621,16 @@
 
 			component_row = self.append(component_type)
 			for attr in (
-				'depends_on_payment_days', 'salary_component', 'abbr'
+				'depends_on_payment_days', 'salary_component',
 				'do_not_include_in_total', 'is_tax_applicable',
 				'is_flexible_benefit', 'variable_based_on_taxable_salary',
 				'exempted_from_income_tax'
 			):
 				component_row.set(attr, component_data.get(attr))
 
+			abbr = component_data.get('abbr') or component_data.get('salary_component_abbr')
+			component_row.set('abbr', abbr)
+
 		if additional_salary:
 			component_row.default_amount = 0
 			component_row.additional_amount = amount
@@ -1049,7 +1056,7 @@
 			repayment_entry.save()
 			repayment_entry.submit()
 
-			loan.loan_repayment_entry = repayment_entry.name
+			frappe.db.set_value("Salary Slip Loan", loan.name, "loan_repayment_entry", repayment_entry.name)
 
 	def cancel_loan_repayment_entry(self):
 		for loan in self.loans:
@@ -1114,10 +1121,12 @@
 			self.bank_name = emp.bank_name
 			self.bank_account_no = emp.bank_ac_no
 
+	@frappe.whitelist()
 	def process_salary_based_on_working_days(self):
 		self.get_working_days_details(lwp=self.leave_without_pay)
 		self.calculate_net_pay()
 
+	@frappe.whitelist()
 	def set_totals(self):
 		self.gross_pay = 0.0
 		if self.salary_slip_based_on_timesheet == 1:
diff --git a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
index 143a306..7672695 100644
--- a/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
+++ b/erpnext/payroll/doctype/salary_slip/test_salary_slip.py
@@ -361,7 +361,6 @@
 		# as per assigned salary structure 40500 in monthly salary so 236000*5/100/12
 		frappe.db.sql("""delete from `tabPayroll Period`""")
 		frappe.db.sql("""delete from `tabSalary Component`""")
-		frappe.db.sql("""delete from `tabAdditional Salary`""")
 
 		payroll_period = create_payroll_period()
 
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.js b/erpnext/payroll/doctype/salary_structure/salary_structure.js
index 6aa1387..b539b1b 100755
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.js
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.js
@@ -111,12 +111,19 @@
 				frappe.set_route('Form', 'Salary Structure Assignment', doc.name);
 			});
 			frm.add_custom_button(__("Assign to Employees"),function () {
-			frm.trigger('assign_to_employees')
-		})
+				frm.trigger('assign_to_employees')
+			})
 		}
+
+		// set columns read-only
 		let fields_read_only = ["is_tax_applicable", "is_flexible_benefit", "variable_based_on_taxable_salary"];
 		fields_read_only.forEach(function(field) {
-			frappe.meta.get_docfield("Salary Detail", field, frm.doc.name).read_only = 1;
+			frm.fields_dict.earnings.grid.update_docfield_property(
+				field, 'read_only', 1
+			);
+			frm.fields_dict.deductions.grid.update_docfield_property(
+				field, 'read_only', 1
+			);
 		});
 		frm.trigger('set_earning_deduction_component');
 	},
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.json b/erpnext/payroll/doctype/salary_structure/salary_structure.json
index de56fc8..5dd1d70 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.json
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.json
@@ -232,7 +232,7 @@
  "idx": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-30 11:30:32.190798",
+ "modified": "2021-03-31 15:41:12.342380",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Structure",
diff --git a/erpnext/payroll/doctype/salary_structure/salary_structure.py b/erpnext/payroll/doctype/salary_structure/salary_structure.py
index 1712081..352c180 100644
--- a/erpnext/payroll/doctype/salary_structure/salary_structure.py
+++ b/erpnext/payroll/doctype/salary_structure/salary_structure.py
@@ -100,7 +100,7 @@
 					from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
 			else:
 				assign_salary_structure_for_employees(employees, self,
-					payroll_payable_account=payroll_payable_account, 
+					payroll_payable_account=payroll_payable_account,
 					from_date=from_date, base=base, variable=variable, income_tax_slab=income_tax_slab)
 		else:
 			frappe.msgprint(_("No Employee Found"))
diff --git a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
index 92bb347..50fabed 100644
--- a/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
+++ b/erpnext/payroll/doctype/salary_structure_assignment/salary_structure_assignment.json
@@ -125,7 +125,6 @@
    "options": "Income Tax Slab"
   },
   {
-   "default": "Company:company:default_currency",
    "depends_on": "eval:(doc.docstatus==1 || doc.salary_structure)",
    "fetch_from": "salary_structure.currency",
    "fieldname": "currency",
@@ -146,7 +145,7 @@
  ],
  "is_submittable": 1,
  "links": [],
- "modified": "2020-11-30 18:07:48.251311",
+ "modified": "2021-03-31 15:49:36.361253",
  "modified_by": "Administrator",
  "module": "Payroll",
  "name": "Salary Structure Assignment",
diff --git a/erpnext/portal/doctype/products_settings/products_settings.js b/erpnext/portal/doctype/products_settings/products_settings.js
index b68b5d7..2f8b037 100644
--- a/erpnext/portal/doctype/products_settings/products_settings.js
+++ b/erpnext/portal/doctype/products_settings/products_settings.js
@@ -10,10 +10,12 @@
 				df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
 			).map(df => ({ label: df.label, value: df.fieldname }));
 
-			const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname);
-			field.fieldtype = 'Select';
-			field.options = valid_fields;
-			frm.fields_dict.filter_fields.grid.refresh();
+			frm.fields_dict.filter_fields.grid.update_docfield_property(
+				'fieldname', 'fieldtype', 'Select'
+			);
+			frm.fields_dict.filter_fields.grid.update_docfield_property(
+				'fieldname', 'options', valid_fields
+			);
 		});
 	}
 });
diff --git a/erpnext/portal/product_configurator/test_product_configurator.py b/erpnext/portal/product_configurator/test_product_configurator.py
index 97042db..3521e7e 100644
--- a/erpnext/portal/product_configurator/test_product_configurator.py
+++ b/erpnext/portal/product_configurator/test_product_configurator.py
@@ -10,8 +10,38 @@
 test_dependencies = ["Item"]
 
 class TestProductConfigurator(unittest.TestCase):
-	def setUp(self):
-		self.create_variant_item()
+	@classmethod
+	def setUpClass(cls):
+		cls.create_variant_item()
+
+	@classmethod
+	def create_variant_item(cls):
+		if not frappe.db.exists('Item', '_Test Variant Item - 2XL'):
+			frappe.get_doc({
+				"description": "_Test Variant Item - 2XL",
+				"item_code": "_Test Variant Item - 2XL",
+				"item_name": "_Test Variant Item - 2XL",
+				"doctype": "Item",
+				"is_stock_item": 1,
+				"variant_of": "_Test Variant Item",
+				"item_group": "_Test Item Group",
+				"stock_uom": "_Test UOM",
+				"item_defaults": [{
+					"company": "_Test Company",
+					"default_warehouse": "_Test Warehouse - _TC",
+					"expense_account": "_Test Account Cost for Goods Sold - _TC",
+					"buying_cost_center": "_Test Cost Center - _TC",
+					"selling_cost_center": "_Test Cost Center - _TC",
+					"income_account": "Sales - _TC"
+				}],
+				"attributes": [
+					{
+						"attribute": "Test Size",
+						"attribute_value": "2XL"
+					}
+				],
+				"show_variant_in_website": 1
+			}).insert()
 
 	def test_product_list(self):
 		template_items = frappe.get_all('Item', {'show_in_website': 1})
@@ -46,39 +76,6 @@
 
 	def test_get_products_for_website(self):
 		items = get_products_for_website(attribute_filters={
-			'Test Size': ['Medium']
+			'Test Size': ['2XL']
 		})
 		self.assertEqual(len(items), 1)
-
-
-	def create_variant_item(self):
-		if not frappe.db.exists('Item', '_Test Variant Item 1'):
-			frappe.get_doc({
-				"description": "_Test Variant Item 12",
-				"doctype": "Item",
-				"is_stock_item": 1,
-				"variant_of": "_Test Variant Item",
-				"item_code": "_Test Variant Item 1",
-				"item_group": "_Test Item Group",
-				"item_name": "_Test Variant Item 1",
-				"stock_uom": "_Test UOM",
-				"item_defaults": [{
-					"company": "_Test Company",
-					"default_warehouse": "_Test Warehouse - _TC",
-					"expense_account": "_Test Account Cost for Goods Sold - _TC",
-					"buying_cost_center": "_Test Cost Center - _TC",
-					"selling_cost_center": "_Test Cost Center - _TC",
-					"income_account": "Sales - _TC"
-				}],
-				"attributes": [
-					{
-						"attribute": "Test Size",
-						"attribute_value": "Medium"
-					}
-				],
-				"show_variant_in_website": 1
-			}).insert()
-
-
-	def tearDown(self):
-		frappe.db.rollback()
\ No newline at end of file
diff --git a/erpnext/portal/product_configurator/utils.py b/erpnext/portal/product_configurator/utils.py
index 21fd7c2..d77eb2c 100644
--- a/erpnext/portal/product_configurator/utils.py
+++ b/erpnext/portal/product_configurator/utils.py
@@ -298,7 +298,7 @@
 
 
 def get_items(filters=None, search=None):
-	start = frappe.form_dict.start or 0
+	start = frappe.form_dict.get('start', 0)
 	products_settings = get_product_settings()
 	page_length = products_settings.products_per_page
 
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 077011a..c5265e2 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -18,8 +18,8 @@
 		};
 	},
 	onload: function (frm) {
-		var so = frappe.meta.get_docfield("Project", "sales_order");
-		so.get_route_options_for_new_doc = function (field) {
+		const so = frm.get_docfield("sales_order");
+		so.get_route_options_for_new_doc = () => {
 			if (frm.is_new()) return;
 			return {
 				"customer": frm.doc.customer,
diff --git a/erpnext/projects/doctype/project/project.py b/erpnext/projects/doctype/project/project.py
index 8ba0b6c..f9e1359 100644
--- a/erpnext/projects/doctype/project/project.py
+++ b/erpnext/projects/doctype/project/project.py
@@ -81,12 +81,18 @@
 
 	def calculate_start_date(self, task_details):
 		self.start_date = add_days(self.expected_start_date, task_details.start)
-		self.start_date = update_if_holiday(self.holiday_list, self.start_date)
+		self.start_date = self.update_if_holiday(self.start_date)
 		return self.start_date
 
 	def calculate_end_date(self, task_details):
 		self.end_date = add_days(self.start_date, task_details.duration)
-		return update_if_holiday(self.holiday_list, self.end_date)
+		return self.update_if_holiday(self.end_date)
+
+	def update_if_holiday(self, date):
+		holiday_list = self.holiday_list or get_holiday_list(self.company)
+		while is_holiday(holiday_list, date):
+			date = add_days(date, 1)
+		return date
 
 	def dependency_mapping(self, template_tasks, project_tasks):
 		for template_task in template_tasks:
@@ -541,9 +547,3 @@
 
 	project.status = status
 	project.save()
-
-def update_if_holiday(holiday_list, date):
-	holiday_list = holiday_list or get_holiday_list()
-	while is_holiday(holiday_list, date):
-		date = add_days(date, 1)
-	return date
diff --git a/erpnext/projects/doctype/project/test_project.py b/erpnext/projects/doctype/project/test_project.py
index 6290538..70139c6 100644
--- a/erpnext/projects/doctype/project/test_project.py
+++ b/erpnext/projects/doctype/project/test_project.py
@@ -4,13 +4,14 @@
 
 
 import frappe, unittest
+from frappe.utils import getdate, nowdate, add_days
+
+from erpnext.projects.doctype.project_template.test_project_template import make_project_template
+from erpnext.projects.doctype.task.test_task import create_task
+
 test_records = frappe.get_test_records('Project')
 test_ignore = ["Sales Order"]
 
-from erpnext.projects.doctype.project_template.test_project_template import make_project_template
-from erpnext.projects.doctype.project.project import update_if_holiday
-from erpnext.projects.doctype.task.test_task import create_task
-from frappe.utils import getdate, nowdate, add_days
 
 class TestProject(unittest.TestCase):
 	def test_project_with_template_having_no_parent_and_depend_tasks(self):
@@ -32,12 +33,16 @@
 
 	def test_project_template_having_parent_child_tasks(self):
 		project_name = "Test Project with Template - Tasks with Parent-Child Relation"
+
+		if frappe.db.get_value('Project', {'project_name': project_name}, 'name'):
+			project_name = frappe.db.get_value('Project', {'project_name': project_name}, 'name')
+
 		frappe.db.sql(""" delete from tabTask where project = %s """, project_name)
 		frappe.delete_doc('Project', project_name)
 
 		task1 = task_exists("Test Template Task Parent")
 		if not task1:
-			task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=4)
+			task1 = create_task(subject="Test Template Task Parent", is_group=1, is_template=1, begin=1, duration=10)
 
 		task2 = task_exists("Test Template Task Child 1")
 		if not task2:
@@ -52,7 +57,7 @@
 		tasks = frappe.get_all('Task', ['subject','exp_end_date','depends_on_tasks', 'name', 'parent_task'], dict(project=project.name), order_by='creation asc')
 
 		self.assertEqual(tasks[0].subject, 'Test Template Task Parent')
-		self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 4))
+		self.assertEqual(getdate(tasks[0].exp_end_date), calculate_end_date(project, 1, 10))
 
 		self.assertEqual(tasks[1].subject, 'Test Template Task Child 1')
 		self.assertEqual(getdate(tasks[1].exp_end_date), calculate_end_date(project, 1, 3))
@@ -97,7 +102,8 @@
 		project_name = name,
 		status = 'Open',
 		project_template = template.name,
-		expected_start_date = nowdate()
+		expected_start_date = nowdate(),
+		company="_Test Company"
 	)).insert()
 
 	return project
@@ -112,7 +118,8 @@
 		doctype = 'Project',
 		project_name = args.project_name,
 		status = 'Open',
-		expected_start_date = args.start_date
+		expected_start_date = args.start_date,
+		company= args.company or '_Test Company'
 	))
 
 	if args.project_template_name:
@@ -131,7 +138,7 @@
 
 def calculate_end_date(project, start, duration):
 	start = add_days(project.expected_start_date, start)
-	start = update_if_holiday(project.holiday_list, start)
+	start = project.update_if_holiday(start)
 	end = add_days(start, duration)
-	end = update_if_holiday(project.holiday_list, end)
-	return getdate(end)
\ No newline at end of file
+	end = project.update_if_holiday(end)
+	return getdate(end)
diff --git a/erpnext/projects/doctype/timesheet/test_timesheet.py b/erpnext/projects/doctype/timesheet/test_timesheet.py
index 4cb3804..f7c764e 100644
--- a/erpnext/projects/doctype/timesheet/test_timesheet.py
+++ b/erpnext/projects/doctype/timesheet/test_timesheet.py
@@ -13,9 +13,18 @@
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 from erpnext.payroll.doctype.salary_structure.test_salary_structure \
 	import make_salary_structure, create_salary_structure_assignment
+from erpnext.payroll.doctype.salary_slip.test_salary_slip import (
+	make_earning_salary_component,
+	make_deduction_salary_component
+)
 from erpnext.hr.doctype.employee.test_employee import make_employee
 
 class TestTimesheet(unittest.TestCase):
+	@classmethod
+	def setUpClass(cls):
+		make_earning_salary_component(setup=True, company_list=['_Test Company'])
+		make_deduction_salary_component(setup=True, company_list=['_Test Company'])
+
 	def setUp(self):
 		for dt in ["Salary Slip", "Salary Structure", "Salary Structure Assignment", "Timesheet"]:
 			frappe.db.sql("delete from `tab%s`" % dt)
@@ -49,7 +58,7 @@
 		self.assertEqual(timesheet.total_billable_amount, 0)
 
 	def test_salary_slip_from_timesheet(self):
-		emp = make_employee("test_employee_6@salary.com")
+		emp = make_employee("test_employee_6@salary.com", company="_Test Company")
 
 		salary_structure = make_salary_structure_for_timesheet(emp)
 		timesheet = make_timesheet(emp, simulate = True, billable=1)
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index 649eb45..ceeecb2 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -276,74 +276,3 @@
 		}
 	}
 }
-
-
-// For customizing print
-cur_frm.pformat.total = function(doc) { return ''; }
-cur_frm.pformat.discount_amount = function(doc) { return ''; }
-cur_frm.pformat.grand_total = function(doc) { return ''; }
-cur_frm.pformat.rounded_total = function(doc) { return ''; }
-cur_frm.pformat.in_words = function(doc) { return ''; }
-
-cur_frm.pformat.taxes= function(doc){
-	//function to make row of table
-	var make_row = function(title, val, bold, is_negative) {
-		var bstart = '<b>'; var bend = '</b>';
-		return '<tr><td style="width:50%;">' + (bold?bstart:'') + title + (bold?bend:'') + '</td>'
-			+ '<td style="width:50%;text-align:right;">' + (is_negative ? '- ' : '')
-		+ format_currency(val, doc.currency) + '</td></tr>';
-	}
-
-	function print_hide(fieldname) {
-		var doc_field = frappe.meta.get_docfield(doc.doctype, fieldname, doc.name);
-		return doc_field.print_hide;
-	}
-
-	out ='';
-	if (!doc.print_without_amount) {
-		var cl = doc.taxes || [];
-
-		// outer table
-		var out='<div><table class="noborder" style="width:100%"><tr><td style="width: 60%"></td><td>';
-
-		// main table
-
-		out +='<table class="noborder" style="width:100%">';
-
-		if(!print_hide('total')) {
-			out += make_row('Total', doc.total, 1);
-		}
-
-		// Discount Amount on net total
-		if(!print_hide('discount_amount') && doc.apply_discount_on == "Net Total" && doc.discount_amount)
-			out += make_row('Discount Amount', doc.discount_amount, 0, 1);
-
-		// add rows
-		if(cl.length){
-			for(var i=0;i<cl.length;i++) {
-				if(cl[i].tax_amount!=0 && !cl[i].included_in_print_rate)
-					out += make_row(cl[i].description, cl[i].tax_amount, 0);
-			}
-		}
-
-		// Discount Amount on grand total
-		if(!print_hide('discount_amount') && doc.apply_discount_on == "Grand Total" && doc.discount_amount)
-			out += make_row('Discount Amount', doc.discount_amount, 0, 1);
-
-		// grand total
-		if(!print_hide('grand_total'))
-			out += make_row('Grand Total', doc.grand_total, 1);
-
-		if(!print_hide('rounded_total'))
-			out += make_row('Rounded Total', doc.rounded_total, 1);
-
-		if(doc.in_words && !print_hide('in_words')) {
-			out +='</table></td></tr>';
-			out += '<tr><td colspan = "2">';
-			out += '<table><tr><td style="width:25%;"><b>In Words</b></td>';
-			out += '<td style="width:50%;">' + doc.in_words + '</td></tr>';
-		}
-		out += '</table></td></tr></table></div>';
-	}
-	return out;
-}
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 1c0abdf..6c2144d 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -577,7 +577,7 @@
 									var d = locals[cdt][cdn];
 									me.add_taxes_from_item_tax_template(d.item_tax_rate);
 									if (d.free_item_data) {
-										me.apply_product_discount(d.free_item_data);
+										me.apply_product_discount(d);
 									}
 								},
 								() => {
@@ -737,34 +737,34 @@
 				this.frm.trigger("item_code", cdt, cdn);
 			}
 			else {
-				var valid_serial_nos = [];
-
 				// Replacing all occurences of comma with carriage return
-				var serial_nos = item.serial_no.trim().replace(/,/g, '\n');
-
-				serial_nos = serial_nos.trim().split('\n');
-
-				// Trim each string and push unique string to new list
-				for (var x=0; x<=serial_nos.length - 1; x++) {
-					if (serial_nos[x].trim() != "" && valid_serial_nos.indexOf(serial_nos[x].trim()) == -1) {
-						valid_serial_nos.push(serial_nos[x].trim());
-					}
-				}
-
-				// Add the new list to the serial no. field in grid with each in new line
-				item.serial_no = valid_serial_nos.join('\n');
+				item.serial_no = item.serial_no.replace(/,/g, '\n');
 				item.conversion_factor = item.conversion_factor || 1;
-
 				refresh_field("serial_no", item.name, item.parentfield);
-				if(!doc.is_return && cint(user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
-					frappe.model.set_value(item.doctype, item.name,
-						"qty", valid_serial_nos.length / item.conversion_factor);
-					frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
+				if (!doc.is_return && cint(frappe.user_defaults.set_qty_in_transactions_based_on_serial_no_input)) {
+					setTimeout(() => {
+						me.update_qty(cdt, cdn);
+					}, 10000);
 				}
 			}
 		}
 	},
 
+	update_qty: function(cdt, cdn) {
+		var valid_serial_nos = [];
+		var serialnos = [];
+		var item = frappe.get_doc(cdt, cdn);
+		serialnos = item.serial_no.split("\n");
+		for (var i = 0; i < serialnos.length; i++) {
+			if (serialnos[i] != "") {
+				valid_serial_nos.push(serialnos[i]);
+			}
+		}
+		frappe.model.set_value(item.doctype, item.name,
+			"qty", valid_serial_nos.length / item.conversion_factor);
+		frappe.model.set_value(item.doctype, item.name, "stock_qty", valid_serial_nos.length);
+	},
+
 	validate: function() {
 		this.calculate_taxes_and_totals(false);
 	},
@@ -1173,6 +1173,11 @@
 				this.calculate_net_weight();
 			}
 
+			// for handling customization not to fetch price list rate
+			if(frappe.flags.dont_fetch_price_list_rate) {
+				return
+			}
+
 			if (!dont_fetch_price_list_rate &&
 				frappe.meta.has_field(doc.doctype, "price_list_currency")) {
 				this.apply_price_list(item, true);
@@ -1204,7 +1209,7 @@
 
 	calculate_stock_uom_rate: function(doc, cdt, cdn) {
 		let item = frappe.get_doc(cdt, cdn);
-		item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);	
+		item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);
 		refresh_field("stock_uom_rate", item.name, item.parentfield);
 	},
 	service_stop_date: function(frm, cdt, cdn) {
@@ -1533,7 +1538,10 @@
 					if(k=="price_list_rate") {
 						if(flt(v) != flt(d.price_list_rate)) price_list_rate_changed = true;
 					}
-					frappe.model.set_value(d.doctype, d.name, k, v);
+
+					if (k !== 'free_item_data') {
+						frappe.model.set_value(d.doctype, d.name, k, v);
+					}
 				}
 			}
 
@@ -1545,7 +1553,7 @@
 			}
 
 			if (d.free_item_data) {
-				me.apply_product_discount(d.free_item_data);
+				me.apply_product_discount(d);
 			}
 
 			if (d.apply_rule_on_other_items) {
@@ -1579,20 +1587,31 @@
 		}
 	},
 
-	apply_product_discount: function(free_item_data) {
-		const items = this.frm.doc.items.filter(d => (d.item_code == free_item_data.item_code
-			&& d.is_free_item)) || [];
+	apply_product_discount: function(args) {
+		const items = this.frm.doc.items.filter(d => (d.is_free_item)) || [];
 
-		if (!items.length) {
-			let row_to_modify = frappe.model.add_child(this.frm.doc,
-				this.frm.doc.doctype + ' Item', 'items');
+		const exist_items = items.map(row => (row.item_code, row.pricing_rules));
 
-			for (let key in free_item_data) {
-				row_to_modify[key] = free_item_data[key];
+		args.free_item_data.forEach(pr_row => {
+			let row_to_modify = {};
+			if (!items || !in_list(exist_items, (pr_row.item_code, pr_row.pricing_rules))) {
+
+				row_to_modify = frappe.model.add_child(this.frm.doc,
+					this.frm.doc.doctype + ' Item', 'items');
+
+			} else if(items) {
+				row_to_modify = items.filter(d => (d.item_code === pr_row.item_code
+					&& d.pricing_rules === pr_row.pricing_rules))[0];
 			}
-		} if (items && items.length && free_item_data) {
-			items[0].qty = free_item_data.qty
-		}
+
+			for (let key in pr_row) {
+				row_to_modify[key] = pr_row[key];
+			}
+		});
+
+		// free_item_data is a temporary variable
+		args.free_item_data = '';
+		refresh_field('items');
 	},
 
 	apply_price_list: function(item, reset_plc_conversion) {
diff --git a/erpnext/public/js/help_links.js b/erpnext/public/js/help_links.js
index 472c537..e789923 100644
--- a/erpnext/public/js/help_links.js
+++ b/erpnext/public/js/help_links.js
@@ -1,466 +1,1051 @@
-frappe.provide('frappe.help.help_links');
+frappe.provide("frappe.help.help_links");
 
-const docsUrl = 'https://erpnext.com/docs/';
+const docsUrl = "https://erpnext.com/docs/";
 
-frappe.help.help_links['rename tool'] = [
-	{ label: 'Bulk Rename', url: docsUrl + 'user/manual/en/setting-up/data/bulk-rename' },
-]
+frappe.help.help_links["Form/Rename Tool"] = [
+	{
+		label: "Bulk Rename",
+		url: docsUrl + "user/manual/en/setting-up/data/bulk-rename",
+	},
+];
 
 //Setup
 
-frappe.help.help_links['user'] = [
-	{ label: 'New User', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/adding-users' },
-	{ label: 'Rename User', url: docsUrl + 'user/manual/en/setting-up/articles/rename-user' },
-]
+frappe.help.help_links["List/User"] = [
+	{
+		label: "New User",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/users-and-permissions/adding-users",
+	},
+	{
+		label: "Rename User",
+		url: docsUrl + "user/manual/en/setting-up/articles/rename-user",
+	},
+];
 
-frappe.help.help_links['permission-manager'] = [
-	{ label: 'Role Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/role-based-permissions' },
-	{ label: 'Managing Perm Level in Permissions Manager', url: docsUrl + 'user/manual/en/setting-up/articles/managing-perm-level' },
-	{ label: 'User Permissions', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/user-permissions' },
-	{ label: 'Sharing', url: docsUrl + 'user/manual/en/setting-up/users-and-permissions/sharing' },
-	{ label: 'Password', url: docsUrl + 'user/manual/en/setting-up/articles/change-password' },
-]
+frappe.help.help_links["permission-manager"] = [
+	{
+		label: "Role Permissions Manager",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/users-and-permissions/role-based-permissions",
+	},
+	{
+		label: "Managing Perm Level in Permissions Manager",
+		url: docsUrl + "user/manual/en/setting-up/articles/managing-perm-level",
+	},
+	{
+		label: "User Permissions",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/users-and-permissions/user-permissions",
+	},
+	{
+		label: "Sharing",
+		url:
+			docsUrl + "user/manual/en/setting-up/users-and-permissions/sharing",
+	},
+	{
+		label: "Password",
+		url: docsUrl + "user/manual/en/setting-up/articles/change-password",
+	},
+];
 
-frappe.help.help_links['system-settings'] = [
-	{ label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/system-settings' },
-]
+frappe.help.help_links["Form/System Settings"] = [
+	{
+		label: "Naming Series",
+		url: docsUrl + "user/manual/en/setting-up/settings/system-settings",
+	},
+];
 
-frappe.help.help_links['data-import-tool'] = [
-	{ label: 'Importing and Exporting Data', url: docsUrl + 'user/manual/en/setting-up/data/data-import-tool' },
-	{ label: 'Overwriting Data from Data Import Tool', url: docsUrl + 'user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool' },
-]
+frappe.help.help_links["data-import-tool"] = [
+	{
+		label: "Importing and Exporting Data",
+		url: docsUrl + "user/manual/en/setting-up/data/data-import-tool",
+	},
+	{
+		label: "Overwriting Data from Data Import Tool",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/articles/overwriting-data-from-data-import-tool",
+	},
+];
 
-frappe.help.help_links['naming-series'] = [
-	{ label: 'Naming Series', url: docsUrl + 'user/manual/en/setting-up/settings/naming-series' },
-	{ label: 'Setting the Current Value for Naming Series', url: docsUrl + 'user/manual/en/setting-up/articles/naming-series-current-value' },
-]
+frappe.help.help_links["module_setup"] = [
+	{
+		label: "Role Permissions Manager",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/users-and-permissions/role-based-permissions",
+	},
+];
 
-frappe.help.help_links['global-defaults'] = [
-	{ label: 'Global Settings', url: docsUrl + 'user/manual/en/setting-up/settings/global-defaults' },
-]
+frappe.help.help_links["Form/Naming Series"] = [
+	{
+		label: "Naming Series",
+		url: docsUrl + "user/manual/en/setting-up/settings/naming-series",
+	},
+	{
+		label: "Setting the Current Value for Naming Series",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/articles/naming-series-current-value",
+	},
+];
 
-frappe.help.help_links['email-digest'] = [
-	{ label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' },
-]
+frappe.help.help_links["Form/Global Defaults"] = [
+	{
+		label: "Global Settings",
+		url: docsUrl + "user/manual/en/setting-up/settings/global-defaults",
+	},
+];
 
-frappe.help.help_links['print-heading'] = [
-	{ label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' },
-]
+frappe.help.help_links["Form/Email Digest"] = [
+	{
+		label: "Email Digest",
+		url: docsUrl + "user/manual/en/setting-up/email/email-digest",
+	},
+];
 
-frappe.help.help_links['letter-head'] = [
-	{ label: 'Letter Head', url: docsUrl + 'user/manual/en/setting-up/print/letter-head' },
-]
+frappe.help.help_links["List/Print Heading"] = [
+	{
+		label: "Print Heading",
+		url: docsUrl + "user/manual/en/setting-up/print/print-headings",
+	},
+];
 
-frappe.help.help_links['address-template'] = [
-	{ label: 'Address Template', url: docsUrl + 'user/manual/en/setting-up/print/address-template' },
-]
+frappe.help.help_links["List/Letter Head"] = [
+	{
+		label: "Letter Head",
+		url: docsUrl + "user/manual/en/setting-up/print/letter-head",
+	},
+];
 
-frappe.help.help_links['terms-and-conditions'] = [
-	{ label: 'Terms and Conditions', url: docsUrl + 'user/manual/en/setting-up/print/terms-and-conditions' },
-]
+frappe.help.help_links["List/Address Template"] = [
+	{
+		label: "Address Template",
+		url: docsUrl + "user/manual/en/setting-up/print/address-template",
+	},
+];
 
-frappe.help.help_links['cheque-print-template'] = [
-	{ label: 'Cheque Print Template', url: docsUrl + 'user/manual/en/setting-up/print/cheque-print-template' },
-]
+frappe.help.help_links["List/Terms and Conditions"] = [
+	{
+		label: "Terms and Conditions",
+		url: docsUrl + "user/manual/en/setting-up/print/terms-and-conditions",
+	},
+];
 
-frappe.help.help_links['email-account'] = [
-	{ label: 'Email Account', url: docsUrl + 'user/manual/en/setting-up/email/email-account' },
-]
+frappe.help.help_links["List/Cheque Print Template"] = [
+	{
+		label: "Cheque Print Template",
+		url: docsUrl + "user/manual/en/setting-up/print/cheque-print-template",
+	},
+];
 
-frappe.help.help_links['notification'] = [
-	{ label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' },
-]
+frappe.help.help_links["List/Email Account"] = [
+	{
+		label: "Email Account",
+		url: docsUrl + "user/manual/en/setting-up/email/email-account",
+	},
+];
 
-frappe.help.help_links['notification'] = [
-	{ label: 'Notification', url: docsUrl + 'user/manual/en/setting-up/email/notifications' },
-]
+frappe.help.help_links["List/Notification"] = [
+	{
+		label: "Notification",
+		url: docsUrl + "user/manual/en/setting-up/email/notifications",
+	},
+];
 
-frappe.help.help_links['email-digest'] = [
-	{ label: 'Email Digest', url: docsUrl + 'user/manual/en/setting-up/email/email-digest' },
-]
+frappe.help.help_links["Form/Notification"] = [
+	{
+		label: "Notification",
+		url: docsUrl + "user/manual/en/setting-up/email/notifications",
+	},
+];
 
-frappe.help.help_links['auto-email-report'] = [
-	{ label: 'Auto Email Reports', url: docsUrl + 'user/manual/en/setting-up/email/email-reports' },
-]
+frappe.help.help_links["List/Email Digest"] = [
+	{
+		label: "Email Digest",
+		url: docsUrl + "user/manual/en/setting-up/email/email-digest",
+	},
+];
 
-frappe.help.help_links['print-settings'] = [
-	{ label: 'Print Settings', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' },
-]
+frappe.help.help_links["List/Auto Email Report"] = [
+	{
+		label: "Auto Email Reports",
+		url: docsUrl + "user/manual/en/setting-up/email/email-reports",
+	},
+];
 
-frappe.help.help_links['print-format-builder'] = [
-	{ label: 'Print Format Builder', url: docsUrl + 'user/manual/en/setting-up/print/print-settings' },
-]
+frappe.help.help_links["Form/Print Settings"] = [
+	{
+		label: "Print Settings",
+		url: docsUrl + "user/manual/en/setting-up/print/print-settings",
+	},
+];
 
-frappe.help.help_links['print-heading'] = [
-	{ label: 'Print Heading', url: docsUrl + 'user/manual/en/setting-up/print/print-headings' },
-]
+frappe.help.help_links["print-format-builder"] = [
+	{
+		label: "Print Format Builder",
+		url: docsUrl + "user/manual/en/setting-up/print/print-settings",
+	},
+];
+
+frappe.help.help_links["List/Print Heading"] = [
+	{
+		label: "Print Heading",
+		url: docsUrl + "user/manual/en/setting-up/print/print-headings",
+	},
+];
 
 //setup-integrations
 
-frappe.help.help_links['paypal-settings'] = [
-	{ label: 'PayPal Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/paypal-integration' },
-]
+frappe.help.help_links["Form/PayPal Settings"] = [
+	{
+		label: "PayPal Settings",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/integrations/paypal-integration",
+	},
+];
 
-frappe.help.help_links['razorpay-settings'] = [
-	{ label: 'Razorpay Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/razorpay-integration' },
-]
+frappe.help.help_links["Form/Razorpay Settings"] = [
+	{
+		label: "Razorpay Settings",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/integrations/razorpay-integration",
+	},
+];
 
-frappe.help.help_links['dropbox-settings'] = [
-	{ label: 'Dropbox Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/dropbox-backup' },
-]
+frappe.help.help_links["Form/Dropbox Settings"] = [
+	{
+		label: "Dropbox Settings",
+		url: docsUrl + "user/manual/en/setting-up/integrations/dropbox-backup",
+	},
+];
 
-frappe.help.help_links['ldap-settings'] = [
-	{ label: 'LDAP Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/ldap-integration' },
-]
+frappe.help.help_links["Form/LDAP Settings"] = [
+	{
+		label: "LDAP Settings",
+		url:
+			docsUrl + "user/manual/en/setting-up/integrations/ldap-integration",
+	},
+];
 
-frappe.help.help_links['stripe-settings'] = [
-	{ label: 'Stripe Settings', url: docsUrl + 'user/manual/en/setting-up/integrations/stripe-integration' },
-]
+frappe.help.help_links["Form/Stripe Settings"] = [
+	{
+		label: "Stripe Settings",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/integrations/stripe-integration",
+	},
+];
 
 //Sales
 
-frappe.help.help_links['quotation'] = [
-	{ label: 'Quotation', url: docsUrl + 'user/manual/en/selling/quotation' },
-	{ label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' },
-	{ label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' },
-	{ label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' },
-]
+frappe.help.help_links["Form/Quotation"] = [
+	{ label: "Quotation", url: docsUrl + "user/manual/en/selling/quotation" },
+	{
+		label: "Applying Discount",
+		url: docsUrl + "user/manual/en/selling/articles/applying-discount",
+	},
+	{
+		label: "Sales Person",
+		url:
+			docsUrl +
+			"user/manual/en/selling/articles/sales-persons-in-the-sales-transactions",
+	},
+	{
+		label: "Applying Margin",
+		url: docsUrl + "user/manual/en/selling/articles/adding-margin",
+	},
+];
 
-frappe.help.help_links['customer'] = [
-	{ label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' },
-	{ label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' },
-]
+frappe.help.help_links["List/Customer"] = [
+	{ label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" },
+	{
+		label: "Credit Limit",
+		url: docsUrl + "user/manual/en/accounts/credit-limit",
+	},
+];
 
-frappe.help.help_links['customer'] = [
-	{ label: 'Customer', url: docsUrl + 'user/manual/en/CRM/customer' },
-	{ label: 'Credit Limit', url: docsUrl + 'user/manual/en/accounts/credit-limit' },
-]
+frappe.help.help_links["Form/Customer"] = [
+	{ label: "Customer", url: docsUrl + "user/manual/en/CRM/customer" },
+	{
+		label: "Credit Limit",
+		url: docsUrl + "user/manual/en/accounts/credit-limit",
+	},
+];
 
-frappe.help.help_links['sales-taxes-and-charges-template'] = [
-	{ label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
-]
+frappe.help.help_links["List/Sales Taxes and Charges Template"] = [
+	{
+		label: "Setting Up Taxes",
+		url: docsUrl + "user/manual/en/setting-up/setting-up-taxes",
+	},
+];
 
-frappe.help.help_links['sales-taxes-and-charges-template'] = [
-	{ label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
-]
+frappe.help.help_links["Form/Sales Taxes and Charges Template"] = [
+	{
+		label: "Setting Up Taxes",
+		url: docsUrl + "user/manual/en/setting-up/setting-up-taxes",
+	},
+];
 
-frappe.help.help_links['sales-order'] = [
-	{ label: 'Sales Order', url: docsUrl + 'user/manual/en/selling/sales-order' },
-	{ label: 'Recurring Sales Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-	{ label: 'Applying Discount', url: docsUrl + 'user/manual/en/selling/articles/applying-discount' },
-	{ label: 'Drop Shipping', url: docsUrl + 'user/manual/en/selling/articles/drop-shipping' },
-	{ label: 'Sales Person', url: docsUrl + 'user/manual/en/selling/articles/sales-persons-in-the-sales-transactions' },
-	{ label: 'Close Sales Order', url: docsUrl + 'user/manual/en/selling/articles/close-sales-order' },
-	{ label: 'Applying Margin', url: docsUrl + 'user/manual/en/selling/articles/adding-margin' },
-]
+frappe.help.help_links["List/Sales Order"] = [
+	{
+		label: "Sales Order",
+		url: docsUrl + "user/manual/en/selling/sales-order",
+	},
+	{
+		label: "Recurring Sales Order",
+		url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+	},
+	{
+		label: "Applying Discount",
+		url: docsUrl + "user/manual/en/selling/articles/applying-discount",
+	},
+];
 
-frappe.help.help_links['product-bundle'] = [
-	{ label: 'Product Bundle', url: docsUrl + 'user/manual/en/selling/setup/product-bundle' },
-]
+frappe.help.help_links["Form/Sales Order"] = [
+	{
+		label: "Sales Order",
+		url: docsUrl + "user/manual/en/selling/sales-order",
+	},
+	{
+		label: "Recurring Sales Order",
+		url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+	},
+	{
+		label: "Applying Discount",
+		url: docsUrl + "user/manual/en/selling/articles/applying-discount",
+	},
+	{
+		label: "Drop Shipping",
+		url: docsUrl + "user/manual/en/selling/articles/drop-shipping",
+	},
+	{
+		label: "Sales Person",
+		url:
+			docsUrl +
+			"user/manual/en/selling/articles/sales-persons-in-the-sales-transactions",
+	},
+	{
+		label: "Close Sales Order",
+		url: docsUrl + "user/manual/en/selling/articles/close-sales-order",
+	},
+	{
+		label: "Applying Margin",
+		url: docsUrl + "user/manual/en/selling/articles/adding-margin",
+	},
+];
 
-frappe.help.help_links['selling-settings'] = [
-	{ label: 'Selling Settings', url: docsUrl + 'user/manual/en/selling/setup/selling-settings' },
-]
+frappe.help.help_links["Form/Product Bundle"] = [
+	{
+		label: "Product Bundle",
+		url: docsUrl + "user/manual/en/selling/setup/product-bundle",
+	},
+];
+
+frappe.help.help_links["Form/Selling Settings"] = [
+	{
+		label: "Selling Settings",
+		url: docsUrl + "user/manual/en/selling/setup/selling-settings",
+	},
+];
 
 //Buying
 
-frappe.help.help_links['supplier'] = [
-	{ label: 'Supplier', url: docsUrl + 'user/manual/en/buying/supplier' },
-]
+frappe.help.help_links["List/Supplier"] = [
+	{ label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" },
+];
 
-frappe.help.help_links['request-for-quotation'] = [
-	{ label: 'Request for Quotation', url: docsUrl + 'user/manual/en/buying/request-for-quotation' },
-	{ label: 'RFQ Video', url: docsUrl + 'user/videos/learn/request-for-quotation.html' },
-]
+frappe.help.help_links["Form/Supplier"] = [
+	{ label: "Supplier", url: docsUrl + "user/manual/en/buying/supplier" },
+];
 
-frappe.help.help_links['supplier-quotation'] = [
-	{ label: 'Supplier Quotation', url: docsUrl + 'user/manual/en/buying/supplier-quotation' },
-]
+frappe.help.help_links["Form/Request for Quotation"] = [
+	{
+		label: "Request for Quotation",
+		url: docsUrl + "user/manual/en/buying/request-for-quotation",
+	},
+	{
+		label: "RFQ Video",
+		url: docsUrl + "user/videos/learn/request-for-quotation.html",
+	},
+];
 
-frappe.help.help_links['buying-settings'] = [
-	{ label: 'Buying Settings', url: docsUrl + 'user/manual/en/buying/setup/buying-settings' },
-]
+frappe.help.help_links["Form/Supplier Quotation"] = [
+	{
+		label: "Supplier Quotation",
+		url: docsUrl + "user/manual/en/buying/supplier-quotation",
+	},
+];
 
-frappe.help.help_links['purchase-order'] = [
-	{ label: 'Purchase Order', url: docsUrl + 'user/manual/en/buying/purchase-order' },
-	{ label: 'Item UoM', url: docsUrl + 'user/manual/en/buying/articles/purchasing-in-different-unit' },
-	{ label: 'Supplier Item Code', url: docsUrl + 'user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item' },
-	{ label: 'Recurring Purchase Order', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-	{ label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
-]
+frappe.help.help_links["Form/Buying Settings"] = [
+	{
+		label: "Buying Settings",
+		url: docsUrl + "user/manual/en/buying/setup/buying-settings",
+	},
+];
 
-frappe.help.help_links['purchase-taxes-and-charges-template'] = [
-	{ label: 'Setting Up Taxes', url: docsUrl + 'user/manual/en/setting-up/setting-up-taxes' },
-]
+frappe.help.help_links["List/Purchase Order"] = [
+	{
+		label: "Purchase Order",
+		url: docsUrl + "user/manual/en/buying/purchase-order",
+	},
+	{
+		label: "Recurring Purchase Order",
+		url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+	},
+];
 
-frappe.help.help_links['pos-profile'] = [
-	{ label: 'POS Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' },
-]
+frappe.help.help_links["Form/Purchase Order"] = [
+	{
+		label: "Purchase Order",
+		url: docsUrl + "user/manual/en/buying/purchase-order",
+	},
+	{
+		label: "Item UoM",
+		url:
+			docsUrl +
+			"user/manual/en/buying/articles/purchasing-in-different-unit",
+	},
+	{
+		label: "Supplier Item Code",
+		url:
+			docsUrl +
+			"user/manual/en/buying/articles/maintaining-suppliers-part-no-in-item",
+	},
+	{
+		label: "Recurring Purchase Order",
+		url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+	},
+	{
+		label: "Subcontracting",
+		url: docsUrl + "user/manual/en/manufacturing/subcontracting",
+	},
+];
 
-frappe.help.help_links['price-list'] = [
-	{ label: 'Price List', url: docsUrl + 'user/manual/en/setting-up/price-lists' },
-]
+frappe.help.help_links["List/Purchase Taxes and Charges Template"] = [
+	{
+		label: "Setting Up Taxes",
+		url: docsUrl + "user/manual/en/setting-up/setting-up-taxes",
+	},
+];
 
-frappe.help.help_links['authorization-rule'] = [
-	{ label: 'Authorization Rule', url: docsUrl + 'user/manual/en/setting-up/authorization-rule' },
-]
+frappe.help.help_links["List/POS Profile"] = [
+	{
+		label: "POS Profile",
+		url: docsUrl + "user/manual/en/setting-up/pos-setting",
+	},
+];
 
-frappe.help.help_links['sms-settings'] = [
-	{ label: 'SMS Settings', url: docsUrl + 'user/manual/en/setting-up/sms-setting' },
-]
+frappe.help.help_links["List/Price List"] = [
+	{
+		label: "Price List",
+		url: docsUrl + "user/manual/en/setting-up/price-lists",
+	},
+];
 
-frappe.help.help_links['stock-reconciliation'] = [
-	{ label: 'Stock Reconciliation', url: docsUrl + 'user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item' },
-]
+frappe.help.help_links["List/Authorization Rule"] = [
+	{
+		label: "Authorization Rule",
+		url: docsUrl + "user/manual/en/setting-up/authorization-rule",
+	},
+];
 
-frappe.help.help_links['territory/view/tree'] = [
-	{ label: 'Territory', url: docsUrl + 'user/manual/en/setting-up/territory' },
-]
+frappe.help.help_links["Form/SMS Settings"] = [
+	{
+		label: "SMS Settings",
+		url: docsUrl + "user/manual/en/setting-up/sms-setting",
+	},
+];
 
-frappe.help.help_links['dropbox-backup'] = [
-	{ label: 'Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/third-party-backups' },
-	{ label: 'Setting Up Dropbox Backup', url: docsUrl + 'user/manual/en/setting-up/articles/setting-up-dropbox-backups' },
-]
+frappe.help.help_links["List/Stock Reconciliation"] = [
+	{
+		label: "Stock Reconciliation",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/stock-reconciliation-for-non-serialized-item",
+	},
+];
 
-frappe.help.help_links['workflow'] = [
-	{ label: 'Workflow', url: docsUrl + 'user/manual/en/setting-up/workflows' },
-]
+frappe.help.help_links["Tree/Territory"] = [
+	{
+		label: "Territory",
+		url: docsUrl + "user/manual/en/setting-up/territory",
+	},
+];
 
-frappe.help.help_links['company'] = [
-	{ label: 'Company', url: docsUrl + 'user/manual/en/setting-up/company-setup' },
-	{ label: 'Managing Multiple Companies', url: docsUrl + 'user/manual/en/setting-up/articles/managing-multiple-companies' },
-	{ label: 'Delete All Related Transactions for a Company', url: docsUrl + 'user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions' },
-]
+frappe.help.help_links["Form/Dropbox Backup"] = [
+	{
+		label: "Dropbox Backup",
+		url: docsUrl + "user/manual/en/setting-up/third-party-backups",
+	},
+	{
+		label: "Setting Up Dropbox Backup",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/articles/setting-up-dropbox-backups",
+	},
+];
+
+frappe.help.help_links["List/Workflow"] = [
+	{ label: "Workflow", url: docsUrl + "user/manual/en/setting-up/workflows" },
+];
+
+frappe.help.help_links["List/Company"] = [
+	{
+		label: "Company",
+		url: docsUrl + "user/manual/en/setting-up/company-setup",
+	},
+	{
+		label: "Managing Multiple Companies",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/articles/managing-multiple-companies",
+	},
+	{
+		label: "Delete All Related Transactions for a Company",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/articles/delete-a-company-and-all-related-transactions",
+	},
+];
 
 //Accounts
 
-frappe.help.help_links['accounts'] = [
-	{ label: 'Introduction to Accounts', url: docsUrl + 'user/manual/en/accounts/' },
-	{ label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts.html' },
-	{ label: 'Multi Currency Accounting', url: docsUrl + 'user/manual/en/accounts/multi-currency-accounting' },
-]
+frappe.help.help_links["modules/Accounts"] = [
+	{
+		label: "Introduction to Accounts",
+		url: docsUrl + "user/manual/en/accounts/",
+	},
+	{
+		label: "Chart of Accounts",
+		url: docsUrl + "user/manual/en/accounts/chart-of-accounts.html",
+	},
+	{
+		label: "Multi Currency Accounting",
+		url: docsUrl + "user/manual/en/accounts/multi-currency-accounting",
+	},
+];
 
-frappe.help.help_links['account/view/tree'] = [
-	{ label: 'Chart of Accounts', url: docsUrl + 'user/manual/en/accounts/chart-of-accounts' },
-	{ label: 'Managing Tree Mastes', url: docsUrl + 'user/manual/en/setting-up/articles/managing-tree-structure-masters' },
-]
+frappe.help.help_links["Tree/Account"] = [
+	{
+		label: "Chart of Accounts",
+		url: docsUrl + "user/manual/en/accounts/chart-of-accounts",
+	},
+	{
+		label: "Managing Tree Mastes",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/articles/managing-tree-structure-masters",
+	},
+];
 
-frappe.help.help_links['sales-invoice'] = [
-	{ label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' },
-	{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
-	{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
-	{ label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-]
+frappe.help.help_links["Form/Sales Invoice"] = [
+	{
+		label: "Sales Invoice",
+		url: docsUrl + "user/manual/en/accounts/sales-invoice",
+	},
+	{
+		label: "Accounts Opening Balance",
+		url: docsUrl + "user/manual/en/accounts/opening-accounts",
+	},
+	{
+		label: "Sales Return",
+		url: docsUrl + "user/manual/en/stock/sales-return",
+	},
+	{
+		label: "Recurring Sales Invoice",
+		url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+	},
+];
 
-frappe.help.help_links['sales-invoice'] = [
-	{ label: 'Sales Invoice', url: docsUrl + 'user/manual/en/accounts/sales-invoice' },
-	{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
-	{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
-	{ label: 'Recurring Sales Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-]
+frappe.help.help_links["List/Sales Invoice"] = [
+	{
+		label: "Sales Invoice",
+		url: docsUrl + "user/manual/en/accounts/sales-invoice",
+	},
+	{
+		label: "Accounts Opening Balance",
+		url: docsUrl + "user/manual/en/accounts/opening-accounts",
+	},
+	{
+		label: "Sales Return",
+		url: docsUrl + "user/manual/en/stock/sales-return",
+	},
+	{
+		label: "Recurring Sales Invoice",
+		url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+	},
+];
 
-frappe.help.help_links['pos'] = [
-	{ label: 'Point of Sale Invoice', url: docsUrl + 'user/manual/en/accounts/point-of-sale-pos-invoice' },
-]
+frappe.help.help_links["pos"] = [
+	{
+		label: "Point of Sale Invoice",
+		url: docsUrl + "user/manual/en/accounts/point-of-sale-pos-invoice",
+	},
+];
 
-frappe.help.help_links['pos-profile'] = [
-	{ label: 'Point of Sale Profile', url: docsUrl + 'user/manual/en/setting-up/pos-setting' },
-]
+frappe.help.help_links["List/POS Profile"] = [
+	{
+		label: "Point of Sale Profile",
+		url: docsUrl + "user/manual/en/setting-up/pos-setting",
+	},
+];
 
-frappe.help.help_links['purchase-invoice'] = [
-	{ label: 'Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/purchase-invoice' },
-	{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
-	{ label: 'Recurring Purchase Invoice', url: docsUrl + 'user/manual/en/accounts/recurring-orders-and-invoices' },
-]
+frappe.help.help_links["List/Purchase Invoice"] = [
+	{
+		label: "Purchase Invoice",
+		url: docsUrl + "user/manual/en/accounts/purchase-invoice",
+	},
+	{
+		label: "Accounts Opening Balance",
+		url: docsUrl + "user/manual/en/accounts/opening-accounts",
+	},
+	{
+		label: "Recurring Purchase Invoice",
+		url: docsUrl + "user/manual/en/accounts/recurring-orders-and-invoices",
+	},
+];
 
-frappe.help.help_links['journal-entry'] = [
-	{ label: 'Journal Entry', url: docsUrl + 'user/manual/en/accounts/journal-entry' },
-	{ label: 'Advance Payment Entry', url: docsUrl + 'user/manual/en/accounts/advance-payment-entry' },
-	{ label: 'Accounts Opening Balance', url: docsUrl + 'user/manual/en/accounts/opening-accounts' },
-]
+frappe.help.help_links["List/Journal Entry"] = [
+	{
+		label: "Journal Entry",
+		url: docsUrl + "user/manual/en/accounts/journal-entry",
+	},
+	{
+		label: "Advance Payment Entry",
+		url: docsUrl + "user/manual/en/accounts/advance-payment-entry",
+	},
+	{
+		label: "Accounts Opening Balance",
+		url: docsUrl + "user/manual/en/accounts/opening-accounts",
+	},
+];
 
-frappe.help.help_links['payment-entry'] = [
-	{ label: 'Payment Entry', url: docsUrl + 'user/manual/en/accounts/payment-entry' },
-]
+frappe.help.help_links["List/Payment Entry"] = [
+	{
+		label: "Payment Entry",
+		url: docsUrl + "user/manual/en/accounts/payment-entry",
+	},
+];
 
-frappe.help.help_links['payment-request'] = [
-	{ label: 'Payment Request', url: docsUrl + 'user/manual/en/accounts/payment-request' },
-]
+frappe.help.help_links["List/Payment Request"] = [
+	{
+		label: "Payment Request",
+		url: docsUrl + "user/manual/en/accounts/payment-request",
+	},
+];
 
-frappe.help.help_links['asset'] = [
-	{ label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
-]
+frappe.help.help_links["List/Asset"] = [
+	{
+		label: "Managing Fixed Assets",
+		url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+	},
+];
 
-frappe.help.help_links['asset-category'] = [
-	{ label: 'Asset Category', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
-]
+frappe.help.help_links["List/Asset Category"] = [
+	{
+		label: "Asset Category",
+		url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+	},
+];
 
-frappe.help.help_links['cost-center/view/tree'] = [
-	{ label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' },
-]
+frappe.help.help_links["Tree/Cost Center"] = [
+	{ label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
+];
 
-frappe.help.help_links['item'] = [
-	{ label: 'Item', url: docsUrl + 'user/manual/en/stock/item' },
-	{ label: 'Item Price', url: docsUrl + 'user/manual/en/stock/item/item-price' },
-	{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
-	{ label: 'Item Wise Taxation', url: docsUrl + 'user/manual/en/accounts/item-wise-taxation' },
-	{ label: 'Managing Fixed Assets', url: docsUrl + 'user/manual/en/accounts/managing-fixed-assets' },
-	{ label: 'Item Codification', url: docsUrl + 'user/manual/en/stock/item/item-codification' },
-	{ label: 'Item Variants', url: docsUrl + 'user/manual/en/stock/item/item-variants' },
-	{ label: 'Item Valuation', url: docsUrl + 'user/manual/en/stock/item/item-valuation-fifo-and-moving-average' },
-]
+frappe.help.help_links["List/Item"] = [
+	{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
+	{
+		label: "Item Price",
+		url: docsUrl + "user/manual/en/stock/item/item-price",
+	},
+	{
+		label: "Barcode",
+		url:
+			docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+	},
+	{
+		label: "Item Wise Taxation",
+		url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+	},
+	{
+		label: "Managing Fixed Assets",
+		url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+	},
+	{
+		label: "Item Codification",
+		url: docsUrl + "user/manual/en/stock/item/item-codification",
+	},
+	{
+		label: "Item Variants",
+		url: docsUrl + "user/manual/en/stock/item/item-variants",
+	},
+	{
+		label: "Item Valuation",
+		url:
+			docsUrl +
+			"user/manual/en/stock/item/item-valuation-fifo-and-moving-average",
+	},
+];
 
-frappe.help.help_links['purchase-receipt'] = [
-	{ label: 'Purchase Receipt', url: docsUrl + 'user/manual/en/stock/purchase-receipt' },
-	{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
-]
+frappe.help.help_links["Form/Item"] = [
+	{ label: "Item", url: docsUrl + "user/manual/en/stock/item" },
+	{
+		label: "Item Price",
+		url: docsUrl + "user/manual/en/stock/item/item-price",
+	},
+	{
+		label: "Barcode",
+		url:
+			docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+	},
+	{
+		label: "Item Wise Taxation",
+		url: docsUrl + "user/manual/en/accounts/item-wise-taxation",
+	},
+	{
+		label: "Managing Fixed Assets",
+		url: docsUrl + "user/manual/en/accounts/managing-fixed-assets",
+	},
+	{
+		label: "Item Codification",
+		url: docsUrl + "user/manual/en/stock/item/item-codification",
+	},
+	{
+		label: "Item Variants",
+		url: docsUrl + "user/manual/en/stock/item/item-variants",
+	},
+	{
+		label: "Item Valuation",
+		url:
+			docsUrl +
+			"user/manual/en/stock/item/item-valuation-fifo-and-moving-average",
+	},
+];
 
-frappe.help.help_links['delivery-note'] = [
-	{ label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' },
-	{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
-	{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
-]
+frappe.help.help_links["List/Purchase Receipt"] = [
+	{
+		label: "Purchase Receipt",
+		url: docsUrl + "user/manual/en/stock/purchase-receipt",
+	},
+	{
+		label: "Barcode",
+		url:
+			docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+	},
+];
 
-frappe.help.help_links['delivery-note'] = [
-	{ label: 'Delivery Note', url: docsUrl + 'user/manual/en/stock/delivery-note' },
-	{ label: 'Sales Return', url: docsUrl + 'user/manual/en/stock/sales-return' },
-	{ label: 'Barcode', url: docsUrl + 'user/manual/en/stock/articles/track-items-using-barcode' },
-	{ label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
-]
+frappe.help.help_links["List/Delivery Note"] = [
+	{
+		label: "Delivery Note",
+		url: docsUrl + "user/manual/en/stock/delivery-note",
+	},
+	{
+		label: "Barcode",
+		url:
+			docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+	},
+	{
+		label: "Sales Return",
+		url: docsUrl + "user/manual/en/stock/sales-return",
+	},
+];
 
-frappe.help.help_links['installation-note'] = [
-	{ label: 'Installation Note', url: docsUrl + 'user/manual/en/stock/installation-note' },
-]
+frappe.help.help_links["Form/Delivery Note"] = [
+	{
+		label: "Delivery Note",
+		url: docsUrl + "user/manual/en/stock/delivery-note",
+	},
+	{
+		label: "Sales Return",
+		url: docsUrl + "user/manual/en/stock/sales-return",
+	},
+	{
+		label: "Barcode",
+		url:
+			docsUrl + "user/manual/en/stock/articles/track-items-using-barcode",
+	},
+	{
+		label: "Subcontracting",
+		url: docsUrl + "user/manual/en/manufacturing/subcontracting",
+	},
+];
 
+frappe.help.help_links["List/Installation Note"] = [
+	{
+		label: "Installation Note",
+		url: docsUrl + "user/manual/en/stock/installation-note",
+	},
+];
 
+frappe.help.help_links["Tree"] = [
+	{
+		label: "Managing Tree Structure Masters",
+		url:
+			docsUrl +
+			"user/manual/en/setting-up/articles/managing-tree-structure-masters",
+	},
+];
 
-frappe.help.help_links['budget'] = [
-	{ label: 'Budgeting', url: docsUrl + 'user/manual/en/accounts/budgeting' },
-]
+frappe.help.help_links["List/Budget"] = [
+	{ label: "Budgeting", url: docsUrl + "user/manual/en/accounts/budgeting" },
+];
 
 //Stock
 
-frappe.help.help_links['material-request'] = [
-	{ label: 'Material Request', url: docsUrl + 'user/manual/en/stock/material-request' },
-	{ label: 'Auto-creation of Material Request', url: docsUrl + 'user/manual/en/stock/articles/auto-creation-of-material-request' },
-]
+frappe.help.help_links["List/Material Request"] = [
+	{
+		label: "Material Request",
+		url: docsUrl + "user/manual/en/stock/material-request",
+	},
+	{
+		label: "Auto-creation of Material Request",
+		url:
+			docsUrl +
+			"user/manual/en/stock/articles/auto-creation-of-material-request",
+	},
+];
 
-frappe.help.help_links['stock-entry'] = [
-	{ label: 'Stock Entry', url: docsUrl + 'user/manual/en/stock/stock-entry' },
-	{ label: 'Stock Entry Types', url: docsUrl + 'user/manual/en/stock/articles/stock-entry-purpose' },
-	{ label: 'Repack Entry', url: docsUrl + 'user/manual/en/stock/articles/repack-entry' },
-	{ label: 'Opening Stock', url: docsUrl + 'user/manual/en/stock/opening-stock' },
-	{ label: 'Subcontracting', url: docsUrl + 'user/manual/en/manufacturing/subcontracting' },
-]
+frappe.help.help_links["Form/Material Request"] = [
+	{
+		label: "Material Request",
+		url: docsUrl + "user/manual/en/stock/material-request",
+	},
+	{
+		label: "Auto-creation of Material Request",
+		url:
+			docsUrl +
+			"user/manual/en/stock/articles/auto-creation-of-material-request",
+	},
+];
 
-frappe.help.help_links['warehouse/view/tree'] = [
-	{ label: 'Warehouse', url: docsUrl + 'user/manual/en/stock/warehouse' },
-]
+frappe.help.help_links["Form/Stock Entry"] = [
+	{ label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" },
+	{
+		label: "Stock Entry Types",
+		url: docsUrl + "user/manual/en/stock/articles/stock-entry-purpose",
+	},
+	{
+		label: "Repack Entry",
+		url: docsUrl + "user/manual/en/stock/articles/repack-entry",
+	},
+	{
+		label: "Opening Stock",
+		url: docsUrl + "user/manual/en/stock/opening-stock",
+	},
+	{
+		label: "Subcontracting",
+		url: docsUrl + "user/manual/en/manufacturing/subcontracting",
+	},
+];
 
-frappe.help.help_links['serial-no'] = [
-	{ label: 'Serial No', url: docsUrl + 'user/manual/en/stock/serial-no' },
-]
+frappe.help.help_links["List/Stock Entry"] = [
+	{ label: "Stock Entry", url: docsUrl + "user/manual/en/stock/stock-entry" },
+];
 
-frappe.help.help_links['batch'] = [
-	{ label: 'Batch', url: docsUrl + 'user/manual/en/stock/batch' },
-]
+frappe.help.help_links["Tree/Warehouse"] = [
+	{ label: "Warehouse", url: docsUrl + "user/manual/en/stock/warehouse" },
+];
 
-frappe.help.help_links['packing-slip'] = [
-	{ label: 'Packing Slip', url: docsUrl + 'user/manual/en/stock/tools/packing-slip' },
-]
+frappe.help.help_links["List/Serial No"] = [
+	{ label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" },
+];
 
-frappe.help.help_links['quality-inspection'] = [
-	{ label: 'Quality Inspection', url: docsUrl + 'user/manual/en/stock/tools/quality-inspection' },
-]
+frappe.help.help_links["Form/Serial No"] = [
+	{ label: "Serial No", url: docsUrl + "user/manual/en/stock/serial-no" },
+];
 
-frappe.help.help_links['landed-cost-voucher'] = [
-	{ label: 'Landed Cost Voucher', url: docsUrl + 'user/manual/en/stock/tools/landed-cost-voucher' },
-]
+frappe.help.help_links["Form/Batch"] = [
+	{ label: "Batch", url: docsUrl + "user/manual/en/stock/batch" },
+];
 
-frappe.help.help_links['item-group/view/tree'] = [
-	{ label: 'Item Group', url: docsUrl + 'user/manual/en/stock/setup/item-group' },
-]
+frappe.help.help_links["Form/Packing Slip"] = [
+	{
+		label: "Packing Slip",
+		url: docsUrl + "user/manual/en/stock/tools/packing-slip",
+	},
+];
 
-frappe.help.help_links['item-attribute'] = [
-	{ label: 'Item Attribute', url: docsUrl + 'user/manual/en/stock/setup/item-attribute' },
-]
+frappe.help.help_links["Form/Quality Inspection"] = [
+	{
+		label: "Quality Inspection",
+		url: docsUrl + "user/manual/en/stock/tools/quality-inspection",
+	},
+];
 
-frappe.help.help_links['uom'] = [
-	{ label: 'Fractions in UOM', url: docsUrl + 'user/manual/en/stock/articles/managing-fractions-in-uom' },
-]
+frappe.help.help_links["Form/Landed Cost Voucher"] = [
+	{
+		label: "Landed Cost Voucher",
+		url: docsUrl + "user/manual/en/stock/tools/landed-cost-voucher",
+	},
+];
 
-frappe.help.help_links['stock-reconciliation'] = [
-	{ label: 'Opening Stock Entry', url: docsUrl + 'user/manual/en/stock/opening-stock' },
-]
+frappe.help.help_links["Tree/Item Group"] = [
+	{
+		label: "Item Group",
+		url: docsUrl + "user/manual/en/stock/setup/item-group",
+	},
+];
+
+frappe.help.help_links["Form/Item Attribute"] = [
+	{
+		label: "Item Attribute",
+		url: docsUrl + "user/manual/en/stock/setup/item-attribute",
+	},
+];
+
+frappe.help.help_links["Form/UOM"] = [
+	{
+		label: "Fractions in UOM",
+		url:
+			docsUrl + "user/manual/en/stock/articles/managing-fractions-in-uom",
+	},
+];
+
+frappe.help.help_links["Form/Stock Reconciliation"] = [
+	{
+		label: "Opening Stock Entry",
+		url: docsUrl + "user/manual/en/stock/opening-stock",
+	},
+];
 
 //CRM
 
-frappe.help.help_links['lead'] = [
-	{ label: 'Lead', url: docsUrl + 'user/manual/en/CRM/lead' },
-]
+frappe.help.help_links["Form/Lead"] = [
+	{ label: "Lead", url: docsUrl + "user/manual/en/CRM/lead" },
+];
 
-frappe.help.help_links['opportunity'] = [
-	{ label: 'Opportunity', url: docsUrl + 'user/manual/en/CRM/opportunity' },
-]
+frappe.help.help_links["Form/Opportunity"] = [
+	{ label: "Opportunity", url: docsUrl + "user/manual/en/CRM/opportunity" },
+];
 
-frappe.help.help_links['address'] = [
-	{ label: 'Address', url: docsUrl + 'user/manual/en/CRM/address' },
-]
+frappe.help.help_links["Form/Address"] = [
+	{ label: "Address", url: docsUrl + "user/manual/en/CRM/address" },
+];
 
-frappe.help.help_links['contact'] = [
-	{ label: 'Contact', url: docsUrl + 'user/manual/en/CRM/contact' },
-]
+frappe.help.help_links["Form/Contact"] = [
+	{ label: "Contact", url: docsUrl + "user/manual/en/CRM/contact" },
+];
 
-frappe.help.help_links['newsletter'] = [
-	{ label: 'Newsletter', url: docsUrl + 'user/manual/en/CRM/newsletter' },
-]
+frappe.help.help_links["Form/Newsletter"] = [
+	{ label: "Newsletter", url: docsUrl + "user/manual/en/CRM/newsletter" },
+];
 
-frappe.help.help_links['campaign'] = [
-	{ label: 'Campaign', url: docsUrl + 'user/manual/en/CRM/setup/campaign' },
-]
+frappe.help.help_links["Form/Campaign"] = [
+	{ label: "Campaign", url: docsUrl + "user/manual/en/CRM/setup/campaign" },
+];
 
-frappe.help.help_links['sales-person/view/tree'] = [
-	{ label: 'Sales Person', url: docsUrl + 'user/manual/en/CRM/setup/sales-person' },
-]
+frappe.help.help_links["Tree/Sales Person"] = [
+	{
+		label: "Sales Person",
+		url: docsUrl + "user/manual/en/CRM/setup/sales-person",
+	},
+];
 
-frappe.help.help_links['sales-person'] = [
-	{ label: 'Sales Person Target', url: docsUrl + 'user/manual/en/selling/setup/sales-person-target-allocation' },
-]
+frappe.help.help_links["Form/Sales Person"] = [
+	{
+		label: "Sales Person Target",
+		url:
+			docsUrl +
+			"user/manual/en/selling/setup/sales-person-target-allocation",
+	},
+];
+
+//Support
+
+frappe.help.help_links["List/Feedback Trigger"] = [
+	{
+		label: "Feedback Trigger",
+		url: docsUrl + "user/manual/en/setting-up/feedback/setting-up-feedback",
+	},
+];
+
+frappe.help.help_links["List/Feedback Request"] = [
+	{
+		label: "Feedback Request",
+		url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback",
+	},
+];
+
+frappe.help.help_links["List/Feedback Request"] = [
+	{
+		label: "Feedback Request",
+		url: docsUrl + "user/manual/en/setting-up/feedback/submit-feedback",
+	},
+];
 
 //Manufacturing
 
-frappe.help.help_links['bom'] = [
-	{ label: 'Bill of Material', url: docsUrl + 'user/manual/en/manufacturing/bill-of-materials' },
-	{ label: 'Nested BOM Structure', url: docsUrl + 'user/manual/en/manufacturing/articles/nested-bom-structure' },
-]
+frappe.help.help_links["Form/BOM"] = [
+	{
+		label: "Bill of Material",
+		url: docsUrl + "user/manual/en/manufacturing/bill-of-materials",
+	},
+	{
+		label: "Nested BOM Structure",
+		url:
+			docsUrl +
+			"user/manual/en/manufacturing/articles/nested-bom-structure",
+	},
+];
 
-frappe.help.help_links['work-order'] = [
-	{ label: 'Work Order', url: docsUrl + 'user/manual/en/manufacturing/work-order' },
-]
+frappe.help.help_links["Form/Work Order"] = [
+	{
+		label: "Work Order",
+		url: docsUrl + "user/manual/en/manufacturing/work-order",
+	},
+];
 
-frappe.help.help_links['workstation'] = [
-	{ label: 'Workstation', url: docsUrl + 'user/manual/en/manufacturing/workstation' },
-]
+frappe.help.help_links["Form/Workstation"] = [
+	{
+		label: "Workstation",
+		url: docsUrl + "user/manual/en/manufacturing/workstation",
+	},
+];
 
-frappe.help.help_links['operation'] = [
-	{ label: 'Operation', url: docsUrl + 'user/manual/en/manufacturing/operation' },
-]
+frappe.help.help_links["Form/Operation"] = [
+	{
+		label: "Operation",
+		url: docsUrl + "user/manual/en/manufacturing/operation",
+	},
+];
 
-frappe.help.help_links['bom-update-tool'] = [
-	{ label: 'BOM Update Tool', url: docsUrl + 'user/manual/en/manufacturing/tools/bom-update-tool' },
-]
+frappe.help.help_links["Form/BOM Update Tool"] = [
+	{
+		label: "BOM Update Tool",
+		url: docsUrl + "user/manual/en/manufacturing/tools/bom-update-tool",
+	},
+];
 
 //Customize
 
-frappe.help.help_links['customize-form'] = [
-	{ label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
-	{ label: 'Customize Field', url: docsUrl + 'user/manual/en/customize-erpnext/customize-form' },
-]
+frappe.help.help_links["Form/Customize Form"] = [
+	{
+		label: "Custom Field",
+		url: docsUrl + "user/manual/en/customize-erpnext/custom-field",
+	},
+	{
+		label: "Customize Field",
+		url: docsUrl + "user/manual/en/customize-erpnext/customize-form",
+	},
+];
 
-frappe.help.help_links['custom-field'] = [
-	{ label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
-]
+frappe.help.help_links["Form/Custom Field"] = [
+	{
+		label: "Custom Field",
+		url: docsUrl + "user/manual/en/customize-erpnext/custom-field",
+	},
+];
 
-frappe.help.help_links['custom-field'] = [
-	{ label: 'Custom Field', url: docsUrl + 'user/manual/en/customize-erpnext/custom-field' },
-]
+frappe.help.help_links["Form/Custom Field"] = [
+	{
+		label: "Custom Field",
+		url: docsUrl + "user/manual/en/customize-erpnext/custom-field",
+	},
+];
diff --git a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py
index bf82cc0..5a8ec73 100644
--- a/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py
+++ b/erpnext/quality_management/doctype/quality_feedback/quality_feedback.py
@@ -7,6 +7,7 @@
 from frappe.model.document import Document
 
 class QualityFeedback(Document):
+	@frappe.whitelist()
 	def set_parameters(self):
 		if self.template and not getattr(self, 'parameters', []):
 			for d in frappe.get_doc('Quality Feedback Template', self.template).parameters:
diff --git a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
index db8bda7..68ed339 100644
--- a/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
+++ b/erpnext/regional/doctype/e_invoice_settings/e_invoice_settings.json
@@ -8,6 +8,7 @@
   "enable",
   "section_break_2",
   "sandbox_mode",
+  "applicable_from",
   "credentials",
   "auth_token",
   "token_expiry"
@@ -48,12 +49,19 @@
    "fieldname": "sandbox_mode",
    "fieldtype": "Check",
    "label": "Sandbox Mode"
+  },
+  {
+   "fieldname": "applicable_from",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Applicable From",
+   "reqd": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2021-01-13 12:04:49.449199",
+ "modified": "2021-03-30 12:26:25.538294",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "E Invoice Settings",
diff --git a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
index dd9d997..a65b1ca 100644
--- a/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
+++ b/erpnext/regional/doctype/e_invoice_user/e_invoice_user.json
@@ -5,6 +5,7 @@
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "company",
   "gstin",
   "username",
   "password"
@@ -30,12 +31,20 @@
    "in_list_view": 1,
    "label": "Password",
    "reqd": 1
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Company",
+   "options": "Company",
+   "reqd": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-12-22 15:10:53.466205",
+ "modified": "2021-03-22 12:16:56.365616",
  "modified_by": "Administrator",
  "module": "Regional",
  "name": "E Invoice User",
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
index 888b2da..369a400 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.html
@@ -109,7 +109,7 @@
 			</td>
 		</tr>
 		<tr>
-			<td>{{__("Suppliies made to Composition Taxable Persons")}}</td>
+			<td>{{__("Supplies made to Composition Taxable Persons")}}</td>
 			<td class="right">
 				{% for row in data.inter_sup.comp_details %}
 					{% if row %}
diff --git a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
index 68c8a0d..a5dd5a2 100644
--- a/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/gstr_3b_report.py
@@ -172,7 +172,6 @@
 		self.json_output = frappe.as_json(self.report_dict)
 
 	def set_inward_nil_exempt(self, inward_nil_exempt):
-
 		self.report_dict["inward_sup"]["isup_details"][0]["inter"] = flt(inward_nil_exempt.get("gst").get("inter"), 2)
 		self.report_dict["inward_sup"]["isup_details"][0]["intra"] = flt(inward_nil_exempt.get("gst").get("intra"), 2)
 		self.report_dict["inward_sup"]["isup_details"][1]["inter"] = flt(inward_nil_exempt.get("non_gst").get("inter"), 2)
@@ -238,7 +237,6 @@
 		self.report_dict[supply_type][supply_category]["txval"] += flt(txval, 2)
 
 	def set_inter_state_supply(self, inter_state_supply):
-
 		osup_det = self.report_dict["sup_details"]["osup_det"]
 
 		for key, value in iteritems(inter_state_supply):
@@ -349,13 +347,20 @@
 		return inter_state_supply_details
 
 	def get_inward_nil_exempt(self, state):
-
 		inward_nil_exempt = frappe.db.sql(""" select p.place_of_supply, sum(i.base_amount) as base_amount,
 			i.is_nil_exempt, i.is_non_gst from `tabPurchase Invoice` p , `tabPurchase Invoice Item` i
 			where p.docstatus = 1 and p.name = i.parent
-			and i.is_nil_exempt = 1 or i.is_non_gst = 1 and
+			and p.gst_category != 'Registered Composition'
+			and (i.is_nil_exempt = 1 or i.is_non_gst = 1) and
 			month(p.posting_date) = %s and year(p.posting_date) = %s and p.company = %s and p.company_gstin = %s
-			group by p.place_of_supply """, (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+			group by p.place_of_supply, i.is_nil_exempt, i.is_non_gst""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
+
+		inward_nil_exempt += frappe.db.sql("""SELECT sum(base_net_total) as base_amount, gst_category, place_of_supply
+			FROM `tabPurchase Invoice`
+			WHERE docstatus = 1 and gst_category = 'Registered Composition'
+			and month(posting_date) = %s and year(posting_date) = %s
+			and company = %s and company_gstin = %s
+			group by place_of_supply""", (self.month_no, self.year, self.company, self.gst_details.get("gstin")), as_dict=1)
 
 		inward_nil_exempt_details = {
 			"gst": {
@@ -370,9 +375,11 @@
 
 		for d in inward_nil_exempt:
 			if d.place_of_supply:
-				if d.is_nil_exempt == 1 and state == d.place_of_supply.split("-")[1]:
+				if (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
+					and state == d.place_of_supply.split("-")[1]:
 					inward_nil_exempt_details["gst"]["intra"] += d.base_amount
-				elif d.is_nil_exempt == 1 and state != d.place_of_supply.split("-")[1]:
+				elif (d.is_nil_exempt == 1 or d.get('gst_category') == 'Registered Composition') \
+					and state != d.place_of_supply.split("-")[1]:
 					inward_nil_exempt_details["gst"]["inter"] += d.base_amount
 				elif d.is_non_gst == 1 and state == d.place_of_supply.split("-")[1]:
 					inward_nil_exempt_details["non_gst"]["intra"] += d.base_amount
diff --git a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
index 023b4ed..ef8af24 100644
--- a/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
+++ b/erpnext/regional/doctype/gstr_3b_report/test_gstr_3b_report.py
@@ -64,7 +64,7 @@
 		self.assertEqual(output["sup_details"]["osup_zero"]["iamt"], 18),
 		self.assertEqual(output["inter_sup"]["unreg_details"][0]["iamt"], 18),
 		self.assertEqual(output["sup_details"]["osup_nil_exmp"]["txval"], 100),
-		self.assertEqual(output["inward_sup"]["isup_details"][0]["inter"], 250)
+		self.assertEqual(output["inward_sup"]["isup_details"][0]["intra"], 250)
 		self.assertEqual(output["itc_elg"]["itc_avl"][4]["samt"], 22.50)
 		self.assertEqual(output["itc_elg"]["itc_avl"][4]["camt"], 22.50)
 
@@ -228,6 +228,19 @@
 
 	pi1.submit()
 
+	pi2 = make_purchase_invoice(company="_Test Company GST",
+			customer = '_Test Registered Supplier',
+			currency = 'INR',
+			item = 'Milk',
+			warehouse = 'Finished Goods - _GST',
+			expense_account = 'Cost of Goods Sold - _GST',
+			cost_center = 'Main - _GST',
+			rate=250,
+			qty=1,
+			do_not_save=1
+		)
+	pi2.submit()
+
 def make_suppliers():
 	if not frappe.db.exists("Supplier", "_Test Registered Supplier"):
 		frappe.get_doc({
diff --git a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
index ef384d4..41a0f11 100644
--- a/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
+++ b/erpnext/regional/doctype/tax_exemption_80g_certificate/tax_exemption_80g_certificate.py
@@ -16,6 +16,7 @@
 		self.validate_duplicates()
 		self.validate_company_details()
 		self.set_company_address()
+		self.calculate_total()
 		self.set_title()
 
 	def validate_date(self):
@@ -49,17 +50,28 @@
 			frappe.throw(_('Please set the {0} for company {1}').format(frappe.bold('PAN Number'),
 				get_link_to_form('Company', self.company)))
 
+	@frappe.whitelist()
 	def set_company_address(self):
 		address = get_company_address(self.company)
 		self.company_address = address.company_address
 		self.company_address_display = address.company_address_display
 
+	def calculate_total(self):
+		if self.recipient == 'Donor':
+			return
+
+		total = 0
+		for entry in self.payments:
+			total += flt(entry.amount)
+		self.total = total
+
 	def set_title(self):
-		if self.recipient == "Member":
+		if self.recipient == 'Member':
 			self.title = self.member_name
 		else:
 			self.title = self.donor_name
 
+	@frappe.whitelist()
 	def get_payments(self):
 		if not self.member:
 			frappe.throw(_('Please select a Member first.'))
@@ -71,7 +83,7 @@
 			'from_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)],
 			'to_date': ['between', (fiscal_year.year_start_date, fiscal_year.year_end_date)],
 			'membership_status': ('!=', 'Cancelled')
-		}, ['from_date', 'amount', 'name', 'invoice', 'payment_id'])
+		}, ['from_date', 'amount', 'name', 'invoice', 'payment_id'], order_by='from_date')
 
 		if not memberships:
 			frappe.msgprint(_('No Membership Payments found against the Member {0}').format(self.member))
diff --git a/erpnext/regional/india/__init__.py b/erpnext/regional/india/__init__.py
index 378b735..faeb36f 100644
--- a/erpnext/regional/india/__init__.py
+++ b/erpnext/regional/india/__init__.py
@@ -69,7 +69,7 @@
  "Mizoram": "15",
  "Nagaland": "13",
  "Odisha": "21",
- "Other Territory": "98",
+ "Other Territory": "97",
  "Pondicherry": "34",
  "Punjab": "03",
  "Rajasthan": "08",
diff --git a/erpnext/regional/india/e_invoice/einv_validation.json b/erpnext/regional/india/e_invoice/einv_validation.json
index 86290cf..f4a3542 100644
--- a/erpnext/regional/india/e_invoice/einv_validation.json
+++ b/erpnext/regional/india/e_invoice/einv_validation.json
@@ -919,7 +919,8 @@
         "minLength": 1,
         "maxLength": 15,
         "pattern": "^([0-9A-Z/-]){1,15}$",
-        "description": "Tranport Document Number"
+        "description": "Tranport Document Number",
+        "validationMsg": "Transport Receipt No is invalid"
       },
       "TransDocDt": {
         "type": "string",
diff --git a/erpnext/regional/india/e_invoice/einvoice.js b/erpnext/regional/india/e_invoice/einvoice.js
index 7cd64f2..c1a222a 100644
--- a/erpnext/regional/india/e_invoice/einvoice.js
+++ b/erpnext/regional/india/e_invoice/einvoice.js
@@ -1,12 +1,13 @@
 erpnext.setup_einvoice_actions = (doctype) => {
 	frappe.ui.form.on(doctype, {
 		async refresh(frm) {
-			const einvoicing_enabled = await frappe.db.get_single_value("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;
+			const res = await frappe.call({
+				method: 'erpnext.regional.india.e_invoice.utils.validate_eligibility',
+				args: { doc: frm.doc }
+			});
+			const invoice_eligible = res.message;
 
-			if (cint(einvoicing_enabled) == 0 || !valid_supply_type || company_transaction) return;
+			if (!invoice_eligible) return;
 
 			const { doctype, irn, irn_cancelled, ewaybill, eway_bill_cancelled, name, __unsaved } = frm.doc;
 
@@ -109,45 +110,25 @@
 			}
 
 			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,
+					let message = __('Cancellation of e-way bill is currently not supported. ');
+					message += '<br><br>';
+					message += __('You must first use the portal to cancel the e-way bill and then update the cancelled status in the ERPNext system.');
+
+					frappe.msgprint({
+						title: __('Update E-Way Bill Cancelled Status?'),
+						message: message,
+						indicator: 'orange',
 						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
-								},
+								args: { doctype, docname: name },
 								freeze: true,
-								callback: () => frm.reload_doc() || d.hide(),
-								error: () => d.hide()
+								callback: () => frm.reload_doc()
 							});
 						},
-						primary_action_label: __('Submit')
+						primary_action_label: __('Yes')
 					});
-					d.show();
 				};
 				add_custom_button(__("Cancel E-Way Bill"), action);
 			}
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index 96f7f1b..fb1f464 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -15,18 +15,43 @@
 import io
 from frappe import _, bold
 from pyqrcode import create as qrcreate
+from frappe.utils.background_jobs import enqueue
+from frappe.utils.scheduler import is_scheduler_inactive
+from frappe.core.page.background_jobs.background_jobs import get_info
 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, get_link_to_form
+from frappe.utils.data import cstr, cint, format_date, flt, time_diff_in_seconds, now_datetime, add_to_date, get_link_to_form, getdate, time_diff_in_hours
 
-def validate_einvoice_fields(doc):
-	einvoicing_enabled = cint(frappe.db.get_value('E Invoice Settings', 'E Invoice Settings', 'enable'))
-	invalid_doctype = doc.doctype != 'Sales Invoice'
+@frappe.whitelist()
+def validate_eligibility(doc):
+	if isinstance(doc, six.string_types):
+		doc = json.loads(doc)
+
+	invalid_doctype = doc.get('doctype') != 'Sales Invoice'
+	if invalid_doctype:
+		return False
+
+	einvoicing_enabled = cint(frappe.db.get_single_value('E Invoice Settings', 'enable'))
+	if not einvoicing_enabled:
+		return False
+
+	einvoicing_eligible_from = frappe.db.get_single_value('E Invoice Settings', 'applicable_from') or '2021-04-01'
+	if getdate(doc.get('posting_date')) < getdate(einvoicing_eligible_from):
+		return False
+
 	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')
 	no_taxes_applied = not doc.get('taxes')
 
-	if not einvoicing_enabled or invalid_doctype or invalid_supply_type or company_transaction or no_taxes_applied:
+	if invalid_supply_type or company_transaction or no_taxes_applied:
+		return False
+
+	return True
+
+def validate_einvoice_fields(doc):
+	invoice_eligible = validate_eligibility(doc)
+
+	if not invoice_eligible:
 		return
 
 	if doc.docstatus == 0 and doc._action == 'save':
@@ -35,6 +60,8 @@
 		if len(doc.name) > 16:
 			raise_document_name_too_long_error()
 
+		doc.einvoice_status = 'Pending'
+
 	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'))
 
@@ -76,6 +103,9 @@
 	))
 
 def get_doc_details(invoice):
+	if getdate(invoice.posting_date) < getdate('2021-01-01'):
+		frappe.throw(_('IRN generation is not allowed for invoices dated before 1st Jan 2021'), title=_('Not Allowed'))
+
 	invoice_type = 'CRN' if invoice.is_return else 'INV'
 
 	invoice_name = invoice.name
@@ -87,53 +117,38 @@
 		invoice_date=invoice_date
 	))
 
-def get_party_details(address_name):
-	d = frappe.get_all('Address', filters={'name': address_name}, fields=['*'])[0]
-
-	if (not d.gstin
-		or not d.city
-		or not d.pincode
-		or not d.address_title
-		or not d.address_line1
-		or not d.gst_state_number):
+def validate_address_fields(address, is_shipping_address):
+	if ((not address.gstin and not is_shipping_address)
+		or not address.city
+		or not address.pincode
+		or not address.address_title
+		or not address.address_line1
+		or not address.gst_state_number):
 
 		frappe.throw(
-			msg=_('Address lines, city, pincode, gstin is mandatory for address {}. Please set them and try again.').format(
-				get_link_to_form('Address', address_name)
-			),
+			msg=_('Address Lines, City, Pincode, GSTIN are mandatory for address {}. Please set them and try again.').format(address.name),
 			title=_('Missing Address Fields')
 		)
 
-	if d.gst_state_number == 97:
-		# according to einvoice standard
-		pincode = 999999
+def get_party_details(address_name, is_shipping_address=False):
+	addr = frappe.get_doc('Address', address_name)
+	
+	validate_address_fields(addr, is_shipping_address)
 
-	return frappe._dict(dict(
-		gstin=d.gstin,
-		legal_name=sanitize_for_json(d.address_title),
-		location=sanitize_for_json(d.city),
-		pincode=d.pincode,
-		state_code=d.gst_state_number,
-		address_line1=sanitize_for_json(d.address_line1),
-		address_line2=sanitize_for_json(d.address_line2)
+	if addr.gst_state_number == 97:
+		# according to einvoice standard
+		addr.pincode = 999999
+
+	party_address_details = frappe._dict(dict(
+		legal_name=sanitize_for_json(addr.address_title),
+		location=sanitize_for_json(addr.city),
+		pincode=addr.pincode, gstin=addr.gstin,
+		state_code=addr.gst_state_number,
+		address_line1=sanitize_for_json(addr.address_line1),
+		address_line2=sanitize_for_json(addr.address_line2)
 	))
 
-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)
+	return party_address_details
 
 def get_overseas_address_details(address_name):
 	address_title, address_line1, address_line2, city = frappe.db.get_value(
@@ -169,10 +184,15 @@
 		item.description = sanitize_for_json(d.item_name)
 
 		item.qty = abs(item.qty)
-		item.discount_amount = 0
-		item.unit_rate = abs(item.base_net_amount / item.qty)
-		item.gross_amount = abs(item.base_net_amount)
-		item.taxable_value = abs(item.base_net_amount)
+
+		if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
+			item.discount_amount = abs(item.base_amount - item.base_net_amount)
+		else:
+			item.discount_amount = 0
+
+		item.unit_rate = abs((abs(item.taxable_value) - item.discount_amount)/ item.qty)
+		item.gross_amount = abs(item.taxable_value) + item.discount_amount
+		item.taxable_value = abs(item.taxable_value)
 
 		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
@@ -205,11 +225,11 @@
 		is_applicable = t.tax_amount and t.account_head in gst_accounts_list
 		if is_applicable:
 			# this contains item wise tax rate & tax amount (incl. discount)
-			item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code)
+			item_tax_detail = json.loads(t.item_wise_tax_detail).get(item.item_code or item.item_name)
 
 			item_tax_rate = item_tax_detail[0]
 			# item tax amount excluding discount amount
-			item_tax_amount = (item_tax_rate / 100) * item.base_net_amount
+			item_tax_amount = (item_tax_rate / 100) * item.taxable_value
 
 			if t.account_head in gst_accounts.cess_account:
 				item_tax_amount_after_discount = item_tax_detail[1]
@@ -223,6 +243,9 @@
 				if t.account_head in gst_accounts[f'{tax_type}_account']:
 					item.tax_rate += item_tax_rate
 					item[f'{tax_type}_amount'] += abs(item_tax_amount)
+		else:
+			# TODO: other charges per item
+			pass
 
 	return item
 
@@ -230,10 +253,14 @@
 	invoice_value_details = frappe._dict(dict())
 
 	if invoice.apply_discount_on == 'Net Total' and invoice.discount_amount:
-		invoice_value_details.base_total = abs(invoice.base_total)
-		invoice_value_details.invoice_discount_amt = abs(invoice.base_discount_amount)
+		# Discount already applied on net total which means on items
+		invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
+		invoice_value_details.invoice_discount_amt = 0
+	elif invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount:
+		invoice_value_details.invoice_discount_amt = invoice.base_discount_amount
+		invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
 	else:
-		invoice_value_details.base_total = abs(invoice.base_net_total)
+		invoice_value_details.base_total = abs(sum([i.taxable_value for i in invoice.get('items')]))
 		# since tax already considers discount amount
 		invoice_value_details.invoice_discount_amt = 0
 
@@ -254,7 +281,11 @@
 	invoice_value_details.total_igst_amt = 0
 	invoice_value_details.total_cess_amt = 0
 	invoice_value_details.total_other_charges = 0
+	considered_rows = []
+
 	for t in invoice.taxes:
+		tax_amount = t.base_tax_amount if (invoice.apply_discount_on == 'Grand Total' and invoice.discount_amount) \
+						else t.base_tax_amount_after_discount_amount
 		if t.account_head in gst_accounts_list:
 			if t.account_head in gst_accounts.cess_account:
 				# using after discount amt since item also uses after discount amt for cess calc
@@ -262,12 +293,26 @@
 
 			for tax_type in ['igst', 'cgst', 'sgst']:
 				if t.account_head in gst_accounts[f'{tax_type}_account']:
-					invoice_value_details[f'total_{tax_type}_amt'] += abs(t.base_tax_amount_after_discount_amount)
+
+					invoice_value_details[f'total_{tax_type}_amt'] += abs(tax_amount)
+				update_other_charges(t, invoice_value_details, gst_accounts_list, invoice, considered_rows)
 		else:
-			invoice_value_details.total_other_charges += abs(t.base_tax_amount_after_discount_amount)
+			invoice_value_details.total_other_charges += abs(tax_amount)
 
 	return invoice_value_details
 
+def update_other_charges(tax_row, invoice_value_details, gst_accounts_list, invoice, considered_rows):
+	prev_row_id = cint(tax_row.row_id) - 1
+	if tax_row.account_head in gst_accounts_list and prev_row_id not in considered_rows:
+		if tax_row.charge_type == 'On Previous Row Amount':
+			amount = invoice.get('taxes')[prev_row_id].tax_amount_after_discount_amount
+			invoice_value_details.total_other_charges -= abs(amount)
+			considered_rows.append(prev_row_id)
+		if tax_row.charge_type == 'On Previous Row Total':
+			amount = invoice.get('taxes')[prev_row_id].base_total - invoice.base_net_total
+			invoice_value_details.total_other_charges -= abs(amount)
+			considered_rows.append(prev_row_id)
+
 def get_payment_details(invoice):
 	payee_name = invoice.company
 	mode_of_payment = ', '.join([d.mode_of_payment for d in invoice.payments])
@@ -280,6 +325,10 @@
 	))
 
 def get_return_doc_reference(invoice):
+	if not invoice.return_against:
+		frappe.throw(_('For generating IRN, reference to the original invoice is mandatory for a credit note. Please set {} field to generate e-invoice.')
+			.format(frappe.bold('Return Against')), title=_('Missing Field'))
+
 	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')
@@ -287,7 +336,11 @@
 
 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'))
+		frappe.throw(_('E-Way Bill cannot be generated for Credit Notes & Debit Notes. Please clear fields in the Transporter Section of the invoice.'),
+			title=_('Invalid Fields'))
+	
+	if not invoice.distance:
+		frappe.throw(_('Distance is mandatory for generating e-way bill for an e-invoice.'), title=_('Missing Field'))
 
 	mode_of_transport = { '': '', 'Road': '1', 'Air': '2', 'Rail': '3', 'Ship': '4' }
 	vehicle_type = { 'Regular': 'R', 'Over Dimensional Cargo (ODC)': 'O' }
@@ -305,9 +358,15 @@
 
 def validate_mandatory_fields(invoice):
 	if not invoice.company_address:
-		frappe.throw(_('Company Address is mandatory to fetch company GSTIN details.'), title=_('Missing Fields'))
+		frappe.throw(
+			_('Company Address is mandatory to fetch company GSTIN details. Please set Company Address and try again.'),
+			title=_('Missing Fields')
+		)
 	if not invoice.customer_address:
-		frappe.throw(_('Customer Address is mandatory to fetch customer GSTIN details.'), title=_('Missing Fields'))
+		frappe.throw(
+			_('Customer Address is mandatory to fetch customer GSTIN details. Please set Company Address and try again.'),
+			title=_('Missing Fields')
+		)
 	if not frappe.db.get_value('Address', invoice.company_address, 'gstin'):
 		frappe.throw(
 			_('GSTIN is mandatory to fetch company GSTIN details. Please enter GSTIN in selected company address.'),
@@ -319,6 +378,39 @@
 			title=_('Missing Fields')
 		)
 
+def validate_totals(einvoice):
+	item_list = einvoice['ItemList']
+	value_details = einvoice['ValDtls']
+
+	total_item_ass_value = 0
+	total_item_cgst_value = 0
+	total_item_sgst_value = 0
+	total_item_igst_value = 0
+	total_item_value = 0
+	for item in item_list:
+		total_item_ass_value += flt(item['AssAmt'])
+		total_item_cgst_value += flt(item['CgstAmt'])
+		total_item_sgst_value += flt(item['SgstAmt'])
+		total_item_igst_value += flt(item['IgstAmt'])
+		total_item_value += flt(item['TotItemVal'])
+
+		if abs(flt(item['AssAmt']) * flt(item['GstRt']) / 100) - (flt(item['CgstAmt']) + flt(item['SgstAmt']) + flt(item['IgstAmt'])) > 1:
+			frappe.throw(_('Row #{}: GST rate is invalid. Please remove tax rows with zero tax amount from taxes table.').format(item.idx))
+
+	if abs(flt(value_details['AssVal']) - total_item_ass_value) > 1:
+		frappe.throw(_('Total Taxable Value of the items is not equal to the Invoice Net Total. Please check item taxes / discounts for any correction.'))
+
+	if abs(flt(value_details['TotInvVal']) + flt(value_details['Discount']) - total_item_value) > 1:
+		frappe.throw(_('Total Value of the items is not equal to the Invoice Grand Total. Please check item taxes / discounts for any correction.'))
+
+	calculated_invoice_value = \
+			flt(value_details['AssVal']) + flt(value_details['CgstVal']) \
+			+ flt(value_details['SgstVal']) + flt(value_details['IgstVal']) \
+			+ flt(value_details['OthChrg']) - flt(value_details['Discount'])
+
+	if abs(flt(value_details['TotInvVal']) - calculated_invoice_value) > 1:
+		frappe.throw(_('Total Item Value + Taxes - Discount is not equal to the Invoice Grand Total. Please check taxes / discounts for any correction.'))
+
 def make_einvoice(invoice):
 	validate_mandatory_fields(invoice)
 
@@ -334,24 +426,30 @@
 		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 sanitize_for_json(invoice.billing_address_gstin)
-		place_of_supply = place_of_supply[:2]
+		place_of_supply = get_place_of_supply(invoice, invoice.doctype)
+		if place_of_supply:
+			place_of_supply = place_of_supply.split('-')[0]
+		else:
+			place_of_supply = sanitize_for_json(invoice.billing_address_gstin)[:2]
 		buyer_details.update(dict(place_of_supply=place_of_supply))
 
+	seller_details.update(dict(legal_name=invoice.company))
+	buyer_details.update(dict(legal_name=invoice.customer_name or invoice.customer))
+
 	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:
 		if invoice.gst_category == 'Overseas':
 			shipping_details = get_overseas_address_details(invoice.shipping_address_name)
 		else:
-			shipping_details = get_party_details(invoice.shipping_address_name)
+			shipping_details = get_party_details(invoice.shipping_address_name, is_shipping_address=True)
 
 	if invoice.is_pos and invoice.base_paid_amount:
 		payment_details = get_payment_details(invoice)
 
-	if invoice.is_return and invoice.return_against:
+	if invoice.is_return:
 		prev_doc_details = get_return_doc_reference(invoice)
 
-	if invoice.transporter:
+	if invoice.transporter and flt(invoice.distance) and not invoice.is_return:
 		eway_bill_details = get_eway_bill_details(invoice)
 
 	# not yet implemented
@@ -364,18 +462,70 @@
 		period_details=period_details, prev_doc_details=prev_doc_details,
 		export_details=export_details, eway_bill_details=eway_bill_details
 	)
-	einvoice = safe_json_load(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)
+	try:
+		einvoice = safe_json_load(einvoice)
+		einvoice = santize_einvoice_fields(einvoice)
+		validate_totals(einvoice)
+
+	except Exception:
+		log_error(einvoice)
+		link_to_error_list = '<a href="List/Error Log/List?method=E Invoice Request Failed">Error Log</a>'
+		frappe.throw(
+			_('An error occurred while creating e-invoice for {}. Please check {} for more information.').format(
+				invoice.name, link_to_error_list),
+			title=_('E Invoice Creation Failed')
+		)
+
+	return einvoice
+
+def log_error(data=None):
+	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 santize_einvoice_fields(einvoice):
+	int_fields = ["Pin","Distance","CrDay"]
+	float_fields = ["Qty","FreeQty","UnitPrice","TotAmt","Discount","PreTaxVal","AssAmt","GstRt","IgstAmt","CgstAmt","SgstAmt","CesRt","CesAmt","CesNonAdvlAmt","StateCesRt","StateCesAmt","StateCesNonAdvlAmt","OthChrg","TotItemVal","AssVal","CgstVal","SgstVal","IgstVal","CesVal","StCesVal","Discount","OthChrg","RndOffAmt","TotInvVal","TotInvValFc","PaidAmt","PaymtDue","ExpDuty",]
+	copy = einvoice.copy()
+	for key, value in copy.items():
+		if isinstance(value, list):
+			for idx, d in enumerate(value):
+				santized_dict = santize_einvoice_fields(d)
+				if santized_dict:
+					einvoice[key][idx] = santized_dict
+				else:
+					einvoice[key].pop(idx)
+
+			if not einvoice[key]:
+				einvoice.pop(key, None)
+
+		elif isinstance(value, dict):
+			santized_dict = santize_einvoice_fields(value)
+			if santized_dict:
+				einvoice[key] = santized_dict
+			else:
+				einvoice.pop(key, None)
+
+		elif not value or value == "None":
+			einvoice.pop(key, None)
+
+		elif key in float_fields:
+			einvoice[key] = flt(value, 2)
+
+		elif key in int_fields:
+			einvoice[key] = cint(value)
 
 	return einvoice
 
@@ -391,70 +541,22 @@
 		snippet = json_string[start:end]
 		frappe.throw(_("Error in input data. Please check for any special characters near following input: <br> {}").format(snippet))
 
-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'))
-			precision = 3 if '.999' in str(field_validation.get('maximum')) else 2
-			einvoice[fieldname] = flt(value, precision) 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 RequestFailed(Exception):
+	pass
+class CancellationNotAllowed(Exception):
+	pass
 
 class GSPConnector():
 	def __init__(self, doctype=None, docname=None):
-		self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
-		sandbox_mode = self.e_invoice_settings.sandbox_mode
+		self.doctype = doctype
+		self.docname = docname
 
-		self.invoice = frappe.get_cached_doc(doctype, docname) if doctype and docname else None
-		self.credentials = self.get_credentials()
+		self.set_invoice()
+		self.set_credentials()
 
 		# authenticate url is same for sandbox & live
 		self.authenticate_url = 'https://gsp.adaequare.com/gsp/authenticate?grant_type=token'
-		self.base_url = 'https://gsp.adaequare.com' if not sandbox_mode else 'https://gsp.adaequare.com/test'
+		self.base_url = 'https://gsp.adaequare.com' if not self.e_invoice_settings.sandbox_mode else 'https://gsp.adaequare.com/test'
 
 		self.cancel_irn_url = self.base_url + '/enriched/ei/api/invoice/cancel'
 		self.irn_details_url = self.base_url + '/enriched/ei/api/invoice/irn'
@@ -463,15 +565,26 @@
 		self.cancel_ewaybill_url = self.base_url + '/enriched/ewb/ewayapi?action=CANEWB'
 		self.generate_ewaybill_url = self.base_url + '/enriched/ei/api/ewaybill'
 
-	def get_credentials(self):
+	def set_invoice(self):
+		self.invoice = None
+		if self.doctype and self.docname:
+			self.invoice = frappe.get_cached_doc(self.doctype, self.docname)
+
+	def set_credentials(self):
+		self.e_invoice_settings = frappe.get_cached_doc('E Invoice Settings')
+
+		if not self.e_invoice_settings.enable:
+			frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
+
 		if self.invoice:
 			gstin = self.get_seller_gstin()
-			if not self.e_invoice_settings.enable:
-				frappe.throw(_("E-Invoicing is disabled. Please enable it from {} to generate e-invoices.").format(get_link_to_form("E Invoice Settings", "E Invoice Settings")))
-			credentials = next(d for d in self.e_invoice_settings.credentials if d.gstin == gstin)
+			credentials_for_gstin = [d for d in self.e_invoice_settings.credentials if d.gstin == gstin]
+			if credentials_for_gstin:
+				self.credentials = credentials_for_gstin[0]
+			else:
+				frappe.throw(_('Cannot find e-invoicing credentials for selected Company GSTIN. Please check E-Invoice Settings'))
 		else:
-			credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
-		return credentials
+			self.credentials = self.e_invoice_settings.credentials[0] if self.e_invoice_settings.credentials else None
 
 	def get_seller_gstin(self):
 		gstin = self.invoice.company_gstin or frappe.db.get_value('Address', self.invoice.company_address, 'gstin')
@@ -522,7 +635,7 @@
 			self.e_invoice_settings.reload()
 
 		except Exception:
-			self.log_error(res)
+			log_error(res)
 			self.raise_error(True)
 
 	def get_headers(self):
@@ -544,14 +657,14 @@
 			if res.get('success'):
 				return res.get('result')
 			else:
-				self.log_error(res)
+				log_error(res)
 				raise RequestFailed
 
 		except RequestFailed:
 			self.raise_error()
 
 		except Exception:
-			self.log_error()
+			log_error()
 			self.raise_error(True)
 
 	@staticmethod
@@ -569,12 +682,13 @@
 		return details
 
 	def generate_irn(self):
-		headers = self.get_headers()
-		einvoice = make_einvoice(self.invoice)
-		data = json.dumps(einvoice, indent=4)
-
+		data = {}
 		try:
+			headers = self.get_headers()
+			einvoice = make_einvoice(self.invoice)
+			data = json.dumps(einvoice, indent=4)
 			res = self.make_request('post', self.generate_irn_url, headers, data)
+
 			if res.get('success'):
 				self.set_einvoice_data(res.get('result'))
 
@@ -594,12 +708,36 @@
 
 		except RequestFailed:
 			errors = self.sanitize_error_message(res.get('message'))
+			self.set_failed_status(errors=errors)
 			self.raise_error(errors=errors)
 
-		except Exception:
-			self.log_error(data)
+		except Exception as e:
+			self.set_failed_status(errors=str(e))
+			log_error(data)
 			self.raise_error(True)
 
+	@staticmethod
+	def bulk_generate_irn(invoices):
+		gsp_connector = GSPConnector()
+		gsp_connector.doctype = 'Sales Invoice'
+
+		failed = []
+
+		for invoice in invoices:
+			try:
+				gsp_connector.docname = invoice
+				gsp_connector.set_invoice()
+				gsp_connector.set_credentials()
+				gsp_connector.generate_irn()
+
+			except Exception as e:
+				failed.append({
+					'docname': invoice,
+					'message': str(e)
+				})
+
+		return failed
+
 	def get_irn_details(self, irn):
 		headers = self.get_headers()
 
@@ -616,21 +754,30 @@
 			self.raise_error(errors=errors)
 
 		except Exception:
-			self.log_error()
+			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)
-
+		data, res = {}, {}
 		try:
+			# validate cancellation
+			if time_diff_in_hours(now_datetime(), self.invoice.ack_date) > 24:
+				frappe.throw(_('E-Invoice cannot be cancelled after 24 hours of IRN generation.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
+			if not irn:
+				frappe.throw(_('IRN not found. You must generate IRN before cancelling.'), title=_('Not Allowed'), exc=CancellationNotAllowed)
+
+			headers = self.get_headers()
+			data = json.dumps({
+				'Irn': irn,
+				'Cnlrsn': reason,
+				'Cnlrem': remark
+			}, indent=4)
+
 			res = self.make_request('post', self.cancel_irn_url, headers, data)
-			if res.get('success'):
+			if res.get('success') or '9999' in res.get('message'):
 				self.invoice.irn_cancelled = 1
+				self.invoice.irn_cancel_date = res.get('result')['CancelDate'] if res.get('result') else ""
+				self.invoice.einvoice_status = 'Cancelled'
 				self.invoice.flags.updater_reference = {
 					'doctype': self.invoice.doctype,
 					'docname': self.invoice.name,
@@ -643,12 +790,41 @@
 
 		except RequestFailed:
 			errors = self.sanitize_error_message(res.get('message'))
+			self.set_failed_status(errors=errors)
 			self.raise_error(errors=errors)
 
-		except Exception:
-			self.log_error(data)
+		except CancellationNotAllowed as e:
+			self.set_failed_status(errors=str(e))
+			self.raise_error(errors=str(e))
+
+		except Exception as e:
+			self.set_failed_status(errors=str(e))
+			log_error(data)
 			self.raise_error(True)
 
+	@staticmethod
+	def bulk_cancel_irn(invoices, reason, remark):
+		gsp_connector = GSPConnector()
+		gsp_connector.doctype = 'Sales Invoice'
+
+		failed = []
+
+		for invoice in invoices:
+			try:
+				gsp_connector.docname = invoice
+				gsp_connector.set_invoice()
+				gsp_connector.set_credentials()
+				irn = gsp_connector.invoice.irn
+				gsp_connector.cancel_irn(irn, reason, remark)
+
+			except Exception as e:
+				failed.append({
+					'docname': invoice,
+					'message': str(e)
+				})
+
+		return failed
+
 	def generate_eway_bill(self, **kwargs):
 		args = frappe._dict(kwargs)
 
@@ -687,7 +863,7 @@
 			self.raise_error(errors=errors)
 
 		except Exception:
-			self.log_error(data)
+			log_error(data)
 			self.raise_error(True)
 
 	def cancel_eway_bill(self, eway_bill, reason, remark):
@@ -719,7 +895,7 @@
 			self.raise_error(errors=errors)
 
 		except Exception:
-			self.log_error(data)
+			log_error(data)
 			self.raise_error(True)
 
 	def sanitize_error_message(self, message):
@@ -734,6 +910,9 @@
 			]
 			then we trim down the message by looping over errors
 		'''
+		if not message:
+			return []
+
 		errors = re.findall(': [^:]+', message)
 		for idx, e in enumerate(errors):
 			# remove colons
@@ -745,22 +924,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:
@@ -780,8 +943,13 @@
 
 		self.invoice.irn = res.get('Irn')
 		self.invoice.ewaybill = res.get('EwbNo')
+		self.invoice.ack_no = res.get('AckNo')
+		self.invoice.ack_date = res.get('AckDt')
 		self.invoice.signed_einvoice = dec_signed_invoice
+		self.invoice.ack_no = res.get('AckNo')
+		self.invoice.ack_date = res.get('AckDt')
 		self.invoice.signed_qr_code = res.get('SignedQRCode')
+		self.invoice.einvoice_status = 'Generated'
 
 		self.attach_qrcode_image()
 
@@ -818,6 +986,17 @@
 		self.invoice.flags.ignore_validate = True
 		self.invoice.save()
 
+	def set_failed_status(self, errors=None):
+		frappe.db.rollback()
+		self.invoice.einvoice_status = 'Failed'
+		self.invoice.failure_description = self.get_failure_message(errors) if errors else ""
+		self.update_invoice()
+		frappe.db.commit()
+	
+	def get_failure_message(self, errors):
+		if isinstance(errors, list):
+			errors = ', '.join(errors)
+		return errors
 
 def sanitize_for_json(string):
 	"""Escape JSON specific characters from a string."""
@@ -847,5 +1026,114 @@
 
 @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)
+	# TODO: uncomment when eway_bill api from Adequare is enabled
+	# gsp_connector = GSPConnector(doctype, docname)
+	# gsp_connector.cancel_eway_bill(eway_bill, reason, remark)
+
+	# update cancelled status only, to be able to cancel irn next
+	frappe.db.set_value(doctype, docname, 'eway_bill_cancelled', 1)
+
+@frappe.whitelist()
+def generate_einvoices(docnames):
+	docnames = json.loads(docnames) or []
+
+	if len(docnames) < 10:
+		failures = GSPConnector.bulk_generate_irn(docnames)
+		frappe.local.message_log = []
+
+		if failures:
+			show_bulk_action_failure_message(failures)
+
+		success = len(docnames) - len(failures)
+		frappe.msgprint(
+			_('{} e-invoices generated successfully').format(success),
+			title=_('Bulk E-Invoice Generation Complete')
+		)
+			
+	else:
+		enqueue_bulk_action(schedule_bulk_generate_irn, docnames=docnames)
+
+def schedule_bulk_generate_irn(docnames):
+	failures = GSPConnector.bulk_generate_irn(docnames)
+	frappe.local.message_log = []
+
+	frappe.publish_realtime("bulk_einvoice_generation_complete", {
+		"user": frappe.session.user,
+		"failures": failures,
+		"invoices": docnames
+	})
+
+def show_bulk_action_failure_message(failures):
+	for doc in failures:
+		docname = '<a href="sales-invoice/{0}">{0}</a>'.format(doc.get('docname'))
+		message = doc.get('message').replace("'", '"')
+		if message[0] == '[':
+			errors = json.loads(message)
+			error_list = ''.join(['<li>{}</li>'.format(err) for err in errors])
+			message = '''{} has following errors:<br>
+				<ul style="padding-left: 20px; padding-top: 5px">{}</ul>'''.format(docname, error_list)
+		else:
+			message = '{} - {}'.format(docname, message)
+
+		frappe.msgprint(
+			message,
+			title=_('Bulk E-Invoice Generation Complete'),
+			indicator='red'
+		)
+
+@frappe.whitelist()
+def cancel_irns(docnames, reason, remark):
+	docnames = json.loads(docnames) or []
+
+	if len(docnames) < 10:
+		failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
+		frappe.local.message_log = []
+
+		if failures:
+			show_bulk_action_failure_message(failures)
+
+		success = len(docnames) - len(failures)
+		frappe.msgprint(
+			_('{} e-invoices cancelled successfully').format(success),
+			title=_('Bulk E-Invoice Cancellation Complete')
+		)
+	else:
+		enqueue_bulk_action(schedule_bulk_cancel_irn, docnames=docnames, reason=reason, remark=remark)
+
+def schedule_bulk_cancel_irn(docnames, reason, remark):
+	failures = GSPConnector.bulk_cancel_irn(docnames, reason, remark)
+	frappe.local.message_log = []
+
+	frappe.publish_realtime("bulk_einvoice_cancellation_complete", {
+		"user": frappe.session.user,
+		"failures": failures,
+		"invoices": docnames
+	})
+
+def enqueue_bulk_action(job, **kwargs):
+	check_scheduler_status()
+
+	enqueue(
+		job,
+		**kwargs,
+		queue="long",
+		timeout=10000,
+		event="processing_bulk_einvoice_action",
+		now=frappe.conf.developer_mode or frappe.flags.in_test,
+	)
+
+	if job == schedule_bulk_generate_irn:
+		msg = _('E-Invoices will be generated in a background process.')
+	else:
+		msg = _('E-Invoices will be cancelled in a background process.')
+
+	frappe.msgprint(msg, alert=1)
+
+def check_scheduler_status():
+	if is_scheduler_inactive() and not frappe.flags.in_test:
+		frappe.throw(_("Scheduler is inactive. Cannot enqueue job."), title=_("Scheduler Inactive"))
+
+def job_already_enqueued(job_name):
+	enqueued_jobs = [d.get("job_name") for d in get_info()]
+	if job_name in enqueued_jobs:
+		return True
\ No newline at end of file
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index ee49aae..ec58fd2 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -5,6 +5,7 @@
 
 import frappe, os, json
 from frappe.custom.doctype.custom_field.custom_field import create_custom_fields
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.permissions import add_permission, update_permission_property
 from erpnext.regional.india import states
 from erpnext.accounts.utils import get_fiscal_year, FiscalYearError
@@ -18,6 +19,7 @@
 # TODO: for all countries
 def setup_company_independent_fixtures():
 	make_custom_fields()
+	make_property_setters()
 	add_permissions()
 	add_custom_roles_for_reports()
 	frappe.enqueue('erpnext.regional.india.setup.add_hsn_sac_codes', now=frappe.flags.in_test)
@@ -49,7 +51,7 @@
 
 def add_custom_roles_for_reports():
 	for report_name in ('GST Sales Register', 'GST Purchase Register',
-		'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill'):
+		'GST Itemised Sales Register', 'GST Itemised Purchase Register', 'Eway Bill', 'E-Invoice Summary'):
 
 		if not frappe.db.get_value('Custom Role', dict(report=report_name)):
 			frappe.get_doc(dict(
@@ -110,6 +112,11 @@
 	frappe.db.set_value("Print Format", "GST Tax Invoice", "disabled", 0)
 	frappe.db.set_value("Print Format", "GST E-Invoice", "disabled", 0)
 
+def make_property_setters():
+	# GST rules do not allow for an invoice no. bigger than 16 characters
+	make_property_setter('Sales Invoice', 'naming_series', 'options', 'SINV-.YY.-\nSRET-.YY.-', '')
+	make_property_setter('Purchase Invoice', 'naming_series', 'options', 'PINV-.YY.-\nPRET-.YY.-', '')
+
 def make_custom_fields(update=True):
 	hsn_sac_field = dict(fieldname='gst_hsn_code', label='HSN/SAC',
 		fieldtype='Data', fetch_from='item_code.gst_hsn_code', insert_after='description',
@@ -120,6 +127,9 @@
 	is_non_gst = dict(fieldname='is_non_gst', label='Is Non GST',
 		fieldtype='Check', fetch_from='item_code.is_non_gst', insert_after='is_nil_exempt',
 		print_hide=1)
+	taxable_value = dict(fieldname='taxable_value', label='Taxable Value',
+		fieldtype='Currency', insert_after='base_net_amount', hidden=1, options="Company:company:default_currency",
+		print_hide=1)
 
 	purchase_invoice_gst_category = [
 		dict(fieldname='gst_section', label='GST Details', fieldtype='Section Break',
@@ -149,6 +159,13 @@
 			fetch_if_empty=1),
 	]
 
+	delivery_note_gst_category = [
+		dict(fieldname='gst_category', label='GST Category',
+			fieldtype='Select', insert_after='gst_vehicle_type', print_hide=1,
+			options='\nRegistered Regular\nRegistered Composition\nUnregistered\nSEZ\nOverseas\nConsumer\nDeemed Export\nUIN Holders',
+			fetch_from='customer.gst_category', fetch_if_empty=1),
+	]
+
 	invoice_gst_fields = [
 		dict(fieldname='invoice_copy', label='Invoice Copy',
 			fieldtype='Select', insert_after='export_type', print_hide=1, allow_on_submit=1,
@@ -273,7 +290,7 @@
 			'allow_on_submit': 1,
 			'insert_after': 'customer_name_in_arabic',
 			'translatable': 0,
-    	}
+		}
 	]
 
 	si_ewaybill_fields = [
@@ -401,21 +418,37 @@
 		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='einvoice_section', label='E-Invoice Fields', fieldtype='Section Break', insert_after='gst_vehicle_type',
+			print_hide=1, hidden=1),
+		
+		dict(fieldname='ack_no', label='Ack. No.', fieldtype='Data', read_only=1, hidden=1, insert_after='einvoice_section',
+			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='signed_qr_code', fieldtype='Code', options='JSON', hidden=1, no_copy=1, print_hide=1, read_only=1),
+		dict(fieldname='irn_cancel_date', label='Cancel Date', fieldtype='Data', read_only=1, hidden=1, insert_after='ack_date', 
+			no_copy=1, print_hide=1),
 
-		dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, no_copy=1, print_hide=1, read_only=1)
+		dict(fieldname='signed_einvoice', label='Signed E-Invoice', fieldtype='Code', options='JSON', hidden=1, insert_after='irn_cancel_date',
+			no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='signed_qr_code', label='Signed QRCode', fieldtype='Code', options='JSON', hidden=1, insert_after='signed_einvoice',
+			no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='qrcode_image', label='QRCode', fieldtype='Attach Image', hidden=1, insert_after='signed_qr_code',
+			no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='einvoice_status', label='E-Invoice Status', fieldtype='Select', insert_after='qrcode_image',
+			options='\nPending\nGenerated\nCancelled\nFailed', default=None, hidden=1, no_copy=1, print_hide=1, read_only=1),
+
+		dict(fieldname='failure_description', label='E-Invoice Failure Description', fieldtype='Code', options='JSON',
+			hidden=1, insert_after='einvoice_status', no_copy=1, print_hide=1, read_only=1)
 	]
 
 	custom_fields = {
@@ -431,7 +464,7 @@
 		'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 + si_einvoice_fields,
-		'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields,
+		'Delivery Note': sales_invoice_gst_fields + ewaybill_fields + sales_invoice_shipping_fields + delivery_note_gst_category,
 		'Sales Order': sales_invoice_gst_fields,
 		'Tax Category': inter_state_gst_field,
 		'Item': [
@@ -446,7 +479,7 @@
 		'Supplier Quotation Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Sales Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Delivery Note Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
-		'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
+		'Sales Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst, taxable_value],
 		'Purchase Order Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Purchase Receipt Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
 		'Purchase Invoice Item': [hsn_sac_field, nil_rated_exempt, is_non_gst],
@@ -860,4 +893,4 @@
 		})
 
 		rule.flags.ignore_mandatory = True
-		rule.save()
\ No newline at end of file
+		rule.save()
diff --git a/erpnext/regional/india/utils.py b/erpnext/regional/india/utils.py
index e24bd6c..7709ddf 100644
--- a/erpnext/regional/india/utils.py
+++ b/erpnext/regional/india/utils.py
@@ -2,7 +2,7 @@
 import frappe, re, json
 from frappe import _
 import erpnext
-from frappe.utils import cstr, flt, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
+from frappe.utils import cstr, flt, cint, date_diff, nowdate, round_based_on_smallest_currency_fraction, money_in_words, getdate
 from erpnext.regional.india import states, state_numbers
 from erpnext.controllers.taxes_and_totals import get_itemised_tax, get_itemised_taxable_amount
 from erpnext.controllers.accounts_controller import get_taxes_and_charges
@@ -41,24 +41,25 @@
 		return
 
 	if len(doc.gstin) != 15:
-		frappe.throw(_("Invalid GSTIN! A GSTIN must have 15 characters."))
+		frappe.throw(_("A GSTIN must have 15 characters."), title=_("Invalid GSTIN"))
 
 	if gst_category and gst_category == 'UIN Holders':
 		if not GSTIN_UIN_FORMAT.match(doc.gstin):
-			frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers"))
+			frappe.throw(_("The input you've entered doesn't match the GSTIN format for UIN Holders or Non-Resident OIDAR Service Providers"),
+				title=_("Invalid GSTIN"))
 	else:
 		if not GSTIN_FORMAT.match(doc.gstin):
-			frappe.throw(_("Invalid GSTIN! The input you've entered doesn't match the format of GSTIN."))
+			frappe.throw(_("The input you've entered doesn't match the format of GSTIN."), title=_("Invalid GSTIN"))
 
 		validate_gstin_check_digit(doc.gstin)
 		set_gst_state_and_state_number(doc)
 
 		if not doc.gst_state:
-			frappe.throw(_("Please Enter GST state"))
+			frappe.throw(_("Please enter GST state"), title=_("Invalid State"))
 
 		if doc.gst_state_number != doc.gstin[:2]:
-			frappe.throw(_("Invalid GSTIN! First 2 digits of GSTIN should match with State number {0}.")
-				.format(doc.gst_state_number))
+			frappe.throw(_("First 2 digits of GSTIN should match with State number {0}.")
+				.format(doc.gst_state_number), title=_("Invalid GSTIN"))
 
 def validate_pan_for_india(doc, method):
 	if doc.get('country') != 'India' or not doc.pan:
@@ -823,9 +824,57 @@
 		return
 
 	gst_accounts = get_gst_accounts(company)
-	gst_account_list = gst_accounts.get('cgst_account') + gst_accounts.get('sgst_account') \
-		+ gst_accounts.get('igst_account')
+
+	gst_account_list = []
+	for account in ['cgst_account', 'sgst_account', 'igst_account']:
+		if account in gst_accounts:
+			gst_account_list += gst_accounts.get(account)
 
 	account_list.extend(gst_account_list)
 
 	return account_list
+
+def update_taxable_values(doc, method):
+	country = frappe.get_cached_value('Company', doc.company, 'country')
+
+	if country != 'India':
+		return
+
+	gst_accounts = get_gst_accounts(doc.company)
+
+	# Only considering sgst account to avoid inflating taxable value
+	gst_account_list = gst_accounts.get('sgst_account', []) + gst_accounts.get('sgst_account', []) \
+		+ gst_accounts.get('igst_account', [])
+
+	additional_taxes = 0
+	total_charges = 0
+	item_count = 0
+	considered_rows = []
+
+	for tax in doc.get('taxes'):
+		prev_row_id = cint(tax.row_id) - 1
+		if tax.account_head in gst_account_list and prev_row_id not in considered_rows:
+			if tax.charge_type == 'On Previous Row Amount':
+				additional_taxes += doc.get('taxes')[prev_row_id].tax_amount_after_discount_amount
+				considered_rows.append(prev_row_id)
+			if tax.charge_type == 'On Previous Row Total':
+				additional_taxes += doc.get('taxes')[prev_row_id].base_total - doc.base_net_total
+				considered_rows.append(prev_row_id)
+
+	for item in doc.get('items'):
+		if doc.apply_discount_on == 'Grand Total' and doc.discount_amount:
+			proportionate_value = item.base_amount if doc.base_total else item.qty
+			total_value = doc.base_total if doc.base_total else doc.total_qty
+		else:
+			proportionate_value = item.base_net_amount if doc.base_net_total else item.qty
+			total_value = doc.base_net_total if doc.base_net_total else doc.total_qty
+
+		applicable_charges = flt(flt(proportionate_value * (flt(additional_taxes) / flt(total_value)),
+			item.precision('taxable_value')))
+		item.taxable_value = applicable_charges + proportionate_value
+		total_charges += applicable_charges
+		item_count += 1
+
+	if total_charges != additional_taxes:
+		diff = additional_taxes - total_charges
+		doc.get('items')[item_count - 1].taxable_value += diff
diff --git a/erpnext/regional/italy/sales_invoice.js b/erpnext/regional/italy/sales_invoice.js
index 586a529..b54ac53 100644
--- a/erpnext/regional/italy/sales_invoice.js
+++ b/erpnext/regional/italy/sales_invoice.js
@@ -11,15 +11,10 @@
 						callback: function(r) {
 							frm.reload_doc();
 							if(r.message) {
-								var w = window.open(
-									frappe.urllib.get_full_url(
-										"/api/method/erpnext.regional.italy.utils.download_e_invoice_file?"
-										+ "file_name=" + r.message
-									)
-								)
-								if (!w) {
-									frappe.msgprint(__("Please enable pop-ups")); return;
-								}
+								open_url_post(frappe.request.url, {
+									cmd: 'frappe.core.doctype.file.file.download_file',
+									file_url: r.message
+								});
 							}
 						}
 					});
diff --git a/erpnext/regional/italy/setup.py b/erpnext/regional/italy/setup.py
index 95b92e7..7db2f6b 100644
--- a/erpnext/regional/italy/setup.py
+++ b/erpnext/regional/italy/setup.py
@@ -128,11 +128,8 @@
 				fetch_from="company.vat_collectability"),
 			dict(fieldname='sb_e_invoicing_reference', label='E-Invoicing',
 				fieldtype='Section Break', insert_after='against_income_account', print_hide=1),
-			dict(fieldname='company_tax_id', label='Company Tax ID',
-				fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
-				fetch_from="company.tax_id"),
 			dict(fieldname='company_fiscal_code', label='Company Fiscal Code',
-				fieldtype='Data', insert_after='company_tax_id', print_hide=1, read_only=1,
+				fieldtype='Data', insert_after='sb_e_invoicing_reference', print_hide=1, read_only=1,
 				fetch_from="company.fiscal_code"),
 			dict(fieldname='company_fiscal_regime', label='Company Fiscal Regime',
 				fieldtype='Data', insert_after='company_fiscal_code', print_hide=1, read_only=1,
@@ -142,6 +139,9 @@
 			dict(fieldname='customer_fiscal_code', label='Customer Fiscal Code',
 				fieldtype='Data', insert_after='cb_e_invoicing_reference', read_only=1,
 				fetch_from="customer.fiscal_code"),
+			dict(fieldname='type_of_document', label='Type of Document',
+				fieldtype='Select', insert_after='customer_fiscal_code',
+				options='\nTD01\nTD02\nTD03\nTD04\nTD05\nTD06\nTD16\nTD17\nTD18\nTD19\nTD20\nTD21\nTD22\nTD23\nTD24\nTD25\nTD26\nTD27'),
 		],
 		'Purchase Invoice Item': invoice_item_fields,
 		'Sales Order Item': invoice_item_fields,
@@ -217,4 +217,4 @@
 	update_permission_property(doctype, 'Accounts Manager', 0, 'delete', 1)
 	add_permission(doctype, 'Accounts Manager', 1)
 	update_permission_property(doctype, 'Accounts Manager', 1, 'write', 1)
-	update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
\ No newline at end of file
+	update_permission_property(doctype, 'Accounts Manager', 1, 'create', 1)
diff --git a/erpnext/regional/italy/utils.py b/erpnext/regional/italy/utils.py
index 6842fb2..ba1aeaf 100644
--- a/erpnext/regional/italy/utils.py
+++ b/erpnext/regional/italy/utils.py
@@ -1,6 +1,8 @@
 from __future__ import unicode_literals
 
-import frappe, json, os
+import io
+import json
+import frappe
 from frappe.utils import flt, cstr
 from erpnext.controllers.taxes_and_totals import get_itemised_tax
 from frappe import _
@@ -28,20 +30,22 @@
 
 @frappe.whitelist()
 def export_invoices(filters=None):
-	saved_xmls = []
+	frappe.has_permission('Sales Invoice', throw=True)
 
-	invoices = frappe.get_all("Sales Invoice", filters=get_conditions(filters), fields=["*"])
+	invoices = frappe.get_all(
+		"Sales Invoice",
+		filters=get_conditions(filters),
+		fields=["name", "company_tax_id"]
+	)
 
-	for invoice in invoices:
-		attachments = get_e_invoice_attachments(invoice)
-		saved_xmls += [attachment.file_name for attachment in attachments]
+	attachments = get_e_invoice_attachments(invoices)
 
-	zip_filename = "{0}-einvoices.zip".format(frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
+	zip_filename = "{0}-einvoices.zip".format(
+		frappe.utils.get_datetime().strftime("%Y%m%d_%H%M%S"))
 
-	download_zip(saved_xmls, zip_filename)
+	download_zip(attachments, zip_filename)
 
 
-@frappe.whitelist()
 def prepare_invoice(invoice, progressive_number):
 	#set company information
 	company = frappe.get_doc("Company", invoice.company)
@@ -53,11 +57,12 @@
 	invoice.company_address_data = company_address
 
 	#Set invoice type
-	if invoice.is_return and invoice.return_against:
-		invoice.type_of_document = "TD04" #Credit Note (Nota di Credito)
-		invoice.return_against_unamended =  get_unamended_name(frappe.get_doc("Sales Invoice", invoice.return_against))
-	else:
-		invoice.type_of_document = "TD01" #Sales Invoice (Fattura)
+	if not invoice.type_of_document:
+		if invoice.is_return and invoice.return_against:
+			invoice.type_of_document = "TD04" #Credit Note (Nota di Credito)
+			invoice.return_against_unamended =  get_unamended_name(frappe.get_doc("Sales Invoice", invoice.return_against))
+		else:
+			invoice.type_of_document = "TD01" #Sales Invoice (Fattura)
 
 	#set customer information
 	invoice.customer_data = frappe.get_doc("Customer", invoice.customer)
@@ -98,7 +103,7 @@
 def get_conditions(filters):
 	filters = json.loads(filters)
 
-	conditions = {"docstatus": 1}
+	conditions = {"docstatus": 1, "company_tax_id": ("!=", "")}
 
 	if filters.get("company"): conditions["company"] = filters["company"]
 	if filters.get("customer"): conditions["customer"] =  filters["customer"]
@@ -111,23 +116,22 @@
 
 	return conditions
 
-#TODO: Use function from frappe once PR #6853 is merged.
+
 def download_zip(files, output_filename):
-	from zipfile import ZipFile
+	import zipfile
 
-	input_files = [frappe.get_site_path('private', 'files', filename) for filename in files]
-	output_path = frappe.get_site_path('private', 'files', output_filename)
+	zip_stream = io.BytesIO()
+	with zipfile.ZipFile(zip_stream, 'w', zipfile.ZIP_DEFLATED) as zip_file:
+		for file in files:
+			file_path = frappe.utils.get_files_path(
+				file.file_name, is_private=file.is_private)
 
-	with ZipFile(output_path, 'w') as output_zip:
-		for input_file in input_files:
-			output_zip.write(input_file, arcname=os.path.basename(input_file))
-
-	with open(output_path, 'rb') as fileobj:
-		filedata = fileobj.read()
+			zip_file.write(file_path, arcname=file.file_name)
 
 	frappe.local.response.filename = output_filename
-	frappe.local.response.filecontent = filedata
+	frappe.local.response.filecontent = zip_stream.getvalue()
 	frappe.local.response.type = "download"
+	zip_stream.close()
 
 def get_invoice_summary(items, taxes):
 	summary_data = frappe._dict()
@@ -307,23 +311,12 @@
 @frappe.whitelist()
 def generate_single_invoice(docname):
 	doc = frappe.get_doc("Sales Invoice", docname)
-
+	frappe.has_permission("Sales Invoice", doc=doc, throw=True)
 
 	e_invoice = prepare_and_attach_invoice(doc, True)
+	return e_invoice.file_url
 
-	return e_invoice.file_name
-
-@frappe.whitelist()
-def download_e_invoice_file(file_name):
-	content = None
-	with open(frappe.get_site_path('private', 'files', file_name), "r") as f:
-		content = f.read()
-
-	frappe.local.response.filename = file_name
-	frappe.local.response.filecontent = content
-	frappe.local.response.type = "download"
-
-#Delete e-invoice attachment on cancel.
+# Delete e-invoice attachment on cancel.
 def sales_invoice_on_cancel(doc, method):
 	if get_company_country(doc.company) not in ['Italy',
 		'Italia', 'Italian Republic', 'Repubblica Italiana']:
@@ -335,16 +328,38 @@
 def get_company_country(company):
 	return frappe.get_cached_value('Company', company, 'country')
 
-def get_e_invoice_attachments(invoice):
-	if not invoice.company_tax_id:
-		return []
+def get_e_invoice_attachments(invoices):
+	if not isinstance(invoices, list):
+		if not invoices.company_tax_id:
+			return
+
+		invoices = [invoices]
+
+	tax_id_map = {
+		invoice.name: (
+			invoice.company_tax_id
+			if invoice.company_tax_id.startswith("IT")
+			else "IT" + invoice.company_tax_id
+		) for invoice in invoices
+	}
+
+	attachments = frappe.get_all(
+		"File",
+		fields=("name", "file_name", "attached_to_name", "is_private"),
+		filters= {
+			"attached_to_name": ('in', tax_id_map),
+			"attached_to_doctype": 'Sales Invoice'
+		}
+	)
 
 	out = []
-	attachments = get_attachments(invoice.doctype, invoice.name)
-	company_tax_id = invoice.company_tax_id if invoice.company_tax_id.startswith("IT") else "IT" + invoice.company_tax_id
-
 	for attachment in attachments:
-		if attachment.file_name and attachment.file_name.startswith(company_tax_id) and attachment.file_name.endswith(".xml"):
+		if (
+			attachment.file_name
+			and attachment.file_name.endswith(".xml")
+			and attachment.file_name.startswith(
+				tax_id_map.get(attachment.attached_to_name))
+		):
 			out.append(attachment)
 
 	return out
diff --git a/erpnext/selling/doctype/lead_source/__init__.py b/erpnext/regional/report/e_invoice_summary/__init__.py
similarity index 100%
copy from erpnext/selling/doctype/lead_source/__init__.py
copy to erpnext/regional/report/e_invoice_summary/__init__.py
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
new file mode 100644
index 0000000..4713217
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.js
@@ -0,0 +1,55 @@
+// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+/* eslint-disable */
+
+frappe.query_reports["E-Invoice Summary"] = {
+	"filters": [
+		{
+			"fieldtype": "Link",
+			"options": "Company",
+			"reqd": 1,
+			"fieldname": "company",
+			"label": __("Company"),
+			"default": frappe.defaults.get_user_default("Company"),
+		},
+		{
+			"fieldtype": "Link",
+			"options": "Customer",
+			"fieldname": "customer",
+			"label": __("Customer")
+		},
+		{
+			"fieldtype": "Date",
+			"reqd": 1,
+			"fieldname": "from_date",
+			"label": __("From Date"),
+			"default": frappe.datetime.add_months(frappe.datetime.get_today(), -1),
+		},
+		{
+			"fieldtype": "Date",
+			"reqd": 1,
+			"fieldname": "to_date",
+			"label": __("To Date"),
+			"default": frappe.datetime.get_today(),
+		},
+		{
+			"fieldtype": "Select",
+			"fieldname": "status",
+			"label": __("Status"),
+			"options": "\nPending\nGenerated\nCancelled\nFailed"
+		}
+	],
+
+	"formatter": function (value, row, column, data, default_formatter) {
+		value = default_formatter(value, row, column, data);
+
+		if (column.fieldname == "einvoice_status" && value) {
+			if (value == 'Pending') value = `<span class="bold" style="color: var(--text-on-orange)">${value}</span>`;
+			else if (value == 'Generated') value = `<span class="bold" style="color: var(--text-on-green)">${value}</span>`;
+			else if (value == 'Cancelled') value = `<span class="bold" style="color: var(--text-on-red)">${value}</span>`;
+			else if (value == 'Failed') value = `<span class="bold"  style="color: var(--text-on-red)">${value}</span>`;
+		}
+
+		return value;
+	}
+};
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
new file mode 100644
index 0000000..4deb073
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.json
@@ -0,0 +1,28 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2021-03-12 11:23:37.312294",
+ "disable_prepared_report": 0,
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "json": "{}",
+ "letter_head": "Logo",
+ "modified": "2021-03-12 12:36:48.689413",
+ "modified_by": "Administrator",
+ "module": "Regional",
+ "name": "E-Invoice Summary",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Sales Invoice",
+ "report_name": "E-Invoice Summary",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "Administrator"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
new file mode 100644
index 0000000..47acf29
--- /dev/null
+++ b/erpnext/regional/report/e_invoice_summary/e_invoice_summary.py
@@ -0,0 +1,106 @@
+# Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from __future__ import unicode_literals
+import frappe
+from frappe import _
+
+def execute(filters=None):
+	validate_filters(filters)
+
+	columns = get_columns()
+	data = get_data(filters)
+
+	return columns, data
+
+def validate_filters(filters={}):
+	filters = frappe._dict(filters)
+
+	if not filters.company:
+		frappe.throw(_('{} is mandatory for generating E-Invoice Summary Report').format(_('Company')), title=_('Invalid Filter'))
+	if filters.company:
+		# validate if company has e-invoicing enabled
+		pass
+	if not filters.from_date or not filters.to_date:
+		frappe.throw(_('From Date & To Date is mandatory for generating E-Invoice Summary Report'), title=_('Invalid Filter'))
+	if filters.from_date > filters.to_date:
+		frappe.throw(_('From Date must be before To Date'), title=_('Invalid Filter'))
+
+def get_data(filters={}):
+	query_filters = {
+		'posting_date': ['between', [filters.from_date, filters.to_date]],
+		'einvoice_status': ['is', 'set'],
+		'company': filters.company
+	}
+	if filters.customer:
+		query_filters['customer'] = filters.customer
+	if filters.status:
+		query_filters['einvoice_status'] = filters.status
+
+	data = frappe.get_all(
+		'Sales Invoice',
+		filters=query_filters,
+		fields=[d.get('fieldname') for d in get_columns()]
+	)
+
+	return data
+
+def get_columns():
+	return [
+		{
+			"fieldtype": "Date",
+			"fieldname": "posting_date",
+			"label": _("Posting Date"),
+			"width": 0
+		},
+		{
+			"fieldtype": "Link", 
+			"fieldname": "name", 
+			"label": _("Sales Invoice"),
+			"options": "Sales Invoice",
+			"width": 140
+		},
+		{ 
+			"fieldtype": "Data", 
+			"fieldname": "einvoice_status", 
+			"label": _("Status"), 
+			"width": 100
+		},
+		{ 
+			"fieldtype": "Link",
+			"fieldname": "customer",
+			"options": "Customer",
+			"label": _("Customer")
+		},
+		{ 
+			"fieldtype": "Check",
+			"fieldname": "is_return",
+			"label": _("Is Return"),
+			"width": 85
+		},
+		{
+			"fieldtype": "Data", 
+			"fieldname": "ack_no", 
+			"label": "Ack. No.", 
+			"width": 145
+		},
+		{ 
+			"fieldtype": "Data", 
+			"fieldname": "ack_date", 
+			"label": "Ack. Date", 
+			"width": 165
+		},
+		{
+			"fieldtype": "Data", 
+			"fieldname": "irn", 
+			"label": _("IRN No."),
+			"width": 250
+		},
+		{
+			"fieldtype": "Currency",
+			"options": "Company:company:default_currency", 
+			"fieldname": "base_grand_total", 
+			"label": _("Grand Total"),
+			"width": 120
+		}
+	]
\ No newline at end of file
diff --git a/erpnext/regional/report/gstr_1/gstr_1.py b/erpnext/regional/report/gstr_1/gstr_1.py
index 09b04ff..62faa30 100644
--- a/erpnext/regional/report/gstr_1/gstr_1.py
+++ b/erpnext/regional/report/gstr_1/gstr_1.py
@@ -78,7 +78,7 @@
 				place_of_supply = invoice_details.get("place_of_supply")
 				ecommerce_gstin =  invoice_details.get("ecommerce_gstin")
 
-				b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin, inv),{
+				b2cs_output.setdefault((rate, place_of_supply, ecommerce_gstin),{
 					"place_of_supply": "",
 					"ecommerce_gstin": "",
 					"rate": "",
@@ -90,7 +90,7 @@
 					"invoice_value": invoice_details.get("base_grand_total"),
 				})
 
-				row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin, inv))
+				row = b2cs_output.get((rate, place_of_supply, ecommerce_gstin))
 				row["place_of_supply"] = place_of_supply
 				row["ecommerce_gstin"] = ecommerce_gstin
 				row["rate"] = rate
diff --git a/erpnext/regional/report/gstr_2/gstr_2.py b/erpnext/regional/report/gstr_2/gstr_2.py
index f899349..616c2b8 100644
--- a/erpnext/regional/report/gstr_2/gstr_2.py
+++ b/erpnext/regional/report/gstr_2/gstr_2.py
@@ -44,7 +44,7 @@
 		for inv, items_based_on_rate in self.items_based_on_tax_rate.items():
 			invoice_details = self.invoices.get(inv)
 			for rate, items in items_based_on_rate.items():
-				if rate:
+				if rate or invoice_details.get('gst_category') == 'Registered Composition':
 					if inv not in self.igst_invoices:
 						rate = rate / 2
 						row, taxable_value = self.get_row_data_for_invoice(inv, invoice_details, rate, items)
@@ -86,7 +86,7 @@
 					conditions += opts[1]
 
 		if self.filters.get("type_of_business") ==  "B2B":
-			conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ') and is_return != 1 "
+			conditions += "and ifnull(gst_category, '') in ('Registered Regular', 'Deemed Export', 'SEZ', 'Registered Composition') and is_return != 1 "
 
 		elif self.filters.get("type_of_business") ==  "CDNR":
 			conditions += """ and is_return = 1 """
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index c452594..96b3fa4 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -230,13 +230,20 @@
 			frappe.db.set(self, "customer_name", newdn)
 
 	def set_loyalty_program(self):
-		if self.loyalty_program: return
+		if self.loyalty_program:
+			return
+
 		loyalty_program = get_loyalty_programs(self)
-		if not loyalty_program: return
+		if not loyalty_program:
+			return
+
 		if len(loyalty_program) == 1:
 			self.loyalty_program = loyalty_program[0]
 		else:
-			frappe.msgprint(_("Multiple Loyalty Program found for the Customer. Please select manually."))
+			frappe.msgprint(
+				_("Multiple Loyalty Programs found for Customer {}. Please select manually.")
+				.format(frappe.bold(self.customer_name))
+			)
 
 	def create_onboarding_docs(self, args):
 		defaults = frappe.defaults.get_defaults()
@@ -340,7 +347,6 @@
 @frappe.whitelist()
 def get_loyalty_programs(doc):
 	''' returns applicable loyalty programs for a customer '''
-	from frappe.desk.treeview import get_children
 
 	lp_details = []
 	loyalty_programs = frappe.get_all("Loyalty Program",
@@ -349,15 +355,33 @@
 			"ifnull(to_date, '2500-01-01')": [">=", today()]})
 
 	for loyalty_program in loyalty_programs:
-		customer_groups = [d.value for d in get_children("Customer Group", loyalty_program.customer_group)] + [loyalty_program.customer_group]
-		customer_territories = [d.value for d in get_children("Territory", loyalty_program.customer_territory)] + [loyalty_program.customer_territory]
-
-		if (not loyalty_program.customer_group or doc.customer_group in customer_groups)\
-			and (not loyalty_program.customer_territory or doc.territory in customer_territories):
+		if (
+			(not loyalty_program.customer_group
+			or doc.customer_group in get_nested_links(
+				"Customer Group",
+				loyalty_program.customer_group,
+				doc.flags.ignore_permissions
+			))
+			and (not loyalty_program.customer_territory
+			or doc.territory in get_nested_links(
+				"Territory",
+				loyalty_program.customer_territory,
+				doc.flags.ignore_permissions
+			))
+		):
 			lp_details.append(loyalty_program.name)
 
 	return lp_details
 
+def get_nested_links(link_doctype, link_name, ignore_permissions=False):
+	from frappe.desk.treeview import _get_children
+
+	links = [link_name]
+	for d in _get_children(link_doctype, link_name, ignore_permissions):
+		links.append(d.value)
+
+	return links
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
@@ -572,4 +596,4 @@
 		""", {
 			'customer': customer,
 			'txt': '%%%s%%' % txt
-		})
\ No newline at end of file
+		})
diff --git a/erpnext/selling/doctype/lead_source/lead_source.js b/erpnext/selling/doctype/lead_source/lead_source.js
deleted file mode 100644
index 6af6a4f..0000000
--- a/erpnext/selling/doctype/lead_source/lead_source.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Lead Source', {
-	refresh: function(frm) {
-
-	}
-});
diff --git a/erpnext/selling/doctype/lead_source/lead_source.json b/erpnext/selling/doctype/lead_source/lead_source.json
deleted file mode 100644
index 373e83a..0000000
--- a/erpnext/selling/doctype/lead_source/lead_source.json
+++ /dev/null
@@ -1,131 +0,0 @@
-{
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 1, 
- "autoname": "field:source_name", 
- "beta": 0, 
- "creation": "2016-09-16 01:47:47.382372", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "fields": [
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "source_name", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Source Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }, 
-  {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "details", 
-   "fieldtype": "Text Editor", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 0, 
-   "label": "Details", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "unique": 0
-  }
- ], 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
-
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 0, 
- "max_attachments": 0, 
- "modified": "2020-09-16 02:03:01.441622", 
- "modified_by": "Administrator", 
- "module": "Selling", 
- "name": "Lead Source", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 1, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Sales Manager", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }, 
-  {
-   "amend": 0, 
-   "apply_user_permissions": 0, 
-   "cancel": 0, 
-   "create": 1, 
-   "delete": 0, 
-   "email": 1, 
-   "export": 1, 
-   "if_owner": 0, 
-   "import": 0, 
-   "permlevel": 0, 
-   "print": 1, 
-   "read": 1, 
-   "report": 1, 
-   "role": "Sales User", 
-   "set_user_permissions": 0, 
-   "share": 1, 
-   "submit": 0, 
-   "write": 1
-  }
- ], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_seen": 0
-}
diff --git a/erpnext/selling/doctype/lead_source/test_lead_source.py b/erpnext/selling/doctype/lead_source/test_lead_source.py
deleted file mode 100644
index 42df18f..0000000
--- a/erpnext/selling/doctype/lead_source/test_lead_source.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-from __future__ import unicode_literals
-
-import frappe
-import unittest
-
-# test_records = frappe.get_test_records('Lead Source')
-
-class TestLeadSource(unittest.TestCase):
-	pass
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 5da248c..246f923 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -64,6 +64,7 @@
 		opp = frappe.get_doc("Opportunity", opportunity)
 		opp.set_status(status=status, update=True)
 
+	@frappe.whitelist()
 	def declare_enquiry_lost(self, lost_reasons_list, detailed_reason=None):
 		if not self.has_sales_order():
 			get_lost_reasons = frappe.get_list('Quotation Lost Reason',
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index e561291..d9e52e1 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -150,7 +150,7 @@
 		if enq:
 			frappe.db.sql("update `tabOpportunity` set status = %s where name=%s",(flag,enq[0][0]))
 
-	def update_prevdoc_status(self, flag):
+	def update_prevdoc_status(self, flag=None):
 		for quotation in list(set([d.prevdoc_docname for d in self.get("items")])):
 			if quotation:
 				doc = frappe.get_doc("Quotation", quotation)
@@ -372,6 +372,7 @@
 			self.indicator_color = "green"
 			self.indicator_title = _("Paid")
 
+	@frappe.whitelist()
 	def get_work_order_items(self, for_raw_material_request=0):
 		'''Returns items with BOM that already do not have a linked work order'''
 		items = []
@@ -778,6 +779,7 @@
 
 @frappe.whitelist()
 def make_purchase_order_for_default_supplier(source_name, selected_items=None, target_doc=None):
+	"""Creates Purchase Order for each Supplier. Returns a list of doc objects."""
 	if not selected_items: return
 
 	if isinstance(selected_items, string_types):
@@ -820,15 +822,16 @@
 		target.stock_qty = (flt(source.stock_qty) - flt(source.ordered_qty))
 		target.project = source_parent.project
 
-	suppliers = [item.get('supplier') for item in selected_items if item.get('supplier') and item.get('supplier')]
-	suppliers = list(set(suppliers))
+	suppliers = [item.get('supplier') for item in selected_items if item.get('supplier')]
+	suppliers = list(dict.fromkeys(suppliers)) # remove duplicates while preserving order
 
-	items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code') and item.get('item_code')]
+	items_to_map = [item.get('item_code') for item in selected_items if item.get('item_code')]
 	items_to_map = list(set(items_to_map))
 
 	if not suppliers:
 		frappe.throw(_("Please set a Supplier against the Items to be considered in the Purchase Order."))
 
+	purchase_orders = []
 	for supplier in suppliers:
 		doc = get_mapped_doc("Sales Order", source_name, {
 			"Sales Order": {
@@ -872,7 +875,9 @@
 
 		doc.insert()
 		frappe.db.commit()
-		return doc
+		purchase_orders.append(doc)
+
+	return purchase_orders
 
 @frappe.whitelist()
 def make_purchase_order(source_name, selected_items=None, target_doc=None):
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index ee16f44..3137621 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1,11 +1,12 @@
 # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 # License: GNU General Public License v3. See license.txt
 from __future__ import unicode_literals
-import frappe
 import json
-from frappe.utils import flt, add_days, nowdate
-import frappe.permissions
 import unittest
+import frappe
+import frappe.permissions
+from frappe.utils import flt, add_days, nowdate
+from frappe.core.doctype.user_permission.test_user_permission import create_user
 from erpnext.selling.doctype.sales_order.sales_order \
 	import make_material_request, make_delivery_note, make_sales_invoice, WarehouseRequired
 from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
@@ -340,6 +341,9 @@
 		prev_total = so.get("base_total")
 		prev_total_in_words = so.get("base_in_words")
 
+		# get reserved qty before update items
+		reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
+
 		first_item_of_so = so.get("items")[0]
 		trans_item = json.dumps([
 			{'item_code' : first_item_of_so.item_code, 'rate' : first_item_of_so.rate, \
@@ -353,6 +357,10 @@
 		self.assertEqual(so.get("items")[-1].rate, 200)
 		self.assertEqual(so.get("items")[-1].qty, 7)
 		self.assertEqual(so.get("items")[-1].amount, 1400)
+
+		# reserved qty should increase after adding row
+		self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 7)
+
 		self.assertEqual(so.status, 'To Deliver and Bill')
 
 		updated_total = so.get("base_total")
@@ -372,6 +380,9 @@
 		create_dn_against_so(so.name, 2)
 		make_sales_invoice(so.name)
 
+		# get reserved qty before update items
+		reserved_qty_for_second_item = get_reserved_qty("_Test Item 2")
+
 		# add an item so as to try removing items
 		trans_item = json.dumps([
 			{"item_code": '_Test Item', "qty": 5, "rate":1000, "docname": so.get("items")[0].name},
@@ -381,6 +392,9 @@
 		so.reload()
 		self.assertEqual(len(so.get("items")), 2)
 
+		# reserved qty should increase after adding row
+		self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item + 2)
+
 		# check if delivered items can be removed
 		trans_item = json.dumps([{
 			"item_code": '_Test Item 2',
@@ -401,6 +415,10 @@
 
 		so.reload()
 		self.assertEqual(len(so.get("items")), 1)
+
+		# reserved qty should decrease (back to initial) after deleting row
+		self.assertEqual(get_reserved_qty('_Test Item 2'), reserved_qty_for_second_item)
+
 		self.assertEqual(so.status, 'To Deliver and Bill')
 
 
@@ -444,10 +462,8 @@
 	def test_update_child_perm(self):
 		so = make_sales_order(item_code= "_Test Item", qty=4)
 
-		user = 'test@example.com'
-		test_user = frappe.get_doc('User', user)
-		test_user.add_roles("Accounts User")
-		frappe.set_user(user)
+		test_user = create_user("test_so_child_perms@example.com", "Accounts User")
+		frappe.set_user(test_user.name)
 
 		# update qty
 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 200, 'qty' : 7, 'docname': so.items[0].name}])
@@ -456,18 +472,14 @@
 		# add new item
 		trans_item = json.dumps([{'item_code' : '_Test Item', 'rate' : 100, 'qty' : 2}])
 		self.assertRaises(frappe.ValidationError, update_child_qty_rate,'Sales Order', trans_item, so.name)
-		test_user.remove_roles("Accounts User")
-		frappe.set_user("Administrator")
 
 	def test_update_child_qty_rate_with_workflow(self):
 		from frappe.model.workflow import apply_workflow
 
-		frappe.set_user("Administrator")
 		workflow = make_sales_order_workflow()
 		so = make_sales_order(item_code= "_Test Item", qty=1, rate=150, do_not_submit=1)
 		apply_workflow(so, 'Approve')
 
-		frappe.set_user("Administrator")
 		user = 'test@example.com'
 		test_user = frappe.get_doc('User', user)
 		test_user.add_roles("Sales User", "Test Junior Approver")
@@ -508,12 +520,18 @@
 
 		so = make_sales_order(item_code = "_Test Item", warehouse=None)
 
+		# get reserved qty of packed item
+		existing_reserved_qty = get_reserved_qty("_Packed Item")
+
 		added_item = json.dumps([{"item_code" : "_Product Bundle Item", "rate" : 200, 'qty' : 2}])
 		update_child_qty_rate('Sales Order', added_item, so.name)
 
 		so.reload()
 		self.assertEqual(so.packed_items[0].qty, 4)
 
+		# reserved qty in packed item should increase after adding bundle item
+		self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 4)
+
 		# test uom and conversion factor change
 		update_uom_conv_factor = json.dumps([{
 			'item_code': so.get("items")[0].item_code,
@@ -528,6 +546,9 @@
 		so.reload()
 		self.assertEqual(so.packed_items[0].qty, 8)
 
+		# reserved qty in packed item should increase after changing bundle item uom
+		self.assertEqual(get_reserved_qty("_Packed Item"), existing_reserved_qty + 8)
+
 	def test_update_child_with_tax_template(self):
 		"""
 			Test Action: Create a SO with one item having its tax account head already in the SO.
@@ -618,33 +639,31 @@
 		frappe.db.set_value("Stock Settings", None, "default_warehouse", old_stock_settings_value)
 
 	def test_warehouse_user(self):
-		frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
-		frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
-		frappe.permissions.add_user_permission("Company", "_Test Company 1", "test2@example.com")
-
-		test_user = frappe.get_doc("User", "test@example.com")
-		test_user.add_roles("Sales User", "Stock User")
-		test_user.remove_roles("Sales Manager")
+		test_user = create_user("test_so_warehouse_user@example.com", "Sales User", "Stock User")
 
 		test_user_2 = frappe.get_doc("User", "test2@example.com")
 		test_user_2.add_roles("Sales User", "Stock User")
 		test_user_2.remove_roles("Sales Manager")
 
-		frappe.set_user("test@example.com")
+		frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name)
+		frappe.permissions.add_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name)
+		frappe.permissions.add_user_permission("Company", "_Test Company 1", test_user_2.name)
 
-		so = make_sales_order(company="_Test Company 1",
+		frappe.set_user(test_user.name)
+
+		so = make_sales_order(company="_Test Company 1", customer="_Test Customer 1",
 			warehouse="_Test Warehouse 2 - _TC1", do_not_save=True)
 		so.conversion_rate = 0.02
 		so.plc_conversion_rate = 0.02
 		self.assertRaises(frappe.PermissionError, so.insert)
 
-		frappe.set_user("test2@example.com")
+		frappe.set_user(test_user_2.name)
 		so.insert()
 
 		frappe.set_user("Administrator")
-		frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", "test@example.com")
-		frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", "test2@example.com")
-		frappe.permissions.remove_user_permission("Company", "_Test Company 1", "test2@example.com")
+		frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 1 - _TC", test_user.name)
+		frappe.permissions.remove_user_permission("Warehouse", "_Test Warehouse 2 - _TC1", test_user_2.name)
+		frappe.permissions.remove_user_permission("Company", "_Test Company 1", test_user_2.name)
 
 	def test_block_delivery_note_against_cancelled_sales_order(self):
 		so = make_sales_order()
@@ -743,7 +762,7 @@
 		so = make_sales_order(item_list=so_items, do_not_submit=True)
 		so.submit()
 
-		po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+		po = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
 		po.submit()
 
 		dn = create_dn_against_so(so.name, delivered_qty=2)
@@ -825,7 +844,7 @@
 		so.submit()
 
 		# create po for only one item
-		po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])
+		po1 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[0]])[0]
 		po1.submit()
 
 		self.assertEqual(so.customer, po1.customer)
@@ -835,7 +854,7 @@
 		self.assertEqual(len(po1.items), 1)
 
 		# create po for remaining item
-		po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])
+		po2 = make_purchase_order_for_default_supplier(so.name, selected_items=[so_items[1]])[0]
 		po2.submit()
 
 		# teardown
@@ -846,6 +865,45 @@
 		so.load_from_db()
 		so.cancel()
 
+	def test_drop_shipping_full_for_default_suppliers(self):
+		"""Test if multiple POs are generated in one go against different default suppliers."""
+		from erpnext.selling.doctype.sales_order.sales_order import make_purchase_order_for_default_supplier
+
+		if not frappe.db.exists("Item", "_Test Item for Drop Shipping 1"):
+			make_item("_Test Item for Drop Shipping 1", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+		if not frappe.db.exists("Item", "_Test Item for Drop Shipping 2"):
+			make_item("_Test Item for Drop Shipping 2", {"is_stock_item": 1, "delivered_by_supplier": 1})
+
+		so_items = [
+			{
+				"item_code": "_Test Item for Drop Shipping 1",
+				"warehouse": "",
+				"qty": 2,
+				"rate": 400,
+				"delivered_by_supplier": 1,
+				"supplier": '_Test Supplier'
+			},
+			{
+				"item_code": "_Test Item for Drop Shipping 2",
+				"warehouse": "",
+				"qty": 2,
+				"rate": 400,
+				"delivered_by_supplier": 1,
+				"supplier": '_Test Supplier 1'
+			}
+		]
+
+		# create so and po
+		so = make_sales_order(item_list=so_items, do_not_submit=True)
+		so.submit()
+
+		purchase_orders = make_purchase_order_for_default_supplier(so.name, selected_items=so_items)
+
+		self.assertEqual(len(purchase_orders), 2)
+		self.assertEqual(purchase_orders[0].supplier, '_Test Supplier')
+		self.assertEqual(purchase_orders[1].supplier, '_Test Supplier 1')
+
 	def test_reserved_qty_for_closing_so(self):
 		bin = frappe.get_all("Bin", filters={"item_code": "_Test Item", "warehouse": "_Test Warehouse - _TC"},
 			fields=["reserved_qty"])
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index 278821e..8adf5bf 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -279,11 +279,6 @@
 					const item_row = frappe.model.get_doc(cdt, cdn);
 					if (item_row && item_row[fieldname] != value) {
 
-						if (fieldname === 'qty' && flt(value) == 0) {
-							this.remove_item_from_cart();
-							return;
-						}
-
 						const { item_code, batch_no, uom } = this.item_details.current_item;
 						const event = {
 							field: fieldname,
@@ -397,6 +392,7 @@
 					this.recent_order_list.toggle_component(false);
 					frappe.run_serially([
 						() => this.frm.refresh(name),
+						() => this.frm.call('reset_mode_of_payments'),
 						() => this.cart.load_invoice(),
 						() => this.item_selector.toggle_component(true)
 					]);
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index be2b769..b10a9e3 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -64,10 +64,7 @@
 				{fieldname: 'print', fieldtype: 'Data', label: 'Print Preview'}
 			],
 			primary_action: () => {
-				const frm = this.events.get_frm();
-				frm.doc = this.doc;
-				frm.print_preview.lang_code = frm.doc.language;
-				frm.print_preview.printit(true);
+				this.print_receipt();
 			},
 			primary_action_label: __('Print'),
 		});
@@ -192,13 +189,21 @@
 		});
 
 		this.$summary_container.on('click', '.print-btn', () => {
-			const frm = this.events.get_frm();
-			frm.doc = this.doc;
-			frm.print_preview.lang_code = frm.doc.language;
-			frm.print_preview.printit(true);
+			this.print_receipt();
 		});
 	}
 
+	print_receipt() {
+		const frm = this.events.get_frm();
+		frappe.utils.print(
+			frm.doctype,
+			frm.docname,
+			frm.pos_print_format,
+			frm.doc.letter_head,
+			frm.doc.language || frappe.boot.lang
+		);
+	}
+
 	attach_shortcuts() {
 		const ctrl_label = frappe.utils.is_mac() ? '⌘' : 'Ctrl';
 		this.$summary_container.find('.print-btn').attr("title", `${ctrl_label}+P`);
diff --git a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
index f396705..6fb7666 100644
--- a/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
+++ b/erpnext/selling/report/customer_credit_balance/customer_credit_balance.py
@@ -57,18 +57,18 @@
 	return columns
 
 def get_details(filters):
-	conditions = ""
 
+	sql_query = """SELECT
+						c.name, c.customer_name,
+						ccl.bypass_credit_limit_check,
+						c.is_frozen, c.disabled
+					FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl
+					WHERE
+						c.name = ccl.parent
+						AND ccl.company = %(company)s"""
+
+	# customer filter is optional.
 	if filters.get("customer"):
-		conditions += " AND c.name = '" + filters.get("customer") + "'"
+		sql_query += " AND c.name = %(customer)s"
 
-	return frappe.db.sql("""SELECT
-			c.name, c.customer_name,
-			ccl.bypass_credit_limit_check,
-			c.is_frozen, c.disabled
-		FROM `tabCustomer` c, `tabCustomer Credit Limit` ccl
-		WHERE
-			c.name = ccl.parent
-			AND ccl.company = '{0}'
-			{1}
-	""".format( filters.get("company"),conditions), as_dict=1) #nosec
+	return frappe.db.sql(sql_query, filters, as_dict=1)
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index c041d26..c2b5e4f 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -259,6 +259,7 @@
 		["default_payroll_payable_account", {"root_type": "Liability"}],
 		["round_off_account", {"root_type": "Expense"}],
 		["write_off_account", {"root_type": "Expense"}],
+		["default_discount_account", {}],
 		["discount_allowed_account", {"root_type": "Expense"}],
 		["discount_received_account", {"root_type": "Income"}],
 		["exchange_gain_loss_account", {"root_type": "Expense"}],
@@ -275,7 +276,7 @@
 		["expenses_included_in_asset_valuation", {"account_type": "Expenses Included In Asset Valuation"}],
 		["capital_work_in_progress_account", {"account_type": "Capital Work in Progress"}],
 		["asset_received_but_not_billed", {"account_type": "Asset Received But Not Billed"}],
-		["unrealized_profit_loss_account", {"root_type": "Liability"}]
+		["unrealized_profit_loss_account", {"root_type": "Liability"},]
 	], function(i, v) {
 		erpnext.company.set_custom_query(frm, v);
 	});
diff --git a/erpnext/setup/doctype/company/company.json b/erpnext/setup/doctype/company/company.json
index 56f60df..83cbf47 100644
--- a/erpnext/setup/doctype/company/company.json
+++ b/erpnext/setup/doctype/company/company.json
@@ -59,6 +59,7 @@
   "default_deferred_expense_account",
   "default_payroll_payable_account",
   "default_expense_claim_payable_account",
+  "default_discount_account",
   "section_break_22",
   "cost_center",
   "column_break_26",
@@ -733,6 +734,12 @@
    "fieldtype": "Link",
    "label": "Unrealized Profit / Loss Account",
    "options": "Account"
+  },
+  {
+   "fieldname": "default_discount_account",
+   "fieldtype": "Link",
+   "label": "Default Payment Discount Account",
+   "options": "Account"
   }
  ],
  "icon": "fa fa-building",
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 433851c..0922171 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -66,6 +66,7 @@
 		if frappe.db.sql("select abbr from tabCompany where name!=%s and abbr=%s", (self.name, self.abbr)):
 			frappe.throw(_("Abbreviation already used for another company"))
 
+	@frappe.whitelist()
 	def create_default_tax_template(self):
 		from erpnext.setup.setup_wizard.operations.taxes_setup import create_sales_tax
 		create_sales_tax({
diff --git a/erpnext/setup/doctype/company/delete_company_transactions.py b/erpnext/setup/doctype/company/delete_company_transactions.py
index 0df4c87..933ed3c 100644
--- a/erpnext/setup/doctype/company/delete_company_transactions.py
+++ b/erpnext/setup/doctype/company/delete_company_transactions.py
@@ -27,7 +27,7 @@
 		if doctype not in ("Account", "Cost Center", "Warehouse", "Budget",
 			"Party Account", "Employee", "Sales Taxes and Charges Template",
 			"Purchase Taxes and Charges Template", "POS Profile", "BOM",
-			"Company", "Bank Account", "Item Tax Template", "Mode Of Payment",
+			"Company", "Bank Account", "Item Tax Template", "Mode Of Payment", "Mode of Payment Account",
 			"Item Default", "Customer", "Supplier", "GST Account"):
 				delete_for_doctype(doctype, company_name)
 
diff --git a/erpnext/setup/doctype/email_digest/email_digest.py b/erpnext/setup/doctype/email_digest/email_digest.py
index cbb4c7c..ac55fdf 100644
--- a/erpnext/setup/doctype/email_digest/email_digest.py
+++ b/erpnext/setup/doctype/email_digest/email_digest.py
@@ -24,6 +24,7 @@
 		self._accounts = {}
 		self.currency = frappe.db.get_value('Company',  self.company,  "default_currency")
 
+	@frappe.whitelist()
 	def get_users(self):
 		"""get list of users"""
 		user_list = frappe.db.sql("""
@@ -41,6 +42,7 @@
 
 		frappe.response['user_list'] = user_list
 
+	@frappe.whitelist()
 	def send(self):
 		# send email only to enabled users
 		valid_users = [p[0] for p in frappe.db.sql("""select name from `tabUser`
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.js b/erpnext/setup/doctype/global_defaults/global_defaults.js
index 552331a..942dd59 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.js
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.js
@@ -17,7 +17,7 @@
 			method: "frappe.client.get_list",
 			args: {
 				doctype: "UOM Conversion Factor",
-				filters: { "category": "Length" },
+				filters: { "category": __("Length") },
 				fields: ["to_uom"],
 				limit_page_length: 500
 			},
diff --git a/erpnext/setup/doctype/global_defaults/global_defaults.py b/erpnext/setup/doctype/global_defaults/global_defaults.py
index fa7bc50..76a8450 100644
--- a/erpnext/setup/doctype/global_defaults/global_defaults.py
+++ b/erpnext/setup/doctype/global_defaults/global_defaults.py
@@ -50,6 +50,7 @@
 		# clear cache
 		frappe.clear_cache()
 
+	@frappe.whitelist()
 	def get_defaults(self):
 		return frappe.defaults.get_defaults()
 
diff --git a/erpnext/setup/doctype/item_group/item_group.js b/erpnext/setup/doctype/item_group/item_group.js
index 1413cb2..885d874 100644
--- a/erpnext/setup/doctype/item_group/item_group.js
+++ b/erpnext/setup/doctype/item_group/item_group.js
@@ -61,7 +61,7 @@
 				frappe.set_route("List", "Item", {"item_group": frm.doc.name});
 			});
 		}
-		
+
 		frappe.model.with_doctype('Item', () => {
 			const item_meta = frappe.get_meta('Item');
 
@@ -69,10 +69,12 @@
 				df => ['Link', 'Table MultiSelect'].includes(df.fieldtype) && !df.hidden
 			).map(df => ({ label: df.label, value: df.fieldname }));
 
-			const field = frappe.meta.get_docfield("Website Filter Field", "fieldname", frm.docname);
-			field.fieldtype = 'Select';
-			field.options = valid_fields;
-			frm.fields_dict.filter_fields.grid.refresh();
+			frm.fields_dict.filter_fields.grid.update_docfield_property(
+				'fieldname', 'fieldtype', 'Select'
+			);
+			frm.fields_dict.filter_fields.grid.update_docfield_property(
+				'fieldname', 'options', valid_fields
+			);
 		});
 	},
 
diff --git a/erpnext/setup/doctype/naming_series/naming_series.py b/erpnext/setup/doctype/naming_series/naming_series.py
index abff973..c4f1de1 100644
--- a/erpnext/setup/doctype/naming_series/naming_series.py
+++ b/erpnext/setup/doctype/naming_series/naming_series.py
@@ -10,10 +10,12 @@
 from frappe.model.document import Document
 from frappe.model.naming import parse_naming_series
 from frappe.permissions import get_doctypes_with_read
+from frappe.core.doctype.doctype.doctype import validate_series
 
 class NamingSeriesNotSetError(frappe.ValidationError): pass
 
 class NamingSeries(Document):
+	@frappe.whitelist()
 	def get_transactions(self, arg=None):
 		doctypes = list(set(frappe.db.sql_list("""select parent
 				from `tabDocField` df where fieldname='naming_series'""")
@@ -52,6 +54,7 @@
 		options = list(filter(lambda x: x, [cstr(n).strip() for n in ol]))
 		return options
 
+	@frappe.whitelist()
 	def update_series(self, arg=None):
 		"""update series list"""
 		self.validate_series_set()
@@ -126,7 +129,7 @@
 		dt = frappe.get_doc("DocType", self.select_doc_for_series)
 		options = self.scrub_options_list(self.set_options.split("\n"))
 		for series in options:
-			dt.validate_series(series)
+			validate_series(dt, series)
 			for i in sr:
 				if i[0]:
 					existing_series = [d.split('.')[0] for d in i[0].split("\n")]
@@ -138,10 +141,12 @@
 		if not re.match("^[\w\- /.#{}]*$", n, re.UNICODE):
 			throw(_('Special Characters except "-", "#", ".", "/", "{" and "}" not allowed in naming series'))
 
+	@frappe.whitelist()
 	def get_options(self, arg=None):
 		if frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series"):
 			return frappe.get_meta(arg or self.select_doc_for_series).get_field("naming_series").options
 
+	@frappe.whitelist()
 	def get_current(self, arg=None):
 		"""get series current"""
 		if self.prefix:
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 1e424dd..82f191d 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -142,13 +142,15 @@
 		}
 	]
 
-	current_nabvar_items = navbar_settings.help_dropdown
+	current_navbar_items = navbar_settings.help_dropdown
 	navbar_settings.set('help_dropdown', [])
 
 	for item in erpnext_navbar_items:
-		navbar_settings.append('help_dropdown', item)
+		current_labels = [item.get('item_label') for item in current_navbar_items]
+		if not item.get('item_label') in current_labels:
+			navbar_settings.append('help_dropdown', item)
 
-	for item in current_nabvar_items:
+	for item in current_navbar_items:
 		navbar_settings.append('help_dropdown', {
 			'item_label': item.item_label,
 			'item_type': item.item_type,
diff --git a/erpnext/setup/workspace/home/home.json b/erpnext/setup/workspace/home/home.json
index 69ca7cf..305456b 100644
--- a/erpnext/setup/workspace/home/home.json
+++ b/erpnext/setup/workspace/home/home.json
@@ -10,13 +10,14 @@
  "hide_custom": 0,
  "icon": "getting-started",
  "idx": 0,
+ "is_default": 0,
  "is_standard": 1,
  "label": "Home",
  "links": [
   {
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Healthcare",
+   "label": "Accounting",
    "onboard": 0,
    "type": "Card Break"
   },
@@ -24,8 +25,8 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Patient",
-   "link_to": "Patient",
+   "label": "Chart of Accounts",
+   "link_to": "Account",
    "link_type": "DocType",
    "onboard": 1,
    "type": "Link"
@@ -34,25 +35,8 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Diagnosis",
-   "link_to": "Diagnosis",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Agriculture",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Crop",
-   "link_to": "Crop",
+   "label": "Company",
+   "link_to": "Company",
    "link_type": "DocType",
    "onboard": 1,
    "type": "Link"
@@ -61,8 +45,8 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Crop Cycle",
-   "link_to": "Crop Cycle",
+   "label": "Customer",
+   "link_to": "Customer",
    "link_type": "DocType",
    "onboard": 1,
    "type": "Link"
@@ -71,112 +55,8 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Location",
-   "link_to": "Location",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Fertilizer",
-   "link_to": "Fertilizer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Education",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Student",
-   "link_to": "Student",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Course",
-   "link_to": "Course",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Instructor",
-   "link_to": "Instructor",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Room",
-   "link_to": "Room",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Non Profit",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Member",
-   "link_to": "Member",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Volunteer",
-   "link_to": "Volunteer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Chapter",
-   "link_to": "Chapter",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Donor",
-   "link_to": "Donor",
+   "label": "Supplier",
+   "link_to": "Supplier",
    "link_type": "DocType",
    "onboard": 1,
    "type": "Link"
@@ -192,6 +72,16 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
+   "label": "Item",
+   "link_to": "Item",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
    "label": "Warehouse",
    "link_to": "Warehouse",
    "link_type": "DocType",
@@ -305,73 +195,6 @@
   {
    "hidden": 0,
    "is_query_report": 0,
-   "label": "Accounting",
-   "onboard": 0,
-   "type": "Card Break"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Item",
-   "link_to": "Item",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Customer",
-   "link_to": "Customer",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Supplier",
-   "link_to": "Supplier",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Company",
-   "link_to": "Company",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Chart of Accounts",
-   "link_to": "Account",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "dependencies": "",
-   "hidden": 0,
-   "is_query_report": 0,
-   "label": "Opening Invoice Creation Tool",
-   "link_to": "Opening Invoice Creation Tool",
-   "link_type": "DocType",
-   "onboard": 1,
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
    "label": "Data Import and Settings",
    "onboard": 0,
    "type": "Card Break"
@@ -390,6 +213,16 @@
    "dependencies": "",
    "hidden": 0,
    "is_query_report": 0,
+   "label": "Opening Invoice Creation Tool",
+   "link_to": "Opening Invoice Creation Tool",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
    "label": "Chart of Accounts Importer",
    "link_to": "Chart of Accounts Importer",
    "link_type": "DocType",
@@ -415,9 +248,177 @@
    "link_type": "DocType",
    "onboard": 1,
    "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Healthcare",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Patient",
+   "link_to": "Patient",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Diagnosis",
+   "link_to": "Diagnosis",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Education",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Student",
+   "link_to": "Student",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Instructor",
+   "link_to": "Instructor",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Course",
+   "link_to": "Course",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Room",
+   "link_to": "Room",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Non Profit",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Donor",
+   "link_to": "Donor",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Member",
+   "link_to": "Member",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Volunteer",
+   "link_to": "Volunteer",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Chapter",
+   "link_to": "Chapter",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Agriculture",
+   "onboard": 0,
+   "type": "Card Break"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Location",
+   "link_to": "Location",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Crop",
+   "link_to": "Crop",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Crop Cycle",
+   "link_to": "Crop Cycle",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
+  },
+  {
+   "dependencies": "",
+   "hidden": 0,
+   "is_query_report": 0,
+   "label": "Fertilizer",
+   "link_to": "Fertilizer",
+   "link_type": "DocType",
+   "onboard": 1,
+   "type": "Link"
   }
  ],
- "modified": "2021-01-01 12:13:16.055668",
+ "modified": "2021-03-16 15:59:58.416154",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Home",
diff --git a/erpnext/shopping_cart/test_shopping_cart.py b/erpnext/shopping_cart/test_shopping_cart.py
index cf59a52..d857bf5 100644
--- a/erpnext/shopping_cart/test_shopping_cart.py
+++ b/erpnext/shopping_cart/test_shopping_cart.py
@@ -16,6 +16,11 @@
 		Note:
 		Shopping Cart == Quotation
 	"""
+
+	@classmethod
+	def tearDownClass(cls):
+		frappe.db.sql("delete from `tabTax Rule`")
+
 	def setUp(self):
 		frappe.set_user("Administrator")
 		create_test_contact_and_address()
@@ -51,8 +56,8 @@
 	def test_add_to_cart(self):
 		self.login_as_customer()
 
-		# remove from cart
-		self.remove_all_items_from_cart()
+		# clear existing quotations
+		self.clear_existing_quotations()
 
 		# add first item
 		update_cart("_Test Item", 1)
@@ -100,6 +105,7 @@
 		self.assertEqual(len(quotation.get("items")), 1)
 
 	def test_tax_rule(self):
+		self.create_tax_rule()
 		self.login_as_customer()
 		quotation = self.create_quotation()
 
@@ -115,6 +121,13 @@
 
 		self.remove_test_quotation(quotation)
 
+	def create_tax_rule(self):
+		tax_rule = frappe.get_test_records("Tax Rule")[0]
+		try:
+			frappe.get_doc(tax_rule).insert()
+		except frappe.DuplicateEntryError:
+			pass
+
 	def create_quotation(self):
 		quotation = frappe.new_doc("Quotation")
 
@@ -195,10 +208,15 @@
 			"_Test Contact For _Test Customer")
 		frappe.set_user("test_contact_customer@example.com")
 
-	def remove_all_items_from_cart(self):
-		quotation = _get_cart_quotation()
-		quotation.flags.ignore_permissions=True
-		quotation.delete()
+	def clear_existing_quotations(self):
+		quotations = frappe.get_all("Quotation", filters={
+			"party_name": get_party().name,
+			"order_type": "Shopping Cart",
+			"docstatus": 0
+		}, order_by="modified desc", pluck="name")
+
+		for quotation in quotations:
+			frappe.delete_doc("Quotation", quotation, ignore_permissions=True, force=True)
 
 	def create_user_if_not_exists(self, email, first_name = None):
 		if frappe.db.exists("User", email):
diff --git a/erpnext/stock/dashboard/item_dashboard.js b/erpnext/stock/dashboard/item_dashboard.js
index 95cb92b..933ca8a 100644
--- a/erpnext/stock/dashboard/item_dashboard.js
+++ b/erpnext/stock/dashboard/item_dashboard.js
@@ -1,14 +1,14 @@
 frappe.provide('erpnext.stock');
 
 erpnext.stock.ItemDashboard = Class.extend({
-	init: function(opts) {
+	init: function (opts) {
 		$.extend(this, opts);
 		this.make();
 	},
-	make: function() {
+	make: function () {
 		var me = this;
 		this.start = 0;
-		if(!this.sort_by) {
+		if (!this.sort_by) {
 			this.sort_by = 'projected_qty';
 			this.sort_order = 'asc';
 		}
@@ -16,22 +16,25 @@
 		this.content = $(frappe.render_template('item_dashboard')).appendTo(this.parent);
 		this.result = this.content.find('.result');
 
-		this.content.on('click', '.btn-move', function() {
-			handle_move_add($(this), "Move")
+		this.content.on('click', '.btn-move', function () {
+			handle_move_add($(this), "Move");
 		});
 
-		this.content.on('click', '.btn-add', function() {
-			handle_move_add($(this), "Add")
+		this.content.on('click', '.btn-add', function () {
+			handle_move_add($(this), "Add");
 		});
 
-		this.content.on('click', '.btn-edit', function() {
+		this.content.on('click', '.btn-edit', function () {
 			let item = unescape($(this).attr('data-item'));
 			let warehouse = unescape($(this).attr('data-warehouse'));
 			let company = unescape($(this).attr('data-company'));
-			frappe.db.get_value('Putaway Rule',
-				{'item_code': item, 'warehouse': warehouse, 'company': company}, 'name', (r) => {
-					frappe.set_route("Form", "Putaway Rule", r.name);
-				});
+			frappe.db.get_value('Putaway Rule', {
+				'item_code': item,
+				'warehouse': warehouse,
+				'company': company
+			}, 'name', (r) => {
+				frappe.set_route("Form", "Putaway Rule", r.name);
+			});
 		});
 
 		function handle_move_add(element, action) {
@@ -39,23 +42,26 @@
 			let warehouse = unescape(element.attr('data-warehouse'));
 			let actual_qty = unescape(element.attr('data-actual_qty'));
 			let disable_quick_entry = Number(unescape(element.attr('data-disable_quick_entry')));
-			let entry_type = action === "Move" ? "Material Transfer": null;
+			let entry_type = action === "Move" ? "Material Transfer" : null;
 
 			if (disable_quick_entry) {
 				open_stock_entry(item, warehouse, entry_type);
 			} else {
 				if (action === "Add") {
 					let rate = unescape($(this).attr('data-rate'));
-					erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function() { me.refresh(); });
-				}
-				else {
-					erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function() { me.refresh(); });
+					erpnext.stock.move_item(item, null, warehouse, actual_qty, rate, function () {
+						me.refresh();
+					});
+				} else {
+					erpnext.stock.move_item(item, warehouse, null, actual_qty, null, function () {
+						me.refresh();
+					});
 				}
 			}
 		}
 
 		function open_stock_entry(item, warehouse, entry_type) {
-			frappe.model.with_doctype('Stock Entry', function() {
+			frappe.model.with_doctype('Stock Entry', function () {
 				var doc = frappe.model.get_new_doc('Stock Entry');
 				if (entry_type) doc.stock_entry_type = entry_type;
 
@@ -64,18 +70,18 @@
 				row.s_warehouse = warehouse;
 
 				frappe.set_route('Form', doc.doctype, doc.name);
-			})
+			});
 		}
 
 		// more
-		this.content.find('.btn-more').on('click', function() {
+		this.content.find('.btn-more').on('click', function () {
 			me.start += me.page_length;
 			me.refresh();
 		});
 
 	},
-	refresh: function() {
-		if(this.before_refresh) {
+	refresh: function () {
+		if (this.before_refresh) {
 			this.before_refresh();
 		}
 
@@ -94,13 +100,13 @@
 		frappe.call({
 			method: this.method,
 			args: args,
-			callback: function(r) {
+			callback: function (r) {
 				me.render(r.message);
 			}
 		});
 	},
-	render: function(data) {
-		if (this.start===0) {
+	render: function (data) {
+		if (this.start === 0) {
 			this.max_count = 0;
 			this.result.empty();
 		}
@@ -115,7 +121,7 @@
 		this.max_count = this.max_count;
 
 		// show more button
-		if (data && data.length===(this.page_length + 1)) {
+		if (data && data.length === (this.page_length + 1)) {
 			this.content.find('.more').removeClass('hidden');
 
 			// remove the last element
@@ -137,15 +143,15 @@
 		}
 	},
 
-	get_item_dashboard_data: function(data, max_count, show_item) {
-		if(!max_count) max_count = 0;
-		if(!data) data = [];
+	get_item_dashboard_data: function (data, max_count, show_item) {
+		if (!max_count) max_count = 0;
+		if (!data) data = [];
 
-		data.forEach(function(d) {
+		data.forEach(function (d) {
 			d.actual_or_pending = d.projected_qty + d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
 			d.pending_qty = 0;
 			d.total_reserved = d.reserved_qty + d.reserved_qty_for_production + d.reserved_qty_for_sub_contract;
-			if(d.actual_or_pending > d.actual_qty) {
+			if (d.actual_or_pending > d.actual_qty) {
 				d.pending_qty = d.actual_or_pending - d.actual_qty;
 			}
 
@@ -161,16 +167,16 @@
 		return {
 			data: data,
 			max_count: max_count,
-			can_write:can_write,
+			can_write: can_write,
 			show_item: show_item || false
 		};
 	},
 
-	get_capacity_dashboard_data: function(data) {
+	get_capacity_dashboard_data: function (data) {
 		if (!data) data = [];
 
-		data.forEach(function(d) {
-			d.color =  d.percent_occupied >=80 ? "#f8814f" : "#2490ef";
+		data.forEach(function (d) {
+			d.color = d.percent_occupied >= 80 ? "#f8814f" : "#2490ef";
 		});
 
 		let can_write = 0;
@@ -185,53 +191,77 @@
 	}
 });
 
-erpnext.stock.move_item = function(item, source, target, actual_qty, rate, callback) {
+erpnext.stock.move_item = function (item, source, target, actual_qty, rate, callback) {
 	var dialog = new frappe.ui.Dialog({
 		title: target ? __('Add Item') : __('Move Item'),
-		fields: [
-			{fieldname: 'item_code', label: __('Item'),
-				fieldtype: 'Link', options: 'Item', read_only: 1},
-			{fieldname: 'source', label: __('Source Warehouse'),
-				fieldtype: 'Link', options: 'Warehouse', read_only: 1},
-			{fieldname: 'target', label: __('Target Warehouse'),
-				fieldtype: 'Link', options: 'Warehouse', reqd: 1},
-			{fieldname: 'qty', label: __('Quantity'), reqd: 1,
-				fieldtype: 'Float', description: __('Available {0}', [actual_qty]) },
-			{fieldname: 'rate', label: __('Rate'), fieldtype: 'Currency', hidden: 1 },
+		fields: [{
+			fieldname: 'item_code',
+			label: __('Item'),
+			fieldtype: 'Link',
+			options: 'Item',
+			read_only: 1
+		},
+		{
+			fieldname: 'source',
+			label: __('Source Warehouse'),
+			fieldtype: 'Link',
+			options: 'Warehouse',
+			read_only: 1
+		},
+		{
+			fieldname: 'target',
+			label: __('Target Warehouse'),
+			fieldtype: 'Link',
+			options: 'Warehouse',
+			reqd: 1
+		},
+		{
+			fieldname: 'qty',
+			label: __('Quantity'),
+			reqd: 1,
+			fieldtype: 'Float',
+			description: __('Available {0}', [actual_qty])
+		},
+		{
+			fieldname: 'rate',
+			label: __('Rate'),
+			fieldtype: 'Currency',
+			hidden: 1
+		},
 		],
-	})
+	});
 	dialog.show();
 	dialog.get_field('item_code').set_input(item);
 
-	if(source) {
+	if (source) {
 		dialog.get_field('source').set_input(source);
 	} else {
 		dialog.get_field('source').df.hidden = 1;
 		dialog.get_field('source').refresh();
 	}
 
-	if(rate) {
+	if (rate) {
 		dialog.get_field('rate').set_value(rate);
 		dialog.get_field('rate').df.hidden = 0;
 		dialog.get_field('rate').refresh();
 	}
 
-	if(target) {
+	if (target) {
 		dialog.get_field('target').df.read_only = 1;
 		dialog.get_field('target').value = target;
 		dialog.get_field('target').refresh();
 	}
 
-	dialog.set_primary_action(__('Submit'), function() {
+	dialog.set_primary_action(__('Submit'), function () {
 		var values = dialog.get_values();
-		if(!values) {
+		if (!values) {
 			return;
 		}
-		if(source && values.qty > actual_qty) {
+		if (source && values.qty > actual_qty) {
 			frappe.msgprint(__('Quantity must be less than or equal to {0}', [actual_qty]));
 			return;
 		}
-		if(values.source === values.target) {
+		if (values.source === values.target) {
 			frappe.msgprint(__('Source and target warehouse must be different'));
 		}
 
@@ -239,21 +269,21 @@
 			method: 'erpnext.stock.doctype.stock_entry.stock_entry_utils.make_stock_entry',
 			args: values,
 			freeze: true,
-			callback: function(r) {
+			callback: function (r) {
 				frappe.show_alert(__('Stock Entry {0} created',
-					['<a href="/app/stock-entry/'+r.message.name+'">' + r.message.name+ '</a>']));
+					['<a href="/app/stock-entry/' + r.message.name + '">' + r.message.name + '</a>']));
 				dialog.hide();
 				callback(r);
 			},
 		});
 	});
 
-	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">'
-		+ __("Add more items or open full form") + '</a></p>')
+	$('<p style="margin-left: 10px;"><a class="link-open text-muted small">' +
+			__("Add more items or open full form") + '</a></p>')
 		.appendTo(dialog.body)
 		.find('.link-open')
-		.on('click', function() {
-			frappe.model.with_doctype('Stock Entry', function() {
+		.on('click', function () {
+			frappe.model.with_doctype('Stock Entry', function () {
 				var doc = frappe.model.get_new_doc('Stock Entry');
 				doc.from_warehouse = dialog.get_value('source');
 				doc.to_warehouse = dialog.get_value('target');
@@ -266,6 +296,6 @@
 				row.transfer_qty = dialog.get_value('qty');
 				row.basic_rate = dialog.get_value('rate');
 				frappe.set_route('Form', doc.doctype, doc.name);
-			})
+			});
 		});
-}
+};
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index cafb5c3..45e6628 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -2,6 +2,7 @@
 
 import frappe
 from frappe.model.db_query import DatabaseQuery
+from frappe.utils import flt, cint
 
 @frappe.whitelist()
 def get_data(item_code=None, warehouse=None, item_group=None,
@@ -42,11 +43,20 @@
 		limit_start=start,
 		limit_page_length='21')
 
+	precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
+
 	for item in items:
 		item.update({
-			'item_name': frappe.get_cached_value("Item", item.item_code, 'item_name'),
-			'disable_quick_entry': frappe.get_cached_value("Item", item.item_code, 'has_batch_no')
-				or frappe.get_cached_value("Item", item.item_code, 'has_serial_no'),
+			'item_name': frappe.get_cached_value(
+				"Item", item.item_code, 'item_name'),
+			'disable_quick_entry': frappe.get_cached_value(
+				"Item", item.item_code, 'has_batch_no')
+			or frappe.get_cached_value(
+				"Item", item.item_code, 'has_serial_no'),
+			'projected_qty': flt(item.projected_qty, precision),
+			'reserved_qty': flt(item.reserved_qty, precision),
+			'reserved_qty_for_production': flt(item.reserved_qty_for_production, precision),
+			'reserved_qty_for_sub_contract': flt(item.reserved_qty_for_sub_contract, precision),
+			'actual_qty': flt(item.actual_qty, precision),
 		})
-
 	return items
diff --git a/erpnext/stock/doctype/bin/bin.json b/erpnext/stock/doctype/bin/bin.json
index 04d624e..8e79f0e 100644
--- a/erpnext/stock/doctype/bin/bin.json
+++ b/erpnext/stock/doctype/bin/bin.json
@@ -1,4 +1,5 @@
 {
+ "actions": [],
  "autoname": "MAT-BIN-.YYYY.-.#####",
  "creation": "2013-01-10 16:34:25",
  "doctype": "DocType",
@@ -112,7 +113,8 @@
   {
    "fieldname": "reserved_qty_for_sub_contract",
    "fieldtype": "Float",
-   "label": "Reserved Qty for sub contract"
+   "label": "Reserved Qty for sub contract",
+   "read_only": 1
   },
   {
    "fieldname": "ma_rate",
@@ -166,7 +168,8 @@
  "hide_toolbar": 1,
  "idx": 1,
  "in_create": 1,
- "modified": "2019-11-18 18:34:59.456882",
+ "links": [],
+ "modified": "2021-03-30 23:09:39.572776",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Bin",
@@ -196,5 +199,6 @@
  ],
  "quick_entry": 1,
  "search_fields": "item_code,warehouse",
+ "sort_field": "modified",
  "sort_order": "ASC"
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 3544390..d326a04 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -101,7 +101,7 @@
 			for f in fieldname:
 				toggle_print_hide(self.meta if key == "parent" else item_meta, f)
 
-		super(DeliveryNote, self).before_print()
+		super(DeliveryNote, self).before_print(settings)
 
 	def set_actual_qty(self):
 		for d in self.get('items'):
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip.py b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
index 28e9533..de85bc3 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip.py
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip.py
@@ -90,6 +90,7 @@
 		delivery_notes = [get_link_to_form("Delivery Note", note) for note in delivery_notes]
 		frappe.msgprint(_("Delivery Notes {0} updated").format(", ".join(delivery_notes)))
 
+	@frappe.whitelist()
 	def process_route(self, optimize):
 		"""
 		Estimate the arrival times for each stop in the Delivery Trip.
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 33a8fe7..6fed9ef 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -1054,6 +1054,7 @@
    "read_only": 1
   },
   {
+   "depends_on": "eval: doc.show_in_website || doc.show_variant_in_website",
    "fieldname": "website_image_alt",
    "fieldtype": "Data",
    "label": "Image Description"
@@ -1066,7 +1067,7 @@
  "index_web_pages_for_search": 1,
  "links": [],
  "max_attachments": 1,
- "modified": "2021-03-15 13:41:04.108932",
+ "modified": "2021-03-18 14:04:38.575519",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",
@@ -1137,4 +1138,4 @@
  "sort_order": "DESC",
  "title_field": "item_name",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index 7b7d2da..7cb84a6 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -50,6 +50,7 @@
 		self.set_onload('stock_exists', self.stock_ledger_created())
 		self.set_asset_naming_series()
 
+	@frappe.whitelist()
 	def set_asset_naming_series(self):
 		if not hasattr(self, '_asset_naming_series'):
 			from erpnext.assets.doctype.asset.asset import get_asset_naming_series
@@ -706,6 +707,7 @@
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", existing_allow_negative_stock)
 		frappe.db.auto_commit_on_many_writes = 0
 
+	@frappe.whitelist()
 	def copy_specification_from_item_group(self):
 		self.set("website_specifications", [])
 		if self.item_group:
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 36d0de1..e0b89d8 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -494,7 +494,8 @@
 
 test_records = frappe.get_test_records('Item')
 
-def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None, customer=None, is_purchase_item=None, opening_stock=None):
+def create_item(item_code, is_stock_item=None, valuation_rate=0, warehouse=None, is_customer_provided_item=None,
+	customer=None, is_purchase_item=None, opening_stock=None, company=None):
 	if not frappe.db.exists("Item", item_code):
 		item = frappe.new_doc("Item")
 		item.item_code = item_code
@@ -509,7 +510,7 @@
 		item.customer = customer or ''
 		item.append("item_defaults", {
 			"default_warehouse": warehouse or '_Test Warehouse - _TC',
-			"company": "_Test Company"
+			"company": company or "_Test Company"
 		})
 		item.save()
 	else:
diff --git a/erpnext/stock/doctype/item/test_records.json b/erpnext/stock/doctype/item/test_records.json
index 909c4ee..6cec852 100644
--- a/erpnext/stock/doctype/item/test_records.json
+++ b/erpnext/stock/doctype/item/test_records.json
@@ -12,6 +12,7 @@
   "item_name": "_Test Item",
   "apply_warehouse_wise_reorder_level": 1,
   "gst_hsn_code": "999800",
+  "opening_stock": 10,
   "valuation_rate": 100,
   "item_defaults": [{
     "company": "_Test Company",
@@ -58,6 +59,8 @@
   "show_in_website": 1,
   "website_warehouse": "_Test Warehouse - _TC",
   "gst_hsn_code": "999800",
+  "opening_stock": 10,
+  "valuation_rate": 100,
   "item_defaults": [{
     "company": "_Test Company",
     "default_warehouse": "_Test Warehouse - _TC",
diff --git a/erpnext/stock/doctype/item_attribute/test_records.json b/erpnext/stock/doctype/item_attribute/test_records.json
index d346979..6aa6ffd 100644
--- a/erpnext/stock/doctype/item_attribute/test_records.json
+++ b/erpnext/stock/doctype/item_attribute/test_records.json
@@ -4,10 +4,12 @@
 		"attribute_name": "Test Size",
 		"priority": 1,
 		"item_attribute_values": [
+			{"attribute_value": "Extra Small", "abbr": "XSL"},
 			{"attribute_value": "Small", "abbr": "S"},
 			{"attribute_value": "Medium", "abbr": "M"},
 			{"attribute_value": "Large", "abbr": "L"},
-			{"attribute_value": "Extra Small", "abbr": "XSL"}
+			{"attribute_value": "Extra Large", "abbr": "XL"},
+			{"attribute_value": "2XL", "abbr": "2XL"}
 		]
 	},
 	{
diff --git a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
index 24f7e31..e8fb347 100644
--- a/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
+++ b/erpnext/stock/doctype/item_variant_settings/item_variant_settings.js
@@ -15,8 +15,9 @@
 				}
 			});
 
-			const child = frappe.meta.get_docfield("Variant Field", "field_name", frm.doc.name);
-			child.options = allow_fields;
+			frm.fields_dict.fields.grid.update_docfield_property(
+				'field_name', 'options', allow_fields
+			);
 		});
 	}
 });
diff --git a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
index 69a8bf1..8310946 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/landed_cost_voucher.py
@@ -12,6 +12,7 @@
 from erpnext.controllers.taxes_and_totals import init_landed_taxes_and_totals
 
 class LandedCostVoucher(Document):
+	@frappe.whitelist()
 	def get_items_from_purchase_receipts(self):
 		self.set("items", [])
 		for pr in self.get("purchase_receipts"):
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 527b0d3..7dfc5da 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -354,6 +354,10 @@
 	},
 	material_request_type: function(frm) {
 		frm.toggle_reqd('customer', frm.doc.material_request_type=="Customer Provided");
+
+		if (frm.doc.material_request_type !== 'Material Transfer' && frm.doc.set_from_warehouse) {
+			frm.set_value('set_from_warehouse', '');
+		}
 	},
 
 });
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index d73349d..8d7b238 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -20,9 +20,9 @@
   "company",
   "amended_from",
   "warehouse_section",
-  "set_warehouse",
-  "column_break5",
   "set_from_warehouse",
+  "column_break5",
+  "set_warehouse",
   "items_section",
   "scan_barcode",
   "items",
@@ -314,7 +314,7 @@
  "idx": 70,
  "is_submittable": 1,
  "links": [],
- "modified": "2020-09-19 01:04:09.285862",
+ "modified": "2021-03-31 23:52:55.392512",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Material Request",
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.js b/erpnext/stock/doctype/packing_slip/packing_slip.js
index bd14e5f..40d4685 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.js
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.js
@@ -110,19 +110,4 @@
 	refresh_many(['net_weight_pkg', 'net_weight_uom', 'gross_weight_uom', 'gross_weight_pkg']);
 }
 
-var make_row = function(title,val,bold){
-	var bstart = '<b>'; var bend = '</b>';
-	return '<tr><td class="datalabelcell">'+(bold?bstart:'')+title+(bold?bend:'')+'</td>'
-	+'<td class="datainputcell" style="text-align:left;">'+ val +'</td>'
-	+'</tr>'
-}
-
-cur_frm.pformat.net_weight_pkg= function(doc){
-	return '<table style="width:100%">' + make_row('Net Weight', doc.net_weight_pkg) + '</table>'
-}
-
-cur_frm.pformat.gross_weight_pkg= function(doc){
-	return '<table style="width:100%">' + make_row('Gross Weight', doc.gross_weight_pkg) + '</table>'
-}
-
 // TODO: validate gross weight field
diff --git a/erpnext/stock/doctype/packing_slip/packing_slip.py b/erpnext/stock/doctype/packing_slip/packing_slip.py
index a7a29cc..2008bff 100644
--- a/erpnext/stock/doctype/packing_slip/packing_slip.py
+++ b/erpnext/stock/doctype/packing_slip/packing_slip.py
@@ -152,6 +152,7 @@
 
 		return cint(recommended_case_no[0][0]) + 1
 
+	@frappe.whitelist()
 	def get_items(self):
 		self.set("items", [])
 
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 0da57b7..6ab68e2 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -25,14 +25,15 @@
 			if not frappe.get_cached_value('Item', item.item_code, 'has_serial_no'):
 				continue
 			if not item.serial_no:
-				frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}".format(
-					frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse))),
+				frappe.throw(_("Row #{0}: {1} does not have any available serial numbers in {2}").format(
+					frappe.bold(item.idx), frappe.bold(item.item_code), frappe.bold(item.warehouse)),
 					title=_("Serial Nos Required"))
 			if len(item.serial_no.split('\n')) == item.picked_qty:
 				continue
 			frappe.throw(_('For item {0} at row {1}, count of serial numbers does not match with the picked quantity')
 				.format(frappe.bold(item.item_code), frappe.bold(item.idx)), title=_("Quantity Mismatch"))
 
+	@frappe.whitelist()
 	def set_item_locations(self, save=False):
 		items = self.aggregate_item_qty()
 		self.item_location_map = frappe._dict()
@@ -345,7 +346,7 @@
 
 		if dn_item:
 			dn_item.warehouse = location.warehouse
-			dn_item.qty = location.picked_qty
+			dn_item.qty = flt(location.picked_qty) / (flt(location.conversion_factor) or 1)
 			dn_item.batch_no = location.batch_no
 			dn_item.serial_no = location.serial_no
 
@@ -378,9 +379,8 @@
 	else:
 		stock_entry = update_stock_entry_items_with_no_reference(pick_list, stock_entry)
 
-	stock_entry.set_incoming_rate()
 	stock_entry.set_actual_qty()
-	stock_entry.calculate_rate_and_amount(update_finished_item_rate=False)
+	stock_entry.calculate_rate_and_amount()
 
 	return stock_entry.as_dict()
 
diff --git a/erpnext/stock/doctype/pick_list/test_pick_list.py b/erpnext/stock/doctype/pick_list/test_pick_list.py
index 8ea7f89d..c4da05a 100644
--- a/erpnext/stock/doctype/pick_list/test_pick_list.py
+++ b/erpnext/stock/doctype/pick_list/test_pick_list.py
@@ -9,6 +9,7 @@
 
 from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 from erpnext.stock.doctype.item.test_item import create_item
+from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
 from erpnext.stock.doctype.stock_reconciliation.stock_reconciliation \
 		import EmptyStockReconciliationItemsError
 
@@ -22,7 +23,7 @@
 				'purpose': 'Opening Stock',
 				'expense_account': 'Temporary Opening - _TC',
 				'items': [{
-					'item_code': '_Test Item Home Desktop 100',
+					'item_code': '_Test Item',
 					'warehouse': '_Test Warehouse - _TC',
 					'valuation_rate': 100,
 					'qty': 5
@@ -37,7 +38,7 @@
 			'customer': '_Test Customer',
 			'items_based_on': 'Sales Order',
 			'locations': [{
-				'item_code': '_Test Item Home Desktop 100',
+				'item_code': '_Test Item',
 				'qty': 5,
 				'stock_qty': 5,
 				'conversion_factor': 1,
@@ -47,7 +48,7 @@
 		})
 		pick_list.set_item_locations()
 
-		self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
+		self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
 		self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
 		self.assertEqual(pick_list.locations[0].qty, 5)
 
@@ -237,7 +238,7 @@
 				'purpose': 'Opening Stock',
 				'expense_account': 'Temporary Opening - _TC',
 				'items': [{
-					'item_code': '_Test Item Home Desktop 100',
+					'item_code': '_Test Item',
 					'warehouse': '_Test Warehouse - _TC',
 					'valuation_rate': 100,
 					'qty': 10
@@ -251,7 +252,7 @@
 				'customer': '_Test Customer',
 				'company': '_Test Company',
 				'items': [{
-					'item_code': '_Test Item Home Desktop 100',
+					'item_code': '_Test Item',
 					'qty': 10,
 					'delivery_date': frappe.utils.today()
 				}],
@@ -264,14 +265,14 @@
 			'customer': '_Test Customer',
 			'items_based_on': 'Sales Order',
 			'locations': [{
-				'item_code': '_Test Item Home Desktop 100',
+				'item_code': '_Test Item',
 				'qty': 5,
 				'stock_qty': 5,
 				'conversion_factor': 1,
 				'sales_order': '_T-Sales Order-1',
 				'sales_order_item': '_T-Sales Order-1_item',
 			}, {
-				'item_code': '_Test Item Home Desktop 100',
+				'item_code': '_Test Item',
 				'qty': 5,
 				'stock_qty': 5,
 				'conversion_factor': 1,
@@ -281,16 +282,71 @@
 		})
 		pick_list.set_item_locations()
 
-		self.assertEqual(pick_list.locations[0].item_code, '_Test Item Home Desktop 100')
+		self.assertEqual(pick_list.locations[0].item_code, '_Test Item')
 		self.assertEqual(pick_list.locations[0].warehouse, '_Test Warehouse - _TC')
 		self.assertEqual(pick_list.locations[0].qty, 5)
 		self.assertEqual(pick_list.locations[0].sales_order_item, '_T-Sales Order-1_item')
 
-		self.assertEqual(pick_list.locations[1].item_code, '_Test Item Home Desktop 100')
+		self.assertEqual(pick_list.locations[1].item_code, '_Test Item')
 		self.assertEqual(pick_list.locations[1].warehouse, '_Test Warehouse - _TC')
 		self.assertEqual(pick_list.locations[1].qty, 5)
 		self.assertEqual(pick_list.locations[1].sales_order_item, sales_order.items[0].name)
 
+	def test_pick_list_for_items_with_multiple_UOM(self):
+		purchase_receipt = make_purchase_receipt(item_code="_Test Item", qty=10)
+		purchase_receipt.submit()
+
+		sales_order = frappe.get_doc({
+				'doctype': 'Sales Order',
+				'customer': '_Test Customer',
+				'company': '_Test Company',
+				'items': [{
+					'item_code': '_Test Item',
+					'qty': 1,
+					'conversion_factor': 5,
+					'delivery_date': frappe.utils.today()
+				}, {
+					'item_code': '_Test Item',
+					'qty': 1,
+					'conversion_factor': 1,
+					'delivery_date': frappe.utils.today()
+				}],
+			}).insert()
+		sales_order.submit()
+
+		pick_list = frappe.get_doc({
+			'doctype': 'Pick List',
+			'company': '_Test Company',
+			'customer': '_Test Customer',
+			'items_based_on': 'Sales Order',
+			'locations': [{
+				'item_code': '_Test Item',
+				'qty': 1,
+				'stock_qty': 5,
+				'conversion_factor': 5,
+				'sales_order': sales_order.name,
+				'sales_order_item': sales_order.items[0].name ,
+			}, {
+				'item_code': '_Test Item',
+				'qty': 1,
+				'stock_qty': 1,
+				'conversion_factor': 1,
+				'sales_order': sales_order.name,
+				'sales_order_item': sales_order.items[1].name ,
+			}]
+		})
+		pick_list.set_item_locations()
+		pick_list.submit()
+
+		delivery_note = create_delivery_note(pick_list.name)
+
+		self.assertEqual(pick_list.locations[0].qty, delivery_note.items[0].qty)
+		self.assertEqual(pick_list.locations[1].qty, delivery_note.items[1].qty)
+		self.assertEqual(sales_order.items[0].conversion_factor, delivery_note.items[0].conversion_factor)
+
+		pick_list.cancel()
+		sales_order.cancel()
+		purchase_receipt.cancel()
 
 	# def test_pick_list_skips_items_in_expired_batch(self):
 	# 	pass
@@ -302,4 +358,4 @@
 	# 	pass
 
 	# def test_pick_list_from_material_request(self):
-	# 	pass
\ No newline at end of file
+	# 	pass
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 57cc350..4d1a514 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -248,13 +248,6 @@
 	}
 }
 
-cur_frm.cscript.select_print_heading = function(doc, cdt, cdn) {
-	if(doc.select_print_heading)
-		cur_frm.pformat.print_heading = doc.select_print_heading;
-	else
-		cur_frm.pformat.print_heading = "Purchase Receipt";
-}
-
 cur_frm.fields_dict['select_print_heading'].get_query = function(doc, cdt, cdn) {
 	return {
 		filters: [
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 70687bda..5d7597b 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -176,7 +176,7 @@
 		if flt(self.per_billed) < 100:
 			self.update_billing_status()
 		else:
-			self.status = "Completed"
+			self.db_set("status", "Completed")
 
 
 		# Updating stock ledger should always be called after updating prevdoc status,
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 7741ee7..7f0c3fa 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -191,7 +191,7 @@
 
 		rm_supp_cost = sum([d.amount for d in pr.get("supplied_items")])
 		self.assertEqual(pr.get("items")[0].rm_supp_cost, flt(rm_supp_cost, 2))
-		
+
 		pr.cancel()
 
 	def test_subcontracting_gle_fg_item_rate_zero(self):
@@ -912,6 +912,57 @@
 		ste1.cancel()
 		po.cancel()
 
+
+	def test_po_to_pi_and_po_to_pr_worflow_full(self):
+		"""Test following behaviour:
+			- Create PO
+			- Create PI from PO and submit
+			- Create PR from PO and submit
+		"""
+		from erpnext.buying.doctype.purchase_order import test_purchase_order
+		from erpnext.buying.doctype.purchase_order import purchase_order
+
+		po = test_purchase_order.create_purchase_order()
+
+		pi = purchase_order.make_purchase_invoice(po.name)
+		pi.submit()
+
+		pr = purchase_order.make_purchase_receipt(po.name)
+		pr.submit()
+
+		pr.load_from_db()
+
+		self.assertEqual(pr.status, "Completed")
+		self.assertEqual(pr.per_billed, 100)
+
+	def test_po_to_pi_and_po_to_pr_worflow_partial(self):
+		"""Test following behaviour:
+			- Create PO
+			- Create partial PI from PO and submit
+			- Create PR from PO and submit
+		"""
+		from erpnext.buying.doctype.purchase_order import test_purchase_order
+		from erpnext.buying.doctype.purchase_order import purchase_order
+
+		po = test_purchase_order.create_purchase_order()
+
+		pi = purchase_order.make_purchase_invoice(po.name)
+		pi.items[0].qty /= 2   # roughly 50%, ^ this function only creates PI with 1 item.
+		pi.submit()
+
+		pr = purchase_order.make_purchase_receipt(po.name)
+		pr.save()
+		# per_billed is only updated after submission.
+		self.assertEqual(flt(pr.per_billed), 0)
+
+		pr.submit()
+
+		pi.load_from_db()
+		pr.load_from_db()
+
+		self.assertEqual(pr.status, "To Bill")
+		self.assertAlmostEqual(pr.per_billed, 50.0, places=2)
+
 def get_sl_entries(voucher_type, voucher_no):
 	return frappe.db.sql(""" select actual_qty, warehouse, stock_value_difference
 		from `tabStock Ledger Entry` where voucher_type=%s and voucher_no=%s
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 58b1eca..469511a 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -18,6 +18,7 @@
 		if self.readings:
 			self.inspect_and_set_status()
 
+	@frappe.whitelist()
 	def get_item_specification_details(self):
 		if not self.quality_inspection_template:
 			self.quality_inspection_template = frappe.db.get_value('Item',
@@ -32,6 +33,7 @@
 			child.update(d)
 			child.status = "Accepted"
 
+	@frappe.whitelist()
 	def get_quality_inspection_template(self):
 		template = ''
 		if self.bom_no:
@@ -62,17 +64,21 @@
 					(quality_inspection, self.modified, self.reference_name, self.item_code))
 
 		else:
+			args = [quality_inspection, self.modified, self.reference_name, self.item_code]
 			doctype = self.reference_type + ' Item'
+
 			if self.reference_type == 'Stock Entry':
 				doctype = 'Stock Entry Detail'
 
 			if self.reference_type and self.reference_name:
 				conditions = ""
 				if self.batch_no and self.docstatus == 1:
-					conditions += " and t1.batch_no = '%s'"%(self.batch_no)
+					conditions += " and t1.batch_no = %s"
+					args.append(self.batch_no)
 
 				if self.docstatus == 2: # if cancel, then remove qi link wherever same name
-					conditions += " and t1.quality_inspection = '%s'"%(self.name)
+					conditions += " and t1.quality_inspection = %s"
+					args.append(self.name)
 
 				frappe.db.sql("""
 					UPDATE
@@ -85,7 +91,7 @@
 						and t1.parent = t2.name
 						{conditions}
 				""".format(parent_doc=self.reference_type, child_doc=doctype, conditions=conditions),
-					(quality_inspection, self.modified, self.reference_name, self.item_code))
+					args)
 
 	def inspect_and_set_status(self):
 		for reading in self.readings:
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 8436acb..3f83780 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -5,7 +5,7 @@
 from __future__ import unicode_literals
 import frappe, erpnext
 from frappe.model.document import Document
-from frappe.utils import cint, get_link_to_form
+from frappe.utils import cint, get_link_to_form, add_to_date, today
 from erpnext.stock.stock_ledger import repost_future_sle
 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
@@ -29,7 +29,7 @@
 			self.company = frappe.get_cached_value(self.voucher_type, self.voucher_no, "company")
 		elif self.warehouse:
 			self.company = frappe.get_cached_value("Warehouse", self.warehouse, "company")
-	
+
 	def set_status(self, status=None):
 		if not status:
 			status = 'Queued'
@@ -39,6 +39,7 @@
 		frappe.enqueue(repost, timeout=1800, queue='long',
 			job_name='repost_sle', now=frappe.flags.in_test, doc=self)
 
+	@frappe.whitelist()
 	def restart_reposting(self):
 		self.set_status('Queued')
 		frappe.enqueue(repost, timeout=1800, queue='long',
@@ -54,7 +55,6 @@
 
 		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:
@@ -103,7 +103,7 @@
 	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}")
@@ -112,4 +112,24 @@
 	)
 	frappe.sendmail(recipients=recipients, subject=subject, message=message)
 
+def repost_entries():
+	riv_entries = get_repost_item_valuation_entries()
 
+	for row in riv_entries:
+		doc = frappe.get_cached_doc('Repost Item Valuation', row.name)
+		repost(doc)
+
+	riv_entries = get_repost_item_valuation_entries()
+	if riv_entries:
+		return
+
+	for d in frappe.get_all('Company', filters= {'enable_perpetual_inventory': 1}):
+		check_if_stock_and_account_balance_synced(today(), d.name)
+
+def get_repost_item_valuation_entries():
+	date = add_to_date(today(), hours=-3)
+
+	return frappe.db.sql(""" SELECT name from `tabRepost Item Valuation`
+		WHERE status != 'Completed' and creation <= %s and docstatus = 1
+		ORDER BY timestamp(posting_date, posting_time) asc, creation asc
+	""", date, as_dict=1)
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 64dcbed..42627f9 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -100,6 +100,13 @@
 
 		frm.add_fetch("bom_no", "inspection_required", "inspection_required");
 		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
+
+		frappe.db.get_single_value('Stock Settings', 'disable_serial_no_and_batch_selector')
+		.then((value) => {
+			if (value) {
+				frappe.flags.hide_serial_batch_dialog = true;
+			}
+		});
 	},
 
 	setup_quality_inspection: function(frm) {
@@ -721,7 +728,7 @@
 							no_batch_serial_number_value = !d.batch_no;
 						}
 
-						if (no_batch_serial_number_value) {
+						if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog) {
 							erpnext.stock.select_batch_and_serial_no(frm, d);
 						}
 					}
@@ -849,7 +856,6 @@
 		}
 		erpnext.hide_company();
 		erpnext.utils.add_item(this.frm);
-		this.frm.trigger('add_to_transit');
 	},
 
 	scan_barcode: function() {
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index ea1b387..f8ac400 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -458,7 +458,7 @@
 			Set rate for outgoing, scrapped and finished items
 		"""
 		# Set rate for outgoing items
-		outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate)
+		outgoing_items_cost = self.set_rate_for_outgoing_items(reset_outgoing_rate, raise_error_if_no_rate)
 		finished_item_qty = sum([d.transfer_qty for d in self.items if d.is_finished_item])
 
 		# Set basic rate for incoming items
@@ -482,13 +482,13 @@
 			d.basic_rate = flt(d.basic_rate, d.precision("basic_rate"))
 			d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
 
-	def set_rate_for_outgoing_items(self, reset_outgoing_rate=True):
+	def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
 		outgoing_items_cost = 0.0
 		for d in self.get('items'):
 			if d.s_warehouse:
 				if reset_outgoing_rate:
 					args = self.get_args_for_incoming_rate(d)
-					rate = get_incoming_rate(args)
+					rate = get_incoming_rate(args, raise_error_if_no_rate)
 					if rate > 0:
 						d.basic_rate = rate
 
@@ -839,6 +839,7 @@
 			if not pro_doc.operations:
 				pro_doc.set_actual_dates()
 
+	@frappe.whitelist()
 	def get_item_details(self, args=None, for_update=False):
 		item = frappe.db.sql("""select i.name, i.stock_uom, i.description, i.image, i.item_name, i.item_group,
 				i.has_batch_no, i.sample_quantity, i.has_serial_no, i.allow_alternative_item,
@@ -913,6 +914,7 @@
 
 		return ret
 
+	@frappe.whitelist()
 	def set_items_for_stock_in(self):
 		self.items = []
 
@@ -937,6 +939,7 @@
 					'batch_no': d.batch_no
 				})
 
+	@frappe.whitelist()
 	def get_items(self):
 		self.set('items', [])
 		self.validate_work_order()
@@ -1010,7 +1013,8 @@
 
 		self.set_scrap_items()
 		self.set_actual_qty()
-		self.calculate_rate_and_amount(raise_error_if_no_rate=False)
+		self.validate_customer_provided_item()
+		self.calculate_rate_and_amount()
 
 	def set_scrap_items(self):
 		if self.purpose != "Send to Subcontractor" and self.purpose in ["Manufacture", "Repack"]:
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 123f0c8..a0e7051 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -179,11 +179,15 @@
 	def test_material_transfer_gl_entry(self):
 		company = frappe.db.get_value('Warehouse', 'Stores - TCP1', 'company')
 
-		mtn = make_stock_entry(item_code="_Test Item", source="Stores - TCP1",
+		item_code = 'Hand Sanitizer - 001'
+		create_item(item_code =item_code, is_stock_item = 1,
+			is_purchase_item=1, opening_stock=1000, valuation_rate=10, company=company, warehouse="Stores - TCP1")
+
+		mtn = make_stock_entry(item_code=item_code, source="Stores - TCP1",
 			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]])
+			[[item_code, "Stores - TCP1", -45.0], [item_code, "Finished Goods - TCP1", 45.0]])
 
 		source_warehouse_account = get_inventory_account(mtn.company, mtn.get("items")[0].s_warehouse)
 
diff --git a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
index 59f1f39..349d8ae 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/test_stock_ledger_entry.py
@@ -125,7 +125,7 @@
 		pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-10',
 			warehouse="Stores - _TC", item_code="_Test Item for Reposting", qty=5, rate=100)
 
-		return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15', 
+		return_pr = make_purchase_receipt(company="_Test Company", posting_date='2020-04-15',
 			warehouse="Stores - _TC", item_code="_Test Item for Reposting", is_return=1, return_against=pr.name, qty=-2)
 
 		# check sle
@@ -278,7 +278,7 @@
 
 		frappe.db.set_value("Buying Settings", None, "backflush_raw_materials_of_subcontract_based_on", "BOM")
 		make_bom(item = subcontracted_item, raw_materials =[rm_item_code], currency="INR")
-		
+
 		# Purchase raw materials on supplier warehouse: Qty = 50, Rate = 100
 		pr = make_purchase_receipt(company=company, posting_date='2020-04-10',
 			warehouse="Stores - _TC", item_code=rm_item_code, qty=10, rate=100)
@@ -292,7 +292,7 @@
 
 		# Update raw material's valuation via LCV, Additional cost = 50
 		lcv = create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company)
-		
+
 		pr1.reload()
 		self.assertEqual(pr1.items[0].valuation_rate, 125)
 
@@ -310,31 +310,36 @@
 		# Back dated stock transactions are only allowed to stock managers
 		frappe.db.set_value("Stock Settings", None,
 			"role_allowed_to_create_edit_back_dated_transactions", "Stock Manager")
-		
+
 		# Set User with Stock User role but not Stock Manager
-		frappe.set_user("test@example.com")
-		user = frappe.get_doc("User", "test@example.com")
-		user.add_roles("Stock User")
-		user.remove_roles("Stock Manager")
+		try:
+			user = frappe.get_doc("User", "test@example.com")
+			frappe.set_user(user.name)
+			user.add_roles("Stock User")
+			user.remove_roles("Stock Manager")
 
-		stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
-		back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
-			posting_date=add_days(today(), -1), do_not_submit=True)
+			stock_entry_on_today = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100)
+			back_dated_se_1 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
+				posting_date=add_days(today(), -1), do_not_submit=True)
 
-		# Block back-dated entry
-		self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit)
+			# Block back-dated entry
+			self.assertRaises(BackDatedStockTransaction, back_dated_se_1.submit)
 
-		user.add_roles("Stock Manager")
+			frappe.set_user("Administrator")
+			user.add_roles("Stock Manager")
+			frappe.set_user(user.name)
 
-		# Back dated entry allowed to Stock Manager
-		back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
-			posting_date=add_days(today(), -1))
+			# Back dated entry allowed to Stock Manager
+			back_dated_se_2 = make_stock_entry(target="_Test Warehouse - _TC", qty=10, basic_rate=100,
+				posting_date=add_days(today(), -1))
 
-		back_dated_se_2.cancel()
-		stock_entry_on_today.cancel()
+			back_dated_se_2.cancel()
+			stock_entry_on_today.cancel()
 
-		frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None)
-		frappe.set_user("Administrator")
+		finally:
+			frappe.db.set_value("Stock Settings", None, "role_allowed_to_create_edit_back_dated_transactions", None)
+			frappe.set_user("Administrator")
+			user.remove_roles("Stock Manager")
 
 
 def create_repack_entry(**args):
@@ -398,4 +403,4 @@
 
 		make_item(d, properties=properties)
 
-	return items
\ No newline at end of file
+	return items
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index f0a90f9..b452e96 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -29,6 +29,8 @@
 		self.remove_items_with_no_change()
 		self.validate_data()
 		self.validate_expense_account()
+		self.validate_customer_provided_item()
+		self.set_zero_value_for_customer_provided_items()
 		self.set_total_qty_and_amount()
 		self.validate_putaway_capacity()
 
@@ -217,7 +219,7 @@
 					if row.valuation_rate in ("", None):
 						row.valuation_rate = previous_sle.get("valuation_rate", 0)
 
-				if row.qty and not row.valuation_rate:
+				if row.qty and not row.valuation_rate and not row.allow_zero_valuation_rate:
 					frappe.throw(_("Valuation Rate required for Item {0} at row {1}").format(row.item_code, row.idx))
 
 				if ((previous_sle and row.qty == previous_sle.get("qty_after_transaction")
@@ -436,6 +438,20 @@
 			if frappe.db.get_value("Account", self.expense_account, "report_type") == "Profit and Loss":
 				frappe.throw(_("Difference Account must be a Asset/Liability type account, since this Stock Reconciliation is an Opening Entry"), OpeningEntryAccountError)
 
+	def set_zero_value_for_customer_provided_items(self):
+		changed_any_values = False
+
+		for d in self.get('items'):
+			is_customer_item = frappe.db.get_value('Item', d.item_code, 'is_customer_provided_item')
+			if is_customer_item and d.valuation_rate:
+				d.valuation_rate = 0.0
+				changed_any_values = True
+
+		if changed_any_values:
+			msgprint(_("Valuation rate for customer provided items has been set to zero."),
+				title=_("Note"), indicator="blue")
+
+
 	def set_total_qty_and_amount(self):
 		for d in self.get("items"):
 			d.amount = flt(d.qty, d.precision("qty")) * flt(d.valuation_rate, d.precision("valuation_rate"))
@@ -531,4 +547,4 @@
 		account = frappe.db.get_value('Account', {'is_group': 0,
 			'company': company, 'account_type': 'Temporary'}, 'name')
 
-	return account
\ No newline at end of file
+	return account
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index 088456f..6690c6a 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -193,6 +193,16 @@
 			stock_doc = frappe.get_doc("Stock Reconciliation", d)
 			stock_doc.cancel()
 
+	def test_customer_provided_items(self):
+		item_code = 'Stock-Reco-customer-Item-100'
+		create_item(item_code, is_customer_provided_item = 1,
+			  customer = '_Test Customer', is_purchase_item = 0)
+
+		sr = create_stock_reconciliation(item_code = item_code, qty = 10, rate = 420)
+
+		self.assertEqual(sr.get("items")[0].allow_zero_valuation_rate, 1)
+		self.assertEqual(sr.get("items")[0].valuation_rate, 0)
+		self.assertEqual(sr.get("items")[0].amount, 0)
 
 def insert_existing_sle(warehouse):
 	from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
diff --git a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
index e53db07..85c7ebe 100644
--- a/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
+++ b/erpnext/stock/doctype/stock_reconciliation_item/stock_reconciliation_item.json
@@ -13,6 +13,7 @@
   "qty",
   "valuation_rate",
   "amount",
+  "allow_zero_valuation_rate",
   "serial_no_and_batch_section",
   "serial_no",
   "column_break_11",
@@ -166,10 +167,19 @@
    "fieldtype": "Link",
    "label": "Batch No",
    "options": "Batch"
+  },
+  {
+   "default": "0",
+   "fieldname": "allow_zero_valuation_rate",
+   "fieldtype": "Check",
+   "label": "Allow Zero Valuation Rate",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "istable": 1,
- "modified": "2019-06-14 17:10:53.188305",
+ "links": [],
+ "modified": "2021-03-23 11:09:44.407157",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reconciliation Item",
@@ -179,4 +189,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/stock/doctype/warehouse/warehouse.json b/erpnext/stock/doctype/warehouse/warehouse.json
index bddb114..9b90932 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.json
+++ b/erpnext/stock/doctype/warehouse/warehouse.json
@@ -70,6 +70,7 @@
    "oldfieldname": "company",
    "oldfieldtype": "Link",
    "options": "Company",
+   "read_only_depends_on": "eval: !doc.__islocal",
    "remember_last_selected_value": 1,
    "reqd": 1,
    "search_index": 1
@@ -244,7 +245,7 @@
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2021-02-16 17:21:52.380098",
+ "modified": "2021-04-09 19:54:56.263965",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Warehouse",
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 873cfec..e23f7d4 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -110,7 +110,7 @@
 	get_gross_profit(out)
 	if args.doctype == 'Material Request':
 		out.rate = args.rate or out.price_list_rate
-		out.amount = flt(args.qty * out.rate)
+		out.amount = flt(args.qty) * flt(out.rate)
 
 	return out
 
@@ -314,7 +314,9 @@
 		"last_purchase_rate": item.last_purchase_rate if args.get("doctype") in ["Purchase Order"] else 0,
 		"transaction_date": args.get("transaction_date"),
 		"against_blanket_order": args.get("against_blanket_order"),
-		"bom_no": item.get("default_bom")
+		"bom_no": item.get("default_bom"),
+		"weight_per_unit": args.get("weight_per_unit") or item.get("weight_per_unit"),
+		"weight_uom": args.get("weight_uom") or item.get("weight_uom")
 	})
 
 	if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
@@ -369,6 +371,9 @@
 	if meta.get_field("barcode"):
 		update_barcode_value(out)
 
+	if out.get("weight_per_unit"):
+		out['total_weight'] = out.weight_per_unit * out.stock_qty
+
 	return out
 
 def get_item_warehouse(item, args, overwrite_warehouse, defaults={}):
diff --git a/erpnext/stock/report/stock_ageing/stock_ageing.py b/erpnext/stock/report/stock_ageing/stock_ageing.py
index ff603fc..623dc2f 100644
--- a/erpnext/stock/report/stock_ageing/stock_ageing.py
+++ b/erpnext/stock/report/stock_ageing/stock_ageing.py
@@ -49,7 +49,7 @@
 	for batch in fifo_queue:
 		batch_age = date_diff(to_date, batch[1])
 
-		if type(batch[0]) in ['int', 'float']:
+		if isinstance(batch[0], (int, float)):
 			age_qty += batch_age * batch[0]
 			total_qty += batch[0]
 		else:
@@ -302,4 +302,4 @@
 		fieldname=fieldname,
 		fieldtype=fieldtype,
 		width=width
-	))
\ No newline at end of file
+	))
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index e5d4d62..6dfede4 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -198,7 +198,7 @@
 		else:
 			qty_diff = flt(d.actual_qty)
 
-		value_diff = flt(d.stock_value) - flt(qty_dict.bal_val)
+		value_diff = flt(d.stock_value_difference)
 
 		if d.posting_date < from_date:
 			qty_dict.opening_qty += qty_diff
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index f54b3c1..121c51c 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -207,11 +207,11 @@
 
 
 	def build(self):
-		from erpnext.controllers.stock_controller import check_if_future_sle_exists
+		from erpnext.controllers.stock_controller import future_sle_exists
 
 		if self.args.get("sle_id"):
 			self.process_sle_against_current_timestamp()
-			if not check_if_future_sle_exists(self.args):
+			if not future_sle_exists(self.args):
 				self.update_bin()
 		else:
 			entries_to_fix = self.get_future_entries_to_fix()
@@ -856,4 +856,4 @@
 			and qty_after_transaction < 0
 		order by timestamp(posting_date, posting_time) asc
 		limit 1
-	""", args, as_dict=1)
\ No newline at end of file
+	""", args, as_dict=1)
diff --git a/erpnext/support/doctype/issue/issue.js b/erpnext/support/doctype/issue/issue.js
index 9fe12f9..ecc9fcf 100644
--- a/erpnext/support/doctype/issue/issue.js
+++ b/erpnext/support/doctype/issue/issue.js
@@ -48,44 +48,62 @@
 		}
 	},
 
-	refresh: function (frm) {
-		if (frm.doc.status !== "Closed") {
-			if (frm.doc.service_level_agreement && frm.doc.agreement_status === "Ongoing") {
-				frappe.call({
-					"method": "frappe.client.get",
-					args: {
-						doctype: "Service Level Agreement",
-						name: frm.doc.service_level_agreement
-					},
-					callback: function(data) {
-						let statuses = data.message.pause_sla_on;
-						const hold_statuses = [];
-						$.each(statuses, (_i, entry) => {
-							hold_statuses.push(entry.status);
-						});
-						if (hold_statuses.includes(frm.doc.status)) {
-							frm.dashboard.clear_headline();
-							let message = {"indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)])};
-							frm.dashboard.set_headline_alert(
-								'<div class="row">' +
-									'<div class="col-xs-12">' +
-										'<span class="indicator whitespace-nowrap '+ message.indicator +'"><span>'+ message.msg +'</span></span> ' +
-									'</div>' +
-								'</div>'
-							);
-						} else {
-							set_time_to_resolve_and_response(frm);
-						}
-					}
-				});
-			}
+	refresh: function(frm) {
 
-			frm.add_custom_button(__("Close"), function () {
+		// alert messages
+		if (frm.doc.status !== "Closed" && frm.doc.service_level_agreement
+			&& frm.doc.agreement_status === "Ongoing") {
+			frappe.call({
+				"method": "frappe.client.get",
+				args: {
+					doctype: "Service Level Agreement",
+					name: frm.doc.service_level_agreement
+				},
+				callback: function(data) {
+					let statuses = data.message.pause_sla_on;
+					const hold_statuses = [];
+					$.each(statuses, (_i, entry) => {
+						hold_statuses.push(entry.status);
+					});
+					if (hold_statuses.includes(frm.doc.status)) {
+						frm.dashboard.clear_headline();
+						let message = { "indicator": "orange", "msg": __("SLA is on hold since {0}", [moment(frm.doc.on_hold_since).fromNow(true)]) };
+						frm.dashboard.set_headline_alert(
+							'<div class="row">' +
+							'<div class="col-xs-12">' +
+							'<span class="indicator whitespace-nowrap ' + message.indicator + '"><span>' + message.msg + '</span></span> ' +
+							'</div>' +
+							'</div>'
+						);
+					} else {
+						set_time_to_resolve_and_response(frm);
+					}
+				}
+			});
+		} else if (frm.doc.service_level_agreement) {
+			frm.dashboard.clear_headline();
+
+			let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
+				{ "indicator": "green", "msg": "Service Level Agreement has been fulfilled" } :
+				{ "indicator": "red", "msg": "Service Level Agreement Failed" };
+
+			frm.dashboard.set_headline_alert(
+				'<div class="row">' +
+				'<div class="col-xs-12">' +
+				'<span class="indicator whitespace-nowrap ' + agreement_status.indicator + '"><span class="hidden-xs">' + agreement_status.msg + '</span></span> ' +
+				'</div>' +
+				'</div>'
+			);
+		}
+
+		// buttons
+		if (frm.doc.status !== "Closed") {
+			frm.add_custom_button(__("Close"), function() {
 				frm.set_value("status", "Closed");
 				frm.save();
 			});
 
-			frm.add_custom_button(__("Task"), function () {
+			frm.add_custom_button(__("Task"), function() {
 				frappe.model.open_mapped_doc({
 					method: "erpnext.support.doctype.issue.issue.make_task",
 					frm: frm
@@ -93,23 +111,7 @@
 			}, __("Create"));
 
 		} else {
-			if (frm.doc.service_level_agreement) {
-				frm.dashboard.clear_headline();
-
-				let agreement_status = (frm.doc.agreement_status == "Fulfilled") ?
-					{"indicator": "green", "msg": "Service Level Agreement has been fulfilled"} :
-					{"indicator": "red", "msg": "Service Level Agreement Failed"};
-
-				frm.dashboard.set_headline_alert(
-					'<div class="row">' +
-						'<div class="col-xs-12">' +
-							'<span class="indicator whitespace-nowrap '+ agreement_status.indicator +'"><span class="hidden-xs">'+ agreement_status.msg +'</span></span> ' +
-						'</div>' +
-					'</div>'
-				);
-			}
-
-			frm.add_custom_button(__("Reopen"), function () {
+			frm.add_custom_button(__("Reopen"), function() {
 				frm.set_value("status", "Open");
 				frm.save();
 			});
diff --git a/erpnext/support/doctype/issue/issue.py b/erpnext/support/doctype/issue/issue.py
index bbbbc4a..b068363 100644
--- a/erpnext/support/doctype/issue/issue.py
+++ b/erpnext/support/doctype/issue/issue.py
@@ -7,7 +7,7 @@
 from frappe import _
 from frappe import utils
 from frappe.model.document import Document
-from frappe.utils import now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
+from frappe.utils import cint, now_datetime, getdate, get_weekdays, add_to_date, get_time, get_datetime, time_diff_in_seconds
 from datetime import datetime, timedelta
 from frappe.model.mapper import get_mapped_doc
 from frappe.utils.user import is_website_user
@@ -128,8 +128,8 @@
 
 	def update_agreement_status(self):
 		if self.service_level_agreement and self.agreement_status == "Ongoing":
-			if frappe.db.get_value("Issue", self.name, "response_by_variance") < 0 or \
-				frappe.db.get_value("Issue", self.name, "resolution_by_variance") < 0:
+			if cint(frappe.db.get_value("Issue", self.name, "response_by_variance")) < 0 or \
+				cint(frappe.db.get_value("Issue", self.name, "resolution_by_variance")) < 0:
 
 				self.agreement_status = "Failed"
 			else:
@@ -165,6 +165,7 @@
 		communication.ignore_mandatory = True
 		communication.save()
 
+	@frappe.whitelist()
 	def split_issue(self, subject, communication_id):
 		# Bug: Pressing enter doesn't send subject
 		from copy import deepcopy
@@ -259,6 +260,7 @@
 				self.set_response_and_resolution_time(priority=self.priority, service_level_agreement=self.service_level_agreement)
 				frappe.msgprint(_("Service Level Agreement has been changed to {0}.").format(self.service_level_agreement))
 
+	@frappe.whitelist()
 	def reset_service_level_agreement(self, reason, user):
 		if not frappe.db.get_single_value("Support Settings", "allow_resetting_service_level_agreement"):
 			frappe.throw(_("Allow Resetting Service Level Agreement from Support Settings."))
diff --git a/erpnext/support/doctype/issue/test_issue.py b/erpnext/support/doctype/issue/test_issue.py
index 483bb15..46d02d8 100644
--- a/erpnext/support/doctype/issue/test_issue.py
+++ b/erpnext/support/doctype/issue/test_issue.py
@@ -12,7 +12,6 @@
 class TestIssue(unittest.TestCase):
 	def setUp(self):
 		frappe.db.sql("delete from `tabService Level Agreement`")
-		frappe.db.sql("delete from `tabEmployee`")
 		frappe.db.set_value("Support Settings", None, "track_service_level_agreement", 1)
 		create_service_level_agreements_for_issues()
 
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
index 5346195..00060b9 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.js
@@ -10,7 +10,9 @@
 			let statuses = frappe.meta.get_docfield('Issue', 'status', frm.doc.name).options;
 			statuses = statuses.split('\n');
 			allow_statuses = statuses.filter((status) => !exclude_statuses.includes(status));
-			frappe.meta.get_docfield('Pause SLA On Status', 'status', frm.doc.name).options = [''].concat(allow_statuses);
+			frm.fields_dict.pause_sla_on.grid.update_docfield_property(
+				'status', 'options', [''].concat(allow_statuses)
+			);
 		});
 	}
-});
\ No newline at end of file
+});
diff --git a/erpnext/support/report/issue_analytics/issue_analytics.js b/erpnext/support/report/issue_analytics/issue_analytics.js
index f87b2c2..746eee0 100644
--- a/erpnext/support/report/issue_analytics/issue_analytics.js
+++ b/erpnext/support/report/issue_analytics/issue_analytics.js
@@ -52,6 +52,7 @@
 			label: __("Status"),
 			fieldtype: "Select",
 			options:[
+				"",
 				{label: __('Open'), value: 'Open'},
 				{label: __('Replied'), value: 'Replied'},
 				{label: __('Resolved'), value: 'Resolved'},
@@ -138,4 +139,4 @@
 			}
 		});
 	}
-};
\ No newline at end of file
+};
diff --git a/erpnext/support/report/issue_summary/issue_summary.js b/erpnext/support/report/issue_summary/issue_summary.js
index 684482a..eb0e06c 100644
--- a/erpnext/support/report/issue_summary/issue_summary.js
+++ b/erpnext/support/report/issue_summary/issue_summary.js
@@ -39,6 +39,7 @@
 			label: __("Status"),
 			fieldtype: "Select",
 			options:[
+				"",
 				{label: __('Open'), value: 'Open'},
 				{label: __('Replied'), value: 'Replied'},
 				{label: __('Resolved'), value: 'Resolved'},
@@ -70,4 +71,4 @@
 			options: "User"
 		}
 	]
-};
\ No newline at end of file
+};
diff --git a/erpnext/support/report/issue_summary/issue_summary.py b/erpnext/support/report/issue_summary/issue_summary.py
index 3d73531..7861e30 100644
--- a/erpnext/support/report/issue_summary/issue_summary.py
+++ b/erpnext/support/report/issue_summary/issue_summary.py
@@ -260,8 +260,7 @@
 					self.issue_summary_data[value]['avg_user_resolution_time'] = entry.get('avg_user_resolution_time') or 0.0
 
 	def get_chart_data(self):
-		if not self.data:
-			return None
+		self.chart = []
 
 		labels = []
 		open_issues = []
@@ -310,8 +309,7 @@
 		}
 
 	def get_report_summary(self):
-		if not self.data:
-			return None
+		self.report_summary = []
 
 		open_issues = 0
 		replied = 0
diff --git a/sider.yml b/sider.yml
new file mode 100644
index 0000000..2ca6e8d
--- /dev/null
+++ b/sider.yml
@@ -0,0 +1,3 @@
+linter:
+  flake8:
+    config: .flake8
\ No newline at end of file