Merge branch 'develop' into accounting_dimension_consistency
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 4453783..3f1998a 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -99,7 +99,7 @@
 			if doctype == "Budget":
 				add_dimension_to_budget_doctype(df.copy(), doc)
 			else:
-				create_custom_field(doctype, df)
+				create_custom_field(doctype, df, ignore_validate=True)
 
 		count += 1
 
@@ -115,7 +115,7 @@
 		}
 	)
 
-	create_custom_field("Budget", df)
+	create_custom_field("Budget", df, ignore_validate=True)
 
 	property_setter = frappe.db.exists("Property Setter", "Budget-budget_against-options")
 
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 9a35a24..417611f 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -18,7 +18,6 @@
   "automatically_fetch_payment_terms",
   "column_break_17",
   "enable_common_party_accounting",
-  "enable_discount_accounting",
   "report_setting_section",
   "use_custom_cash_flow",
   "deferred_accounting_settings_section",
@@ -274,13 +273,6 @@
   },
   {
    "default": "0",
-   "description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
-   "fieldname": "enable_discount_accounting",
-   "fieldtype": "Check",
-   "label": "Enable Discount Accounting"
-  },
-  {
-   "default": "0",
    "description": "Learn about <a href=\"https://docs.erpnext.com/docs/v13/user/manual/en/accounts/articles/common_party_accounting#:~:text=Common%20Party%20Accounting%20in%20ERPNext,Invoice%20against%20a%20primary%20Supplier.\">Common Party</a>",
    "fieldname": "enable_common_party_accounting",
    "fieldtype": "Check",
@@ -354,7 +346,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2022-02-04 12:32:36.805652",
+ "modified": "2022-04-08 14:45:06.796418",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
index 8354981..3b125a2 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.py
@@ -28,7 +28,6 @@
 
 		self.validate_stale_days()
 		self.enable_payment_schedule_in_print()
-		self.toggle_discount_accounting_fields()
 		self.validate_pending_reposts()
 
 	def validate_stale_days(self):
@@ -52,74 +51,6 @@
 				validate_fields_for_doctype=False,
 			)
 
-	def toggle_discount_accounting_fields(self):
-		enable_discount_accounting = cint(self.enable_discount_accounting)
-
-		for doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
-			make_property_setter(
-				doctype,
-				"discount_account",
-				"hidden",
-				not (enable_discount_accounting),
-				"Check",
-				validate_fields_for_doctype=False,
-			)
-			if enable_discount_accounting:
-				make_property_setter(
-					doctype,
-					"discount_account",
-					"mandatory_depends_on",
-					"eval: doc.discount_amount",
-					"Code",
-					validate_fields_for_doctype=False,
-				)
-			else:
-				make_property_setter(
-					doctype,
-					"discount_account",
-					"mandatory_depends_on",
-					"",
-					"Code",
-					validate_fields_for_doctype=False,
-				)
-
-		for doctype in ["Sales Invoice", "Purchase Invoice"]:
-			make_property_setter(
-				doctype,
-				"additional_discount_account",
-				"hidden",
-				not (enable_discount_accounting),
-				"Check",
-				validate_fields_for_doctype=False,
-			)
-			if enable_discount_accounting:
-				make_property_setter(
-					doctype,
-					"additional_discount_account",
-					"mandatory_depends_on",
-					"eval: doc.discount_amount",
-					"Code",
-					validate_fields_for_doctype=False,
-				)
-			else:
-				make_property_setter(
-					doctype,
-					"additional_discount_account",
-					"mandatory_depends_on",
-					"",
-					"Code",
-					validate_fields_for_doctype=False,
-				)
-
-		make_property_setter(
-			"Item",
-			"default_discount_account",
-			"hidden",
-			not (enable_discount_accounting),
-			"Check",
-			validate_fields_for_doctype=False,
-		)
-
 	def validate_pending_reposts(self):
 		if self.acc_frozen_upto:
 			check_pending_reposting(self.acc_frozen_upto)
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 96779d7..0f617b5 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -5,7 +5,10 @@
 import frappe
 from frappe import _, msgprint
 from frappe.model.document import Document
-from frappe.utils import flt, fmt_money, getdate, nowdate
+from frappe.query_builder.custom import ConstantColumn
+from frappe.utils import flt, fmt_money, getdate
+
+import erpnext
 
 form_grid_templates = {"journal_entries": "templates/form_grid/bank_reconciliation_grid.html"}
 
@@ -76,6 +79,52 @@
 			as_dict=1,
 		)
 
+		loan_disbursement = frappe.qb.DocType("Loan Disbursement")
+
+		loan_disbursements = (
+			frappe.qb.from_(loan_disbursement)
+			.select(
+				ConstantColumn("Loan Disbursement").as_("payment_document"),
+				loan_disbursement.name.as_("payment_entry"),
+				loan_disbursement.disbursed_amount.as_("credit"),
+				ConstantColumn(0).as_("debit"),
+				loan_disbursement.reference_number.as_("cheque_number"),
+				loan_disbursement.reference_date.as_("cheque_date"),
+				loan_disbursement.disbursement_date.as_("posting_date"),
+				loan_disbursement.applicant.as_("against_account"),
+			)
+			.where(loan_disbursement.docstatus == 1)
+			.where(loan_disbursement.disbursement_date >= self.from_date)
+			.where(loan_disbursement.disbursement_date <= self.to_date)
+			.where(loan_disbursement.clearance_date.isnull())
+			.where(loan_disbursement.disbursement_account.isin([self.bank_account, self.account]))
+			.orderby(loan_disbursement.disbursement_date)
+			.orderby(loan_disbursement.name, frappe.qb.desc)
+		).run(as_dict=1)
+
+		loan_repayment = frappe.qb.DocType("Loan Repayment")
+
+		loan_repayments = (
+			frappe.qb.from_(loan_repayment)
+			.select(
+				ConstantColumn("Loan Repayment").as_("payment_document"),
+				loan_repayment.name.as_("payment_entry"),
+				loan_repayment.amount_paid.as_("debit"),
+				ConstantColumn(0).as_("credit"),
+				loan_repayment.reference_number.as_("cheque_number"),
+				loan_repayment.reference_date.as_("cheque_date"),
+				loan_repayment.applicant.as_("against_account"),
+				loan_repayment.posting_date,
+			)
+			.where(loan_repayment.docstatus == 1)
+			.where(loan_repayment.clearance_date.isnull())
+			.where(loan_repayment.posting_date >= self.from_date)
+			.where(loan_repayment.posting_date <= self.to_date)
+			.where(loan_repayment.payment_account.isin([self.bank_account, self.account]))
+			.orderby(loan_repayment.posting_date)
+			.orderby(loan_repayment.name, frappe.qb.desc)
+		).run(as_dict=1)
+
 		pos_sales_invoices, pos_purchase_invoices = [], []
 		if self.include_pos_transactions:
 			pos_sales_invoices = frappe.db.sql(
@@ -114,20 +163,29 @@
 
 		entries = sorted(
 			list(payment_entries)
-			+ list(journal_entries + list(pos_sales_invoices) + list(pos_purchase_invoices)),
-			key=lambda k: k["posting_date"] or getdate(nowdate()),
+			+ list(journal_entries)
+			+ list(pos_sales_invoices)
+			+ list(pos_purchase_invoices)
+			+ list(loan_disbursements)
+			+ list(loan_repayments),
+			key=lambda k: getdate(k["posting_date"]),
 		)
 
 		self.set("payment_entries", [])
 		self.total_amount = 0.0
+		default_currency = erpnext.get_default_currency()
 
 		for d in entries:
 			row = self.append("payment_entries", {})
 
 			amount = flt(d.get("debit", 0)) - flt(d.get("credit", 0))
 
+			if not d.get("account_currency"):
+				d.account_currency = default_currency
+
 			formatted_amount = fmt_money(abs(amount), 2, d.account_currency)
 			d.amount = formatted_amount + " " + (_("Dr") if amount > 0 else _("Cr"))
+			d.posting_date = getdate(d.posting_date)
 
 			d.pop("credit")
 			d.pop("debit")
diff --git a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py
index 706fbbe..c1e55f6 100644
--- a/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/test_bank_clearance.py
@@ -1,9 +1,96 @@
 # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-# import frappe
 import unittest
 
+import frappe
+from frappe.utils import add_months, getdate
+
+from erpnext.accounts.doctype.payment_entry.test_payment_entry import get_payment_entry
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+from erpnext.loan_management.doctype.loan.test_loan import (
+	create_loan,
+	create_loan_accounts,
+	create_loan_type,
+	create_repayment_entry,
+	make_loan_disbursement_entry,
+)
+
 
 class TestBankClearance(unittest.TestCase):
-	pass
+	@classmethod
+	def setUpClass(cls):
+		make_bank_account()
+		create_loan_accounts()
+		create_loan_masters()
+		add_transactions()
+
+	# Basic test case to test if bank clearance tool doesn't break
+	# Detailed test can be added later
+	def test_bank_clearance(self):
+		bank_clearance = frappe.get_doc("Bank Clearance")
+		bank_clearance.account = "_Test Bank Clearance - _TC"
+		bank_clearance.from_date = add_months(getdate(), -1)
+		bank_clearance.to_date = getdate()
+		bank_clearance.get_payment_entries()
+		self.assertEqual(len(bank_clearance.payment_entries), 3)
+
+
+def make_bank_account():
+	if not frappe.db.get_value("Account", "_Test Bank Clearance - _TC"):
+		frappe.get_doc(
+			{
+				"doctype": "Account",
+				"account_type": "Bank",
+				"account_name": "_Test Bank Clearance",
+				"company": "_Test Company",
+				"parent_account": "Bank Accounts - _TC",
+			}
+		).insert()
+
+
+def create_loan_masters():
+	create_loan_type(
+		"Clearance Loan",
+		2000000,
+		13.5,
+		25,
+		0,
+		5,
+		"Cash",
+		"_Test Bank Clearance - _TC",
+		"_Test Bank Clearance - _TC",
+		"Loan Account - _TC",
+		"Interest Income Account - _TC",
+		"Penalty Income Account - _TC",
+	)
+
+
+def add_transactions():
+	make_payment_entry()
+	make_loan()
+
+
+def make_loan():
+	loan = create_loan(
+		"_Test Customer",
+		"Clearance Loan",
+		280000,
+		"Repay Over Number of Periods",
+		20,
+		applicant_type="Customer",
+	)
+	loan.submit()
+	make_loan_disbursement_entry(loan.name, loan.loan_amount, disbursement_date=getdate())
+	repayment_entry = create_repayment_entry(loan.name, "_Test Customer", getdate(), loan.loan_amount)
+	repayment_entry.save()
+	repayment_entry.submit()
+
+
+def make_payment_entry():
+	pi = make_purchase_invoice(supplier="_Test Supplier", qty=1, rate=690)
+	pe = get_payment_entry("Purchase Invoice", pi.name, bank_account="_Test Bank Clearance - _TC")
+	pe.reference_no = "Conrad Oct 18"
+	pe.reference_date = "2018-10-24"
+	pe.insert()
+	pe.submit()
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index a5f9e24..a1d86e2 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -669,7 +669,7 @@
 		exchange_rate_map, net_rate_map = get_purchase_document_details(self)
 
 		enable_discount_accounting = cint(
-			frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+			frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
 		)
 		provisional_accounting_for_non_stock_items = cint(
 			frappe.db.get_value(
@@ -1159,7 +1159,7 @@
 		# tax table gl entries
 		valuation_tax = {}
 		enable_discount_accounting = cint(
-			frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+			frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
 		)
 
 		for tax in self.get("taxes"):
@@ -1252,7 +1252,7 @@
 	def enable_discount_accounting(self):
 		if not hasattr(self, "_enable_discount_accounting"):
 			self._enable_discount_accounting = cint(
-				frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+				frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
 			)
 
 		return self._enable_discount_accounting
@@ -1369,7 +1369,9 @@
 		if (
 			not self.is_internal_transfer() and self.rounding_adjustment and self.base_rounding_adjustment
 		):
-			round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
+			round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
+				self.company, "Purchase Invoice", self.name
+			)
 
 			gl_entries.append(
 				self.get_gl_dict(
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index 59bd637..30d26ac 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -5,6 +5,7 @@
 import unittest
 
 import frappe
+from frappe.tests.utils import change_settings
 from frappe.utils import add_days, cint, flt, getdate, nowdate, today
 
 import erpnext
@@ -336,8 +337,8 @@
 
 		self.assertEqual(discrepancy_caused_by_exchange_rate_diff, amount)
 
+	@change_settings("Buying Settings", {"enable_discount_accounting": 1})
 	def test_purchase_invoice_with_discount_accounting_enabled(self):
-		enable_discount_accounting()
 
 		discount_account = create_account(
 			account_name="Discount Account",
@@ -353,10 +354,10 @@
 		]
 
 		check_gl_entries(self, pi.name, expected_gle, nowdate())
-		enable_discount_accounting(enable=0)
 
+	@change_settings("Buying Settings", {"enable_discount_accounting": 1})
 	def test_additional_discount_for_purchase_invoice_with_discount_accounting_enabled(self):
-		enable_discount_accounting()
+
 		additional_discount_account = create_account(
 			account_name="Discount Account",
 			parent_account="Indirect Expenses - _TC",
@@ -1588,12 +1589,6 @@
 	accounts_settings.save()
 
 
-def enable_discount_accounting(enable=1):
-	accounts_settings = frappe.get_doc("Accounts Settings")
-	accounts_settings.enable_discount_accounting = enable
-	accounts_settings.save()
-
-
 def make_purchase_invoice(**args):
 	pi = frappe.new_doc("Purchase Invoice")
 	args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 1efd3dc..f0880c1 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1051,7 +1051,7 @@
 
 	def make_tax_gl_entries(self, gl_entries):
 		enable_discount_accounting = cint(
-			frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+			frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
 		)
 
 		for tax in self.get("taxes"):
@@ -1097,7 +1097,7 @@
 	def make_item_gl_entries(self, gl_entries):
 		# income account gl entries
 		enable_discount_accounting = cint(
-			frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+			frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
 		)
 
 		for item in self.get("items"):
@@ -1276,7 +1276,7 @@
 	def enable_discount_accounting(self):
 		if not hasattr(self, "_enable_discount_accounting"):
 			self._enable_discount_accounting = cint(
-				frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
+				frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
 			)
 
 		return self._enable_discount_accounting
@@ -1466,7 +1466,9 @@
 			and self.base_rounding_adjustment
 			and not self.is_internal_transfer()
 		):
-			round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(self.company)
+			round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
+				self.company, "Sales Invoice", self.name
+			)
 
 			gl_entries.append(
 				self.get_gl_dict(
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index caa70d0..f2a696d 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -7,6 +7,7 @@
 import frappe
 from frappe.model.dynamic_links import get_dynamic_link_map
 from frappe.model.naming import make_autoname
+from frappe.tests.utils import change_settings
 from frappe.utils import add_days, flt, getdate, nowdate
 
 import erpnext
@@ -1977,6 +1978,13 @@
 			self.assertEqual(expected_values[gle.account][2], gle.credit)
 
 	def test_rounding_adjustment_3(self):
+		from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
+			create_dimension,
+			disable_dimension,
+		)
+
+		create_dimension()
+
 		si = create_sales_invoice(do_not_save=True)
 		si.items = []
 		for d in [(1122, 2), (1122.01, 1), (1122.01, 1)]:
@@ -2004,6 +2012,10 @@
 					"included_in_print_rate": 1,
 				},
 			)
+
+		si.cost_center = "_Test Cost Center 2 - _TC"
+		si.location = "Block 1"
+
 		si.save()
 		si.submit()
 		self.assertEqual(si.net_total, 4007.16)
@@ -2039,6 +2051,18 @@
 
 		self.assertEqual(debit_credit_diff, 0)
 
+		round_off_gle = frappe.db.get_value(
+			"GL Entry",
+			{"voucher_type": "Sales Invoice", "voucher_no": si.name, "account": "Round Off - _TC"},
+			["cost_center", "location"],
+			as_dict=1,
+		)
+
+		self.assertEqual(round_off_gle.cost_center, "_Test Cost Center 2 - _TC")
+		self.assertEqual(round_off_gle.location, "Block 1")
+
+		disable_dimension()
+
 	def test_sales_invoice_with_shipping_rule(self):
 		from erpnext.accounts.doctype.shipping_rule.test_shipping_rule import create_shipping_rule
 
@@ -2684,12 +2708,8 @@
 			sales_invoice.items[0].item_tax_template, "_Test Account Excise Duty @ 10 - _TC"
 		)
 
+	@change_settings("Selling Settings", {"enable_discount_accounting": 1})
 	def test_sales_invoice_with_discount_accounting_enabled(self):
-		from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
-			enable_discount_accounting,
-		)
-
-		enable_discount_accounting()
 
 		discount_account = create_account(
 			account_name="Discount Account",
@@ -2705,14 +2725,10 @@
 		]
 
 		check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
-		enable_discount_accounting(enable=0)
 
+	@change_settings("Selling Settings", {"enable_discount_accounting": 1})
 	def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
-		from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import (
-			enable_discount_accounting,
-		)
 
-		enable_discount_accounting()
 		additional_discount_account = create_account(
 			account_name="Discount Account",
 			parent_account="Indirect Expenses - _TC",
@@ -2743,7 +2759,6 @@
 		]
 
 		check_gl_entries(self, si.name, expected_gle, add_days(nowdate(), -1))
-		enable_discount_accounting(enable=0)
 
 	def test_asset_depreciation_on_sale_with_pro_rata(self):
 		"""
diff --git a/erpnext/accounts/doctype/tax_rule/tax_rule.py b/erpnext/accounts/doctype/tax_rule/tax_rule.py
index 27b78e9..5bfca96 100644
--- a/erpnext/accounts/doctype/tax_rule/tax_rule.py
+++ b/erpnext/accounts/doctype/tax_rule/tax_rule.py
@@ -163,10 +163,15 @@
 def get_tax_template(posting_date, args):
 	"""Get matching tax rule"""
 	args = frappe._dict(args)
+	from_date = to_date = posting_date
+	if not posting_date:
+		from_date = "1900-01-01"
+		to_date = "4000-01-01"
+
 	conditions = [
 		"""(from_date is null or from_date <= '{0}')
-		and (to_date is null or to_date >= '{0}')""".format(
-			posting_date
+		and (to_date is null or to_date >= '{1}')""".format(
+			from_date, to_date
 		)
 	]
 
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index f52e517..89034eb 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -355,7 +355,7 @@
 
 def make_round_off_gle(gl_map, debit_credit_diff, precision):
 	round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
-		gl_map[0].company
+		gl_map[0].company, gl_map[0].voucher_type, gl_map[0].voucher_no
 	)
 	round_off_account_exists = False
 	round_off_gle = frappe._dict()
@@ -392,14 +392,43 @@
 		}
 	)
 
+	update_accounting_dimensions(round_off_gle)
+
 	if not round_off_account_exists:
 		gl_map.append(round_off_gle)
 
 
-def get_round_off_account_and_cost_center(company):
+def update_accounting_dimensions(round_off_gle):
+	dimensions = get_accounting_dimensions()
+	meta = frappe.get_meta(round_off_gle["voucher_type"])
+	has_all_dimensions = True
+
+	for dimension in dimensions:
+		if not meta.has_field(dimension):
+			has_all_dimensions = False
+
+	if dimensions and has_all_dimensions:
+		dimension_values = frappe.db.get_value(
+			round_off_gle["voucher_type"], round_off_gle["voucher_no"], dimensions, as_dict=1
+		)
+
+		for dimension in dimensions:
+			round_off_gle[dimension] = dimension_values.get(dimension)
+
+
+def get_round_off_account_and_cost_center(company, voucher_type, voucher_no):
 	round_off_account, round_off_cost_center = frappe.get_cached_value(
 		"Company", company, ["round_off_account", "round_off_cost_center"]
 	) or [None, None]
+
+	meta = frappe.get_meta(voucher_type)
+
+	# Give first preference to parent cost center for round off GLE
+	if meta.has_field("cost_center"):
+		parent_cost_center = frappe.db.get_value(voucher_type, voucher_no, "cost_center")
+		if parent_cost_center:
+			round_off_cost_center = parent_cost_center
+
 	if not round_off_account:
 		frappe.throw(_("Please mention Round Off Account in Company"))
 
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index b0b3049..db741d9 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -831,9 +831,9 @@
 		"where "
 		"dl.link_doctype=%s "
 		"and dl.link_name=%s "
-		'and dl.parenttype="Address" '
+		"and dl.parenttype='Address' "
 		"and ifnull(ta.disabled, 0) = 0 and"
-		'(ta.address_type="Shipping" or ta.is_shipping_address=1) '
+		"(ta.address_type='Shipping' or ta.is_shipping_address=1) "
 		"order by ta.is_shipping_address desc, ta.address_type desc limit 1",
 		(doctype, name),
 	)
@@ -881,11 +881,11 @@
 		"""
 			SELECT dl.parent, c.is_primary_contact, c.is_billing_contact
 			FROM `tabDynamic Link` dl
-			INNER JOIN tabContact c ON c.name = dl.parent
+			INNER JOIN `tabContact` c ON c.name = dl.parent
 			WHERE
 				dl.link_doctype=%s AND
 				dl.link_name=%s AND
-				dl.parenttype = "Contact"
+				dl.parenttype = 'Contact'
 			ORDER BY is_primary_contact DESC, is_billing_contact DESC
 		""",
 		(doctype, name),
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 50321ba..89a9448 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -20,6 +20,7 @@
   "maintain_same_rate",
   "allow_multiple_items",
   "bill_for_rejected_quantity_in_purchase_invoice",
+  "enable_discount_accounting",
   "subcontract",
   "backflush_raw_materials_of_subcontract_based_on",
   "column_break_11",
@@ -133,6 +134,13 @@
   {
    "fieldname": "column_break_12",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
+   "fieldname": "enable_discount_accounting",
+   "fieldtype": "Check",
+   "label": "Enable Discount Accounting for Buying"
   }
  ],
  "icon": "fa fa-cog",
@@ -140,7 +148,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2022-01-27 17:57:58.367048",
+ "modified": "2022-04-14 15:56:42.340223",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Buying Settings",
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.py b/erpnext/buying/doctype/buying_settings/buying_settings.py
index 5507254..c52b59e 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.py
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.py
@@ -5,10 +5,15 @@
 
 
 import frappe
+from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.model.document import Document
+from frappe.utils import cint
 
 
 class BuyingSettings(Document):
+	def on_update(self):
+		self.toggle_discount_accounting_fields()
+
 	def validate(self):
 		for key in ["supplier_group", "supp_master_name", "maintain_same_rate", "buying_price_list"]:
 			frappe.db.set_default(key, self.get(key, ""))
@@ -21,3 +26,60 @@
 			self.get("supp_master_name") == "Naming Series",
 			hide_name_field=False,
 		)
+
+	def toggle_discount_accounting_fields(self):
+		enable_discount_accounting = cint(self.enable_discount_accounting)
+
+		make_property_setter(
+			"Purchase Invoice Item",
+			"discount_account",
+			"hidden",
+			not (enable_discount_accounting),
+			"Check",
+			validate_fields_for_doctype=False,
+		)
+		if enable_discount_accounting:
+			make_property_setter(
+				"Purchase Invoice Item",
+				"discount_account",
+				"mandatory_depends_on",
+				"eval: doc.discount_amount",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
+		else:
+			make_property_setter(
+				"Purchase Invoice Item",
+				"discount_account",
+				"mandatory_depends_on",
+				"",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
+
+		make_property_setter(
+			"Purchase Invoice",
+			"additional_discount_account",
+			"hidden",
+			not (enable_discount_accounting),
+			"Check",
+			validate_fields_for_doctype=False,
+		)
+		if enable_discount_accounting:
+			make_property_setter(
+				"Purchase Invoice",
+				"additional_discount_account",
+				"mandatory_depends_on",
+				"eval: doc.discount_amount",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
+		else:
+			make_property_setter(
+				"Purchase Invoice",
+				"additional_discount_account",
+				"mandatory_depends_on",
+				"",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index f20df09..78645e0 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1079,9 +1079,14 @@
 		return amount, base_amount
 
 	def make_discount_gl_entries(self, gl_entries):
-		enable_discount_accounting = cint(
-			frappe.db.get_single_value("Accounts Settings", "enable_discount_accounting")
-		)
+		if self.doctype == "Purchase Invoice":
+			enable_discount_accounting = cint(
+				frappe.db.get_single_value("Buying Settings", "enable_discount_accounting")
+			)
+		elif self.doctype == "Sales Invoice":
+			enable_discount_accounting = cint(
+				frappe.db.get_single_value("Selling Settings", "enable_discount_accounting")
+			)
 
 		if enable_discount_accounting:
 			if self.doctype == "Purchase Invoice":
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index 96c730c..19b4d68 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -54,11 +54,11 @@
 			self.calculate_totals()
 
 	def map_fields(self):
-		for field in self.meta.fields:
-			if not self.get(field.fieldname):
+		for field in self.meta.get_valid_columns():
+			if not self.get(field) and frappe.db.field_exists(self.opportunity_from, field):
 				try:
-					value = frappe.db.get_value(self.opportunity_from, self.party_name, field.fieldname)
-					frappe.db.set(self, field.fieldname, value)
+					value = frappe.db.get_value(self.opportunity_from, self.party_name, field)
+					frappe.db.set(self, field, value)
 				except Exception:
 					continue
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index fefb2e5..220ce1d 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -753,7 +753,7 @@
 				bom_item.include_item_in_manufacturing,
 				bom_item.sourced_by_supplier,
 				bom_item.stock_qty / ifnull(bom.quantity, 1) AS qty_consumed_per_unit
-			FROM `tabBOM Explosion Item` bom_item, tabBOM bom
+			FROM `tabBOM Explosion Item` bom_item, `tabBOM` bom
 			WHERE
 				bom_item.parent = bom.name
 				AND bom.name = %s
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 474383d..95be791 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -365,5 +365,6 @@
 erpnext.patches.v13_0.update_expense_claim_status_for_paid_advances
 erpnext.patches.v13_0.create_gst_custom_fields_in_quotation
 erpnext.patches.v13_0.copy_custom_field_filters_to_website_item
+erpnext.patches.v14_0.discount_accounting_separation
 erpnext.patches.v14_0.delete_employee_transfer_property_doctype
 erpnext.patches.v13_0.create_accounting_dimensions_in_orders
\ No newline at end of file
diff --git a/erpnext/patches/v14_0/discount_accounting_separation.py b/erpnext/patches/v14_0/discount_accounting_separation.py
new file mode 100644
index 0000000..fd49805
--- /dev/null
+++ b/erpnext/patches/v14_0/discount_accounting_separation.py
@@ -0,0 +1,9 @@
+import frappe
+
+
+def execute():
+	doc = frappe.get_doc("Accounts Settings")
+	discount_account = doc.enable_discount_accounting
+	if discount_account:
+		for doctype in ["Buying Settings", "Selling Settings"]:
+			frappe.db.set_value(doctype, doctype, "enable_discount_accounting", 1, update_modified=False)
diff --git a/erpnext/regional/india/e_invoice/utils.py b/erpnext/regional/india/e_invoice/utils.py
index a3abe86..0f73c5f 100644
--- a/erpnext/regional/india/e_invoice/utils.py
+++ b/erpnext/regional/india/e_invoice/utils.py
@@ -553,6 +553,7 @@
 		+ flt(value_details["CgstVal"])
 		+ flt(value_details["SgstVal"])
 		+ flt(value_details["IgstVal"])
+		+ flt(value_details["CesVal"])
 		+ flt(value_details["OthChrg"])
 		+ flt(value_details["RndOffAmt"])
 		- flt(value_details["Discount"])
diff --git a/erpnext/regional/india/setup.py b/erpnext/regional/india/setup.py
index 446faaa..062c2ef 100644
--- a/erpnext/regional/india/setup.py
+++ b/erpnext/regional/india/setup.py
@@ -1219,7 +1219,7 @@
 		try:
 			doc = frappe.get_doc(d)
 			doc.flags.ignore_permissions = True
-			doc.insert()
+			doc.insert(ignore_if_duplicate=True)
 		except frappe.NameError:
 			frappe.clear_messages()
 		except frappe.DuplicateEntryError:
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 7c4a3f6..005e24c 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -27,7 +27,8 @@
   "column_break_5",
   "allow_multiple_items",
   "allow_against_multiple_purchase_orders",
-  "hide_tax_id"
+  "hide_tax_id",
+  "enable_discount_accounting"
  ],
  "fields": [
   {
@@ -164,6 +165,13 @@
    "fieldname": "editable_bundle_item_rates",
    "fieldtype": "Check",
    "label": "Calculate Product Bundle Price based on Child Items' Rates"
+  },
+  {
+   "default": "0",
+   "description": "If enabled, additional ledger entries will be made for discounts in a separate Discount Account",
+   "fieldname": "enable_discount_accounting",
+   "fieldtype": "Check",
+   "label": "Enable Discount Accounting for Selling"
   }
  ],
  "icon": "fa fa-cog",
@@ -171,7 +179,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2022-02-04 15:41:59.939261",
+ "modified": "2022-04-14 16:01:29.405642",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Selling Settings",
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.py b/erpnext/selling/doctype/selling_settings/selling_settings.py
index 29e4712..6c09894 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.py
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.py
@@ -14,6 +14,7 @@
 	def on_update(self):
 		self.toggle_hide_tax_id()
 		self.toggle_editable_rate_for_bundle_items()
+		self.toggle_discount_accounting_fields()
 
 	def validate(self):
 		for key in [
@@ -58,3 +59,60 @@
 			"Check",
 			validate_fields_for_doctype=False,
 		)
+
+	def toggle_discount_accounting_fields(self):
+		enable_discount_accounting = cint(self.enable_discount_accounting)
+
+		make_property_setter(
+			"Sales Invoice Item",
+			"discount_account",
+			"hidden",
+			not (enable_discount_accounting),
+			"Check",
+			validate_fields_for_doctype=False,
+		)
+		if enable_discount_accounting:
+			make_property_setter(
+				"Sales Invoice Item",
+				"discount_account",
+				"mandatory_depends_on",
+				"eval: doc.discount_amount",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
+		else:
+			make_property_setter(
+				"Sales Invoice Item",
+				"discount_account",
+				"mandatory_depends_on",
+				"",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
+
+		make_property_setter(
+			"Sales Invoice",
+			"additional_discount_account",
+			"hidden",
+			not (enable_discount_accounting),
+			"Check",
+			validate_fields_for_doctype=False,
+		)
+		if enable_discount_accounting:
+			make_property_setter(
+				"Sales Invoice",
+				"additional_discount_account",
+				"mandatory_depends_on",
+				"eval: doc.discount_amount",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
+		else:
+			make_property_setter(
+				"Sales Invoice",
+				"additional_discount_account",
+				"mandatory_depends_on",
+				"",
+				"Code",
+				validate_fields_for_doctype=False,
+			)
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index 562f7b9..bcd31ad 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -5,6 +5,8 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
+from frappe.query_builder import Criterion
+from frappe.query_builder.functions import Cast_
 from frappe.utils import getdate
 
 
@@ -48,35 +50,57 @@
 			)
 
 	def check_duplicates(self):
-		conditions = (
-			"""where item_code = %(item_code)s and price_list = %(price_list)s and name != %(name)s"""
-		)
 
-		for field in [
+		item_price = frappe.qb.DocType("Item Price")
+
+		query = (
+			frappe.qb.from_(item_price)
+			.select(item_price.price_list_rate)
+			.where(
+				(item_price.item_code == self.item_code)
+				& (item_price.price_list == self.price_list)
+				& (item_price.name != self.name)
+			)
+		)
+		data_fields = (
 			"uom",
 			"valid_from",
 			"valid_upto",
-			"packing_unit",
 			"customer",
 			"supplier",
 			"batch_no",
-		]:
-			if self.get(field):
-				conditions += " and {0} = %({0})s ".format(field)
-			else:
-				conditions += "and (isnull({0}) or {0} = '')".format(field)
-
-		price_list_rate = frappe.db.sql(
-			"""
-				select price_list_rate
-				from `tabItem Price`
-				{conditions}
-			""".format(
-				conditions=conditions
-			),
-			self.as_dict(),
 		)
 
+		number_fields = ["packing_unit"]
+
+		for field in data_fields:
+			if self.get(field):
+				query = query.where(item_price[field] == self.get(field))
+			else:
+				query = query.where(
+					Criterion.any(
+						[
+							item_price[field].isnull(),
+							Cast_(item_price[field], "varchar") == "",
+						]
+					)
+				)
+
+		for field in number_fields:
+			if self.get(field):
+				query = query.where(item_price[field] == self.get(field))
+			else:
+				query = query.where(
+					Criterion.any(
+						[
+							item_price[field].isnull(),
+							item_price[field] == 0,
+						]
+					)
+				)
+
+		price_list_rate = query.run(as_dict=True)
+
 		if price_list_rate:
 			frappe.throw(
 				_(
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index c4aa8a4..27a6eaf 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -1167,7 +1167,7 @@
 			from `tabItem` i LEFT JOIN `tabItem Default` id ON i.name=id.parent and id.company=%s
 			where i.name=%s
 				and i.disabled=0
-				and (i.end_of_life is null or i.end_of_life='0000-00-00' or i.end_of_life > %s)""",
+				and (i.end_of_life is null or i.end_of_life<'1900-01-01' or i.end_of_life > %s)""",
 			(self.company, args.get("item_code"), nowdate()),
 			as_dict=1,
 		)
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 b4fac82..5850ec7 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
@@ -2,12 +2,12 @@
 # See license.txt
 
 import json
-from datetime import timedelta
 from uuid import uuid4
 
 import frappe
 from frappe.core.page.permission_manager.permission_manager import reset
 from frappe.custom.doctype.property_setter.property_setter import make_property_setter
+from frappe.query_builder.functions import CombineDatetime
 from frappe.tests.utils import FrappeTestCase
 from frappe.utils import add_days, today
 from frappe.utils.data import add_to_date
@@ -1126,6 +1126,63 @@
 		# original amount
 		self.assertEqual(50, _get_stock_credit(final_consumption))
 
+	def test_tie_breaking(self):
+		frappe.flags.dont_execute_stock_reposts = True
+		self.addCleanup(frappe.flags.pop, "dont_execute_stock_reposts")
+
+		item = make_item().name
+		warehouse = "_Test Warehouse - _TC"
+
+		posting_date = "2022-01-01"
+		posting_time = "00:00:01"
+		sle = frappe.qb.DocType("Stock Ledger Entry")
+
+		def ordered_qty_after_transaction():
+			return (
+				frappe.qb.from_(sle)
+				.select("qty_after_transaction")
+				.where((sle.item_code == item) & (sle.warehouse == warehouse) & (sle.is_cancelled == 0))
+				.orderby(CombineDatetime(sle.posting_date, sle.posting_time))
+				.orderby(sle.creation)
+			).run(pluck=True)
+
+		first = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=10,
+			posting_time=posting_time,
+			posting_date=posting_date,
+			do_not_submit=True,
+		)
+		second = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=1,
+			posting_date=posting_date,
+			posting_time=posting_time,
+			do_not_submit=True,
+		)
+
+		first.submit()
+		second.submit()
+
+		self.assertEqual([10, 11], ordered_qty_after_transaction())
+
+		first.cancel()
+		self.assertEqual([1], ordered_qty_after_transaction())
+
+		backdated = make_stock_entry(
+			item_code=item,
+			to_warehouse=warehouse,
+			qty=1,
+			posting_date="2021-01-01",
+			posting_time=posting_time,
+		)
+		self.assertEqual([1, 2], ordered_qty_after_transaction())
+
+		backdated.cancel()
+		self.assertEqual([1], ordered_qty_after_transaction())
+
 
 def create_repack_entry(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index a781479..7e5c231 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -8,9 +8,8 @@
 import frappe
 from frappe import _
 from frappe.model.meta import get_field_precision
-from frappe.query_builder.functions import Sum
+from frappe.query_builder.functions import CombineDatetime, Sum
 from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now, nowdate
-from pypika import CustomFunction
 
 import erpnext
 from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
@@ -1158,16 +1157,15 @@
 	item_code, warehouse, batch_no, posting_date, posting_time, creation=None
 ):
 
-	Timestamp = CustomFunction("timestamp", ["date", "time"])
-
 	sle = frappe.qb.DocType("Stock Ledger Entry")
 
-	timestamp_condition = Timestamp(sle.posting_date, sle.posting_time) < Timestamp(
+	timestamp_condition = CombineDatetime(sle.posting_date, sle.posting_time) < CombineDatetime(
 		posting_date, posting_time
 	)
 	if creation:
 		timestamp_condition |= (
-			Timestamp(sle.posting_date, sle.posting_time) == Timestamp(posting_date, posting_time)
+			CombineDatetime(sle.posting_date, sle.posting_time)
+			== CombineDatetime(posting_date, posting_time)
 		) & (sle.creation < creation)
 
 	batch_details = (
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index d40218e..2120304 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -7,6 +7,7 @@
 
 import frappe
 from frappe import _
+from frappe.query_builder.functions import CombineDatetime
 from frappe.utils import cstr, flt, get_link_to_form, nowdate, nowtime
 
 import erpnext
@@ -143,12 +144,10 @@
 
 
 def get_serial_nos_data_after_transactions(args):
-	from pypika import CustomFunction
 
 	serial_nos = set()
 	args = frappe._dict(args)
 	sle = frappe.qb.DocType("Stock Ledger Entry")
-	Timestamp = CustomFunction("timestamp", ["date", "time"])
 
 	stock_ledger_entries = (
 		frappe.qb.from_(sle)
@@ -157,7 +156,8 @@
 			(sle.item_code == args.item_code)
 			& (sle.warehouse == args.warehouse)
 			& (
-				Timestamp(sle.posting_date, sle.posting_time) < Timestamp(args.posting_date, args.posting_time)
+				CombineDatetime(sle.posting_date, sle.posting_time)
+				< CombineDatetime(args.posting_date, args.posting_time)
 			)
 			& (sle.is_cancelled == 0)
 		)