Merge pull request #37131 from ruthra-kumar/more_generic_customer_filter_in_ar_report

refactor: more generic filters in Accounts Receivable report
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 48337ce..d1a97f8 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -68,6 +68,6 @@
 
 wait $wkpid
 
-bench start &> bench_run_logs.txt &
+bench start &>> ~/frappe-bench/bench_start.log &
 CI=Yes bench build --app frappe &
 bench --site test_site reinstall --yes
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index aae2928..07b8de7 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -23,7 +23,7 @@
 
     services:
       mysql:
-        image: mariadb:10.3
+        image: mariadb:10.6
         env:
           MARIADB_ROOT_PASSWORD: 'root'
         ports:
@@ -45,9 +45,7 @@
       - name: Setup Python
         uses: "actions/setup-python@v4"
         with:
-          python-version: |
-              3.7
-              3.10
+          python-version: '3.10'
 
       - name: Setup Node
         uses: actions/setup-node@v2
@@ -102,40 +100,60 @@
       - name: Run Patch Tests
         run: |
           cd ~/frappe-bench/
-          wget https://erpnext.com/files/v10-erpnext.sql.gz
-          bench --site test_site --force restore ~/frappe-bench/v10-erpnext.sql.gz
+          bench remove-app payments --force
+          jq 'del(.install_apps)' ~/frappe-bench/sites/test_site/site_config.json > tmp.json
+          mv tmp.json ~/frappe-bench/sites/test_site/site_config.json
+
+          wget https://erpnext.com/files/v13-erpnext.sql.gz
+          bench --site test_site --force restore ~/frappe-bench/v13-erpnext.sql.gz
 
           git -C "apps/frappe" remote set-url upstream https://github.com/frappe/frappe.git
           git -C "apps/erpnext" remote set-url upstream https://github.com/frappe/erpnext.git
 
-          for version in $(seq 12 13)
-          do
-              echo "Updating to v$version"
-              branch_name="version-$version-hotfix"
 
-              git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
-              git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
+          function update_to_version() {
+            version=$1
 
-              git -C "apps/frappe" checkout -q -f $branch_name
-              git -C "apps/erpnext" checkout -q -f $branch_name
+            branch_name="version-$version-hotfix"
+            echo "Updating to v$version"
 
-              rm -rf ~/frappe-bench/env
-              bench setup env --python python3.7
-              bench pip install -e ./apps/payments
-              bench pip install -e ./apps/erpnext
+            # Fetch and checkout branches
+            git -C "apps/frappe" fetch --depth 1 upstream $branch_name:$branch_name
+            git -C "apps/erpnext" fetch --depth 1 upstream $branch_name:$branch_name
+            git -C "apps/frappe" checkout -q -f $branch_name
+            git -C "apps/erpnext" checkout -q -f $branch_name
 
-              bench --site test_site migrate
-          done
+            # Resetup env and install apps
+            pgrep honcho | xargs kill
+            rm -rf ~/frappe-bench/env
+            bench -v setup env
+            bench pip install -e ./apps/erpnext
+            bench start &>> ~/frappe-bench/bench_start.log &
 
+            bench --site test_site migrate
+          }
+
+          update_to_version 14
 
           echo "Updating to latest version"
           git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
           git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
 
+          pgrep honcho | xargs kill
           rm -rf ~/frappe-bench/env
-          bench -v setup env --python python3.10
-          bench pip install -e ./apps/payments
+          bench -v setup env
           bench pip install -e ./apps/erpnext
+          bench start &>> ~/frappe-bench/bench_start.log &
 
           bench --site test_site migrate
-          bench --site test_site install-app payments
+
+      - name: Show bench output
+        if: ${{ always() }}
+        run: |
+          cd ~/frappe-bench
+          cat bench_start.log || true
+          cd logs
+          for f in ./*.log*; do
+            echo "Printing log: $f";
+            cat $f
+          done
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index 2ce1125..559be06 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -123,6 +123,10 @@
           CI_BUILD_ID: ${{ github.run_id }}
           ORCHESTRATOR_URL: http://test-orchestrator.frappe.io
 
+      - name: Show bench output
+        if: ${{ always() }}
+        run: cat ~/frappe-bench/bench_start.log || true
+
       - name: Upload coverage data
         uses: actions/upload-artifact@v3
         with:
diff --git a/erpnext/accounts/doctype/account/account.js b/erpnext/accounts/doctype/account/account.js
index 3c0eb85..bcf7efc 100644
--- a/erpnext/accounts/doctype/account/account.js
+++ b/erpnext/accounts/doctype/account/account.js
@@ -137,9 +137,6 @@
 					args: {
 						old: frm.doc.name,
 						new: data.name,
-						is_group: frm.doc.is_group,
-						root_type: frm.doc.root_type,
-						company: frm.doc.company,
 					},
 					callback: function (r) {
 						if (!r.exc) {
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index c1eca72..02e6c20 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -18,6 +18,10 @@
 	pass
 
 
+class InvalidAccountMergeError(frappe.ValidationError):
+	pass
+
+
 class Account(NestedSet):
 	nsm_parent_field = "parent_account"
 
@@ -460,25 +464,34 @@
 
 
 @frappe.whitelist()
-def merge_account(old, new, is_group, root_type, company):
+def merge_account(old, new):
 	# Validate properties before merging
 	new_account = frappe.get_cached_doc("Account", new)
+	old_account = frappe.get_cached_doc("Account", old)
 
 	if not new_account:
 		throw(_("Account {0} does not exist").format(new))
 
-	if (new_account.is_group, new_account.root_type, new_account.company) != (
-		cint(is_group),
-		root_type,
-		company,
+	if (
+		cint(new_account.is_group),
+		new_account.root_type,
+		new_account.company,
+		cstr(new_account.account_currency),
+	) != (
+		cint(old_account.is_group),
+		old_account.root_type,
+		old_account.company,
+		cstr(old_account.account_currency),
 	):
 		throw(
-			_(
-				"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company"""
-			)
+			msg=_(
+				"""Merging is only possible if following properties are same in both records. Is Group, Root Type, Company and Account Currency"""
+			),
+			title=("Invalid Accounts"),
+			exc=InvalidAccountMergeError,
 		)
 
-	if is_group and new_account.parent_account == old:
+	if old_account.is_group and new_account.parent_account == old:
 		new_account.db_set("parent_account", frappe.get_cached_value("Account", old, "parent_account"))
 
 	frappe.rename_doc("Account", old, new, merge=1, force=1)
diff --git a/erpnext/accounts/doctype/account/test_account.py b/erpnext/accounts/doctype/account/test_account.py
index 62303bd..30eebef 100644
--- a/erpnext/accounts/doctype/account/test_account.py
+++ b/erpnext/accounts/doctype/account/test_account.py
@@ -7,7 +7,11 @@
 import frappe
 from frappe.test_runner import make_test_records
 
-from erpnext.accounts.doctype.account.account import merge_account, update_account_number
+from erpnext.accounts.doctype.account.account import (
+	InvalidAccountMergeError,
+	merge_account,
+	update_account_number,
+)
 from erpnext.stock import get_company_default_inventory_account, get_warehouse_account
 
 test_dependencies = ["Company"]
@@ -47,49 +51,53 @@
 		frappe.delete_doc("Account", "1211-11-4 - 6 - Debtors 1 - Test - - _TC")
 
 	def test_merge_account(self):
-		if not frappe.db.exists("Account", "Current Assets - _TC"):
-			acc = frappe.new_doc("Account")
-			acc.account_name = "Current Assets"
-			acc.is_group = 1
-			acc.parent_account = "Application of Funds (Assets) - _TC"
-			acc.company = "_Test Company"
-			acc.insert()
-		if not frappe.db.exists("Account", "Securities and Deposits - _TC"):
-			acc = frappe.new_doc("Account")
-			acc.account_name = "Securities and Deposits"
-			acc.parent_account = "Current Assets - _TC"
-			acc.is_group = 1
-			acc.company = "_Test Company"
-			acc.insert()
-		if not frappe.db.exists("Account", "Earnest Money - _TC"):
-			acc = frappe.new_doc("Account")
-			acc.account_name = "Earnest Money"
-			acc.parent_account = "Securities and Deposits - _TC"
-			acc.company = "_Test Company"
-			acc.insert()
-		if not frappe.db.exists("Account", "Cash In Hand - _TC"):
-			acc = frappe.new_doc("Account")
-			acc.account_name = "Cash In Hand"
-			acc.is_group = 1
-			acc.parent_account = "Current Assets - _TC"
-			acc.company = "_Test Company"
-			acc.insert()
-		if not frappe.db.exists("Account", "Accumulated Depreciation - _TC"):
-			acc = frappe.new_doc("Account")
-			acc.account_name = "Accumulated Depreciation"
-			acc.parent_account = "Fixed Assets - _TC"
-			acc.company = "_Test Company"
-			acc.account_type = "Accumulated Depreciation"
-			acc.insert()
+		create_account(
+			account_name="Current Assets",
+			is_group=1,
+			parent_account="Application of Funds (Assets) - _TC",
+			company="_Test Company",
+		)
 
-		doc = frappe.get_doc("Account", "Securities and Deposits - _TC")
+		create_account(
+			account_name="Securities and Deposits",
+			is_group=1,
+			parent_account="Current Assets - _TC",
+			company="_Test Company",
+		)
+
+		create_account(
+			account_name="Earnest Money",
+			parent_account="Securities and Deposits - _TC",
+			company="_Test Company",
+		)
+
+		create_account(
+			account_name="Cash In Hand",
+			is_group=1,
+			parent_account="Current Assets - _TC",
+			company="_Test Company",
+		)
+
+		create_account(
+			account_name="Receivable INR",
+			parent_account="Current Assets - _TC",
+			company="_Test Company",
+			account_currency="INR",
+		)
+
+		create_account(
+			account_name="Receivable USD",
+			parent_account="Current Assets - _TC",
+			company="_Test Company",
+			account_currency="USD",
+		)
+
 		parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
 
 		self.assertEqual(parent, "Securities and Deposits - _TC")
 
-		merge_account(
-			"Securities and Deposits - _TC", "Cash In Hand - _TC", doc.is_group, doc.root_type, doc.company
-		)
+		merge_account("Securities and Deposits - _TC", "Cash In Hand - _TC")
+
 		parent = frappe.db.get_value("Account", "Earnest Money - _TC", "parent_account")
 
 		# Parent account of the child account changes after merging
@@ -98,30 +106,28 @@
 		# Old account doesn't exist after merging
 		self.assertFalse(frappe.db.exists("Account", "Securities and Deposits - _TC"))
 
-		doc = frappe.get_doc("Account", "Current Assets - _TC")
-
 		# Raise error as is_group property doesn't match
 		self.assertRaises(
-			frappe.ValidationError,
+			InvalidAccountMergeError,
 			merge_account,
 			"Current Assets - _TC",
 			"Accumulated Depreciation - _TC",
-			doc.is_group,
-			doc.root_type,
-			doc.company,
 		)
 
-		doc = frappe.get_doc("Account", "Capital Stock - _TC")
-
 		# Raise error as root_type property doesn't match
 		self.assertRaises(
-			frappe.ValidationError,
+			InvalidAccountMergeError,
 			merge_account,
 			"Capital Stock - _TC",
 			"Softwares - _TC",
-			doc.is_group,
-			doc.root_type,
-			doc.company,
+		)
+
+		# Raise error as currency doesn't match
+		self.assertRaises(
+			InvalidAccountMergeError,
+			merge_account,
+			"Receivable INR - _TC",
+			"Receivable USD - _TC",
 		)
 
 	def test_account_sync(self):
@@ -400,11 +406,20 @@
 		"Account", filters={"account_name": kwargs.get("account_name"), "company": kwargs.get("company")}
 	)
 	if account:
-		return account
+		account = frappe.get_doc("Account", account)
+		account.update(
+			dict(
+				is_group=kwargs.get("is_group", 0),
+				parent_account=kwargs.get("parent_account"),
+			)
+		)
+		account.save()
+		return account.name
 	else:
 		account = frappe.get_doc(
 			dict(
 				doctype="Account",
+				is_group=kwargs.get("is_group", 0),
 				account_name=kwargs.get("account_name"),
 				account_type=kwargs.get("account_type"),
 				parent_account=kwargs.get("parent_account"),
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index cfe5e6e..3a2c3cb 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -265,20 +265,21 @@
 
 @frappe.whitelist()
 def get_dimensions(with_cost_center_and_project=False):
-	dimension_filters = frappe.db.sql(
-		"""
-		SELECT label, fieldname, document_type
-		FROM `tabAccounting Dimension`
-		WHERE disabled = 0
-	""",
-		as_dict=1,
-	)
 
-	default_dimensions = frappe.db.sql(
-		"""SELECT p.fieldname, c.company, c.default_dimension
-		FROM `tabAccounting Dimension Detail` c, `tabAccounting Dimension` p
-		WHERE c.parent = p.name""",
-		as_dict=1,
+	c = frappe.qb.DocType("Accounting Dimension Detail")
+	p = frappe.qb.DocType("Accounting Dimension")
+	dimension_filters = (
+		frappe.qb.from_(p)
+		.select(p.label, p.fieldname, p.document_type)
+		.where(p.disabled == 0)
+		.run(as_dict=1)
+	)
+	default_dimensions = (
+		frappe.qb.from_(c)
+		.inner_join(p)
+		.on(c.parent == p.name)
+		.select(p.fieldname, c.company, c.default_dimension)
+		.run(as_dict=1)
 	)
 
 	if isinstance(with_cost_center_and_project, str):
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index 25ef2ea..cb7f5f5 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -84,12 +84,22 @@
 	frappe.set_user("Administrator")
 
 	if not frappe.db.exists("Accounting Dimension", {"document_type": "Department"}):
-		frappe.get_doc(
+		dimension = frappe.get_doc(
 			{
 				"doctype": "Accounting Dimension",
 				"document_type": "Department",
 			}
-		).insert()
+		)
+		dimension.append(
+			"dimension_defaults",
+			{
+				"company": "_Test Company",
+				"reference_document": "Department",
+				"default_dimension": "_Test Department - _TC",
+			},
+		)
+		dimension.insert()
+		dimension.save()
 	else:
 		dimension = frappe.get_doc("Accounting Dimension", "Department")
 		dimension.disabled = 0
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
index 3da5ac3..9a7a9a3 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.py
@@ -7,7 +7,9 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
+from frappe.query_builder.custom import ConstantColumn
 from frappe.utils import cint, flt
+from pypika.terms import Parameter
 
 from erpnext import get_default_cost_center
 from erpnext.accounts.doctype.bank_transaction.bank_transaction import get_total_allocated_amount
@@ -15,7 +17,7 @@
 	get_amounts_not_reflected_in_system,
 	get_entries,
 )
-from erpnext.accounts.utils import get_balance_on
+from erpnext.accounts.utils import get_account_currency, get_balance_on
 
 
 class BankReconciliationTool(Document):
@@ -283,68 +285,68 @@
 	to_reference_date=None,
 ):
 	frappe.flags.auto_reconcile_vouchers = True
-	document_types = ["payment_entry", "journal_entry"]
+	reconciled, partially_reconciled = set(), set()
+
 	bank_transactions = get_bank_transactions(bank_account)
-	matched_transaction = []
 	for transaction in bank_transactions:
 		linked_payments = get_linked_payments(
 			transaction.name,
-			document_types,
+			["payment_entry", "journal_entry"],
 			from_date,
 			to_date,
 			filter_by_reference_date,
 			from_reference_date,
 			to_reference_date,
 		)
-		vouchers = []
-		for r in linked_payments:
-			vouchers.append(
-				{
-					"payment_doctype": r[1],
-					"payment_name": r[2],
-					"amount": r[4],
-				}
-			)
-		transaction = frappe.get_doc("Bank Transaction", transaction.name)
-		account = frappe.db.get_value("Bank Account", transaction.bank_account, "account")
-		matched_trans = 0
-		for voucher in vouchers:
-			gl_entry = frappe.db.get_value(
-				"GL Entry",
-				dict(
-					account=account, voucher_type=voucher["payment_doctype"], voucher_no=voucher["payment_name"]
-				),
-				["credit", "debit"],
-				as_dict=1,
-			)
-			gl_amount, transaction_amount = (
-				(gl_entry.credit, transaction.deposit)
-				if gl_entry.credit > 0
-				else (gl_entry.debit, transaction.withdrawal)
-			)
-			allocated_amount = gl_amount if gl_amount >= transaction_amount else transaction_amount
-			transaction.append(
-				"payment_entries",
-				{
-					"payment_document": voucher["payment_doctype"],
-					"payment_entry": voucher["payment_name"],
-					"allocated_amount": allocated_amount,
+
+		if not linked_payments:
+			continue
+
+		vouchers = list(
+			map(
+				lambda entry: {
+					"payment_doctype": entry.get("doctype"),
+					"payment_name": entry.get("name"),
+					"amount": entry.get("paid_amount"),
 				},
+				linked_payments,
 			)
-			matched_transaction.append(str(transaction.name))
-		transaction.save()
-		transaction.update_allocations()
-	matched_transaction_len = len(set(matched_transaction))
-	if matched_transaction_len == 0:
-		frappe.msgprint(_("No matching references found for auto reconciliation"))
-	elif matched_transaction_len == 1:
-		frappe.msgprint(_("{0} transaction is reconcilied").format(matched_transaction_len))
-	else:
-		frappe.msgprint(_("{0} transactions are reconcilied").format(matched_transaction_len))
+		)
+
+		updated_transaction = reconcile_vouchers(transaction.name, json.dumps(vouchers))
+
+		if updated_transaction.status == "Reconciled":
+			reconciled.add(updated_transaction.name)
+		elif flt(transaction.unallocated_amount) != flt(updated_transaction.unallocated_amount):
+			# Partially reconciled (status = Unreconciled & unallocated amount changed)
+			partially_reconciled.add(updated_transaction.name)
+
+	alert_message, indicator = get_auto_reconcile_message(partially_reconciled, reconciled)
+	frappe.msgprint(title=_("Auto Reconciliation"), msg=alert_message, indicator=indicator)
 
 	frappe.flags.auto_reconcile_vouchers = False
+	return reconciled, partially_reconciled
 
-	return frappe.get_doc("Bank Transaction", transaction.name)
+
+def get_auto_reconcile_message(partially_reconciled, reconciled):
+	"""Returns alert message and indicator for auto reconciliation depending on result state."""
+	alert_message, indicator = "", "blue"
+	if not partially_reconciled and not reconciled:
+		alert_message = _("No matches occurred via auto reconciliation")
+		return alert_message, indicator
+
+	indicator = "green"
+	if reconciled:
+		alert_message += _("{0} Transaction(s) Reconciled").format(len(reconciled))
+		alert_message += "<br>"
+
+	if partially_reconciled:
+		alert_message += _("{0} {1} Partially Reconciled").format(
+			len(partially_reconciled),
+			_("Transactions") if len(partially_reconciled) > 1 else _("Transaction"),
+		)
+
+	return alert_message, indicator
 
 
 @frappe.whitelist()
@@ -390,19 +392,13 @@
 	"Look up & subtract any existing Bank Transaction allocations"
 	copied = []
 	for voucher in vouchers:
-		rows = get_total_allocated_amount(voucher[1], voucher[2])
-		amount = None
-		for row in rows:
-			if row["gl_account"] == gl_account:
-				amount = row["total"]
-				break
+		rows = get_total_allocated_amount(voucher.get("doctype"), voucher.get("name"))
+		filtered_row = list(filter(lambda row: row.get("gl_account") == gl_account, rows))
 
-		if amount:
-			l = list(voucher)
-			l[3] -= amount
-			copied.append(tuple(l))
-		else:
-			copied.append(voucher)
+		if amount := None if not filtered_row else filtered_row[0]["total"]:
+			voucher["paid_amount"] -= amount
+
+		copied.append(voucher)
 	return copied
 
 
@@ -418,6 +414,18 @@
 	to_reference_date,
 ):
 	exact_match = True if "exact_match" in document_types else False
+	queries = get_queries(
+		bank_account,
+		company,
+		transaction,
+		document_types,
+		from_date,
+		to_date,
+		filter_by_reference_date,
+		from_reference_date,
+		to_reference_date,
+		exact_match,
+	)
 
 	filters = {
 		"amount": transaction.unallocated_amount,
@@ -429,30 +437,15 @@
 	}
 
 	matching_vouchers = []
+	for query in queries:
+		matching_vouchers.extend(frappe.db.sql(query, filters, as_dict=True))
 
-	# get matching vouchers from all the apps
-	for method_name in frappe.get_hooks("get_matching_vouchers_for_bank_reconciliation"):
-		matching_vouchers.extend(
-			frappe.get_attr(method_name)(
-				bank_account,
-				company,
-				transaction,
-				document_types,
-				from_date,
-				to_date,
-				filter_by_reference_date,
-				from_reference_date,
-				to_reference_date,
-				exact_match,
-				filters,
-			)
-			or []
-		)
-
-	return sorted(matching_vouchers, key=lambda x: x[0], reverse=True) if matching_vouchers else []
+	return (
+		sorted(matching_vouchers, key=lambda x: x["rank"], reverse=True) if matching_vouchers else []
+	)
 
 
-def get_matching_vouchers_for_bank_reconciliation(
+def get_queries(
 	bank_account,
 	company,
 	transaction,
@@ -463,7 +456,6 @@
 	from_reference_date,
 	to_reference_date,
 	exact_match,
-	filters,
 ):
 	# get queries to get matching vouchers
 	account_from_to = "paid_to" if transaction.deposit > 0.0 else "paid_from"
@@ -488,17 +480,7 @@
 			or []
 		)
 
-	vouchers = []
-
-	for query in queries:
-		vouchers.extend(
-			frappe.db.sql(
-				query,
-				filters,
-			)
-		)
-
-	return vouchers
+	return queries
 
 
 def get_matching_queries(
@@ -515,6 +497,8 @@
 	to_reference_date,
 ):
 	queries = []
+	currency = get_account_currency(bank_account)
+
 	if "payment_entry" in document_types:
 		query = get_pe_matching_query(
 			exact_match,
@@ -541,12 +525,12 @@
 		queries.append(query)
 
 	if transaction.deposit > 0.0 and "sales_invoice" in document_types:
-		query = get_si_matching_query(exact_match)
+		query = get_si_matching_query(exact_match, currency)
 		queries.append(query)
 
 	if transaction.withdrawal > 0.0:
 		if "purchase_invoice" in document_types:
-			query = get_pi_matching_query(exact_match)
+			query = get_pi_matching_query(exact_match, currency)
 			queries.append(query)
 
 	if "bank_transaction" in document_types:
@@ -560,33 +544,48 @@
 	# get matching bank transaction query
 	# find bank transactions in the same bank account with opposite sign
 	# same bank account must have same company and currency
+	bt = frappe.qb.DocType("Bank Transaction")
+
 	field = "deposit" if transaction.withdrawal > 0.0 else "withdrawal"
+	amount_equality = getattr(bt, field) == transaction.unallocated_amount
+	amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
+	amount_condition = amount_equality if exact_match else getattr(bt, field) > 0.0
 
-	return f"""
+	ref_rank = (
+		frappe.qb.terms.Case().when(bt.reference_number == transaction.reference_number, 1).else_(0)
+	)
+	unallocated_rank = (
+		frappe.qb.terms.Case().when(bt.unallocated_amount == transaction.unallocated_amount, 1).else_(0)
+	)
 
-		SELECT
-			(CASE WHEN reference_number = %(reference_no)s THEN 1 ELSE 0 END
-			+ CASE WHEN {field} = %(amount)s THEN 1 ELSE 0 END
-			+ CASE WHEN ( party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
-			+ CASE WHEN unallocated_amount = %(amount)s THEN 1 ELSE 0 END
-			+ 1) AS rank,
-			'Bank Transaction' AS doctype,
-			name,
-			unallocated_amount AS paid_amount,
-			reference_number AS reference_no,
-			date AS reference_date,
-			party,
-			party_type,
-			date AS posting_date,
-			currency
-		FROM
-			`tabBank Transaction`
-		WHERE
-			status != 'Reconciled'
-			AND name != '{transaction.name}'
-			AND bank_account = '{transaction.bank_account}'
-			AND {field} {'= %(amount)s' if exact_match else '> 0.0'}
-	"""
+	party_condition = (
+		(bt.party_type == transaction.party_type)
+		& (bt.party == transaction.party)
+		& bt.party.isnotnull()
+	)
+	party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
+
+	query = (
+		frappe.qb.from_(bt)
+		.select(
+			(ref_rank + amount_rank + party_rank + unallocated_rank + 1).as_("rank"),
+			ConstantColumn("Bank Transaction").as_("doctype"),
+			bt.name,
+			bt.unallocated_amount.as_("paid_amount"),
+			bt.reference_number.as_("reference_no"),
+			bt.date.as_("reference_date"),
+			bt.party,
+			bt.party_type,
+			bt.date.as_("posting_date"),
+			bt.currency,
+		)
+		.where(bt.status != "Reconciled")
+		.where(bt.name != transaction.name)
+		.where(bt.bank_account == transaction.bank_account)
+		.where(amount_condition)
+		.where(bt.docstatus == 1)
+	)
+	return str(query)
 
 
 def get_pe_matching_query(
@@ -600,45 +599,56 @@
 	to_reference_date,
 ):
 	# get matching payment entries query
-	if transaction.deposit > 0.0:
-		currency_field = "paid_to_account_currency as currency"
-	else:
-		currency_field = "paid_from_account_currency as currency"
-	filter_by_date = f"AND posting_date between '{from_date}' and '{to_date}'"
-	order_by = " posting_date"
-	filter_by_reference_no = ""
+	to_from = "to" if transaction.deposit > 0.0 else "from"
+	currency_field = f"paid_{to_from}_account_currency"
+	payment_type = "Receive" if transaction.deposit > 0.0 else "Pay"
+	pe = frappe.qb.DocType("Payment Entry")
+
+	ref_condition = pe.reference_no == transaction.reference_number
+	ref_rank = frappe.qb.terms.Case().when(ref_condition, 1).else_(0)
+
+	amount_equality = pe.paid_amount == transaction.unallocated_amount
+	amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
+	amount_condition = amount_equality if exact_match else pe.paid_amount > 0.0
+
+	party_condition = (
+		(pe.party_type == transaction.party_type)
+		& (pe.party == transaction.party)
+		& pe.party.isnotnull()
+	)
+	party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
+
+	filter_by_date = pe.posting_date.between(from_date, to_date)
 	if cint(filter_by_reference_date):
-		filter_by_date = f"AND reference_date between '{from_reference_date}' and '{to_reference_date}'"
-		order_by = " reference_date"
+		filter_by_date = pe.reference_date.between(from_reference_date, to_reference_date)
+
+	query = (
+		frappe.qb.from_(pe)
+		.select(
+			(ref_rank + amount_rank + party_rank + 1).as_("rank"),
+			ConstantColumn("Payment Entry").as_("doctype"),
+			pe.name,
+			pe.paid_amount,
+			pe.reference_no,
+			pe.reference_date,
+			pe.party,
+			pe.party_type,
+			pe.posting_date,
+			getattr(pe, currency_field).as_("currency"),
+		)
+		.where(pe.docstatus == 1)
+		.where(pe.payment_type.isin([payment_type, "Internal Transfer"]))
+		.where(pe.clearance_date.isnull())
+		.where(getattr(pe, account_from_to) == Parameter("%(bank_account)s"))
+		.where(amount_condition)
+		.where(filter_by_date)
+		.orderby(pe.reference_date if cint(filter_by_reference_date) else pe.posting_date)
+	)
+
 	if frappe.flags.auto_reconcile_vouchers == True:
-		filter_by_reference_no = f"AND reference_no = '{transaction.reference_number}'"
-	return f"""
-		SELECT
-			(CASE WHEN reference_no=%(reference_no)s THEN 1 ELSE 0 END
-			+ CASE WHEN (party_type = %(party_type)s AND party = %(party)s ) THEN 1 ELSE 0 END
-			+ CASE WHEN paid_amount = %(amount)s THEN 1 ELSE 0 END
-			+ 1 ) AS rank,
-			'Payment Entry' as doctype,
-			name,
-			paid_amount,
-			reference_no,
-			reference_date,
-			party,
-			party_type,
-			posting_date,
-			{currency_field}
-		FROM
-			`tabPayment Entry`
-		WHERE
-			docstatus = 1
-			AND payment_type IN (%(payment_type)s, 'Internal Transfer')
-			AND ifnull(clearance_date, '') = ""
-			AND {account_from_to} = %(bank_account)s
-			AND paid_amount {'= %(amount)s' if exact_match else '> 0.0'}
-			{filter_by_date}
-			{filter_by_reference_no}
-		order by{order_by}
-	"""
+		query = query.where(ref_condition)
+
+	return str(query)
 
 
 def get_je_matching_query(
@@ -655,100 +665,121 @@
 	# So one bank could have both types of bank accounts like asset and liability
 	# So cr_or_dr should be judged only on basis of withdrawal and deposit and not account type
 	cr_or_dr = "credit" if transaction.withdrawal > 0.0 else "debit"
-	filter_by_date = f"AND je.posting_date between '{from_date}' and '{to_date}'"
-	order_by = " je.posting_date"
-	filter_by_reference_no = ""
+	je = frappe.qb.DocType("Journal Entry")
+	jea = frappe.qb.DocType("Journal Entry Account")
+
+	ref_condition = je.cheque_no == transaction.reference_number
+	ref_rank = frappe.qb.terms.Case().when(ref_condition, 1).else_(0)
+
+	amount_field = f"{cr_or_dr}_in_account_currency"
+	amount_equality = getattr(jea, amount_field) == transaction.unallocated_amount
+	amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
+
+	filter_by_date = je.posting_date.between(from_date, to_date)
 	if cint(filter_by_reference_date):
-		filter_by_date = f"AND je.cheque_date between '{from_reference_date}' and '{to_reference_date}'"
-		order_by = " je.cheque_date"
-	if frappe.flags.auto_reconcile_vouchers == True:
-		filter_by_reference_no = f"AND je.cheque_no = '{transaction.reference_number}'"
-	return f"""
-		SELECT
-			(CASE WHEN je.cheque_no=%(reference_no)s THEN 1 ELSE 0 END
-			+ CASE WHEN jea.{cr_or_dr}_in_account_currency = %(amount)s THEN 1 ELSE 0 END
-			+ 1) AS rank ,
-			'Journal Entry' AS doctype,
+		filter_by_date = je.cheque_date.between(from_reference_date, to_reference_date)
+
+	query = (
+		frappe.qb.from_(jea)
+		.join(je)
+		.on(jea.parent == je.name)
+		.select(
+			(ref_rank + amount_rank + 1).as_("rank"),
+			ConstantColumn("Journal Entry").as_("doctype"),
 			je.name,
-			jea.{cr_or_dr}_in_account_currency AS paid_amount,
-			je.cheque_no AS reference_no,
-			je.cheque_date AS reference_date,
-			je.pay_to_recd_from AS party,
+			getattr(jea, amount_field).as_("paid_amount"),
+			je.cheque_no.as_("reference_no"),
+			je.cheque_date.as_("reference_date"),
+			je.pay_to_recd_from.as_("party"),
 			jea.party_type,
 			je.posting_date,
-			jea.account_currency AS currency
-		FROM
-			`tabJournal Entry Account` AS jea
-		JOIN
-			`tabJournal Entry` AS je
-		ON
-			jea.parent = je.name
-		WHERE
-			je.docstatus = 1
-			AND je.voucher_type NOT IN ('Opening Entry')
-			AND (je.clearance_date IS NULL OR je.clearance_date='0000-00-00')
-			AND jea.account = %(bank_account)s
-			AND jea.{cr_or_dr}_in_account_currency {'= %(amount)s' if exact_match else '> 0.0'}
-			AND je.docstatus = 1
-			{filter_by_date}
-			{filter_by_reference_no}
-			order by {order_by}
-	"""
+			jea.account_currency.as_("currency"),
+		)
+		.where(je.docstatus == 1)
+		.where(je.voucher_type != "Opening Entry")
+		.where(je.clearance_date.isnull())
+		.where(jea.account == Parameter("%(bank_account)s"))
+		.where(amount_equality if exact_match else getattr(jea, amount_field) > 0.0)
+		.where(je.docstatus == 1)
+		.where(filter_by_date)
+		.orderby(je.cheque_date if cint(filter_by_reference_date) else je.posting_date)
+	)
+
+	if frappe.flags.auto_reconcile_vouchers == True:
+		query = query.where(ref_condition)
+
+	return str(query)
 
 
-def get_si_matching_query(exact_match):
+def get_si_matching_query(exact_match, currency):
 	# get matching sales invoice query
-	return f"""
-		SELECT
-			( CASE WHEN si.customer = %(party)s  THEN 1 ELSE 0 END
-			+ CASE WHEN sip.amount = %(amount)s THEN 1 ELSE 0 END
-			+ 1 ) AS rank,
-			'Sales Invoice' as doctype,
+	si = frappe.qb.DocType("Sales Invoice")
+	sip = frappe.qb.DocType("Sales Invoice Payment")
+
+	amount_equality = sip.amount == Parameter("%(amount)s")
+	amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
+	amount_condition = amount_equality if exact_match else sip.amount > 0.0
+
+	party_condition = si.customer == Parameter("%(party)s")
+	party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
+
+	query = (
+		frappe.qb.from_(sip)
+		.join(si)
+		.on(sip.parent == si.name)
+		.select(
+			(party_rank + amount_rank + 1).as_("rank"),
+			ConstantColumn("Sales Invoice").as_("doctype"),
 			si.name,
-			sip.amount as paid_amount,
-			'' as reference_no,
-			'' as reference_date,
-			si.customer as party,
-			'Customer' as party_type,
+			sip.amount.as_("paid_amount"),
+			ConstantColumn("").as_("reference_no"),
+			ConstantColumn("").as_("reference_date"),
+			si.customer.as_("party"),
+			ConstantColumn("Customer").as_("party_type"),
 			si.posting_date,
-			si.currency
+			si.currency,
+		)
+		.where(si.docstatus == 1)
+		.where(sip.clearance_date.isnull())
+		.where(sip.account == Parameter("%(bank_account)s"))
+		.where(amount_condition)
+		.where(si.currency == currency)
+	)
 
-		FROM
-			`tabSales Invoice Payment` as sip
-		JOIN
-			`tabSales Invoice` as si
-		ON
-			sip.parent = si.name
-		WHERE
-			si.docstatus = 1
-			AND (sip.clearance_date is null or sip.clearance_date='0000-00-00')
-			AND sip.account = %(bank_account)s
-			AND sip.amount {'= %(amount)s' if exact_match else '> 0.0'}
-	"""
+	return str(query)
 
 
-def get_pi_matching_query(exact_match):
+def get_pi_matching_query(exact_match, currency):
 	# get matching purchase invoice query when they are also used as payment entries (is_paid)
-	return f"""
-		SELECT
-			( CASE WHEN supplier = %(party)s THEN 1 ELSE 0 END
-			+ CASE WHEN paid_amount = %(amount)s THEN 1 ELSE 0 END
-			+ 1 ) AS rank,
-			'Purchase Invoice' as doctype,
-			name,
-			paid_amount,
-			'' as reference_no,
-			'' as reference_date,
-			supplier as party,
-			'Supplier' as party_type,
-			posting_date,
-			currency
-		FROM
-			`tabPurchase Invoice`
-		WHERE
-			docstatus = 1
-			AND is_paid = 1
-			AND ifnull(clearance_date, '') = ""
-			AND cash_bank_account = %(bank_account)s
-			AND paid_amount {'= %(amount)s' if exact_match else '> 0.0'}
-	"""
+	purchase_invoice = frappe.qb.DocType("Purchase Invoice")
+
+	amount_equality = purchase_invoice.paid_amount == Parameter("%(amount)s")
+	amount_rank = frappe.qb.terms.Case().when(amount_equality, 1).else_(0)
+	amount_condition = amount_equality if exact_match else purchase_invoice.paid_amount > 0.0
+
+	party_condition = purchase_invoice.supplier == Parameter("%(party)s")
+	party_rank = frappe.qb.terms.Case().when(party_condition, 1).else_(0)
+
+	query = (
+		frappe.qb.from_(purchase_invoice)
+		.select(
+			(party_rank + amount_rank + 1).as_("rank"),
+			ConstantColumn("Purchase Invoice").as_("doctype"),
+			purchase_invoice.name,
+			purchase_invoice.paid_amount,
+			ConstantColumn("").as_("reference_no"),
+			ConstantColumn("").as_("reference_date"),
+			purchase_invoice.supplier.as_("party"),
+			ConstantColumn("Supplier").as_("party_type"),
+			purchase_invoice.posting_date,
+			purchase_invoice.currency,
+		)
+		.where(purchase_invoice.docstatus == 1)
+		.where(purchase_invoice.is_paid == 1)
+		.where(purchase_invoice.clearance_date.isnull())
+		.where(purchase_invoice.cash_bank_account == Parameter("%(bank_account)s"))
+		.where(amount_condition)
+		.where(purchase_invoice.currency == currency)
+	)
+
+	return str(query)
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py
index 599ced5..5a6bb69 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py
@@ -1,9 +1,100 @@
 # Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
-# import frappe
 import unittest
 
+import frappe
+from frappe import qb
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_days, flt, getdate, today
 
-class TestBankReconciliationTool(unittest.TestCase):
-	pass
+from erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool import (
+	auto_reconcile_vouchers,
+	get_bank_transactions,
+)
+from erpnext.accounts.doctype.payment_entry.test_payment_entry import create_payment_entry
+from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
+
+
+class TestBankReconciliationTool(AccountsTestMixin, FrappeTestCase):
+	def setUp(self):
+		self.create_company()
+		self.create_customer()
+		self.clear_old_entries()
+		bank_dt = qb.DocType("Bank")
+		q = qb.from_(bank_dt).delete().where(bank_dt.name == "HDFC").run()
+		self.create_bank_account()
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def create_bank_account(self):
+		bank = frappe.get_doc(
+			{
+				"doctype": "Bank",
+				"bank_name": "HDFC",
+			}
+		).save()
+
+		self.bank_account = (
+			frappe.get_doc(
+				{
+					"doctype": "Bank Account",
+					"account_name": "HDFC _current_",
+					"bank": bank,
+					"is_company_account": True,
+					"account": self.bank,  # account from Chart of Accounts
+				}
+			)
+			.insert()
+			.name
+		)
+
+	def test_auto_reconcile(self):
+		# make payment
+		from_date = add_days(today(), -1)
+		to_date = today()
+		payment = create_payment_entry(
+			company=self.company,
+			posting_date=from_date,
+			payment_type="Receive",
+			party_type="Customer",
+			party=self.customer,
+			paid_from=self.debit_to,
+			paid_to=self.bank,
+			paid_amount=100,
+		).save()
+		payment.reference_no = "123"
+		payment = payment.save().submit()
+
+		# make bank transaction
+		bank_transaction = (
+			frappe.get_doc(
+				{
+					"doctype": "Bank Transaction",
+					"date": to_date,
+					"deposit": 100,
+					"bank_account": self.bank_account,
+					"reference_number": "123",
+				}
+			)
+			.save()
+			.submit()
+		)
+
+		# assert API output pre reconciliation
+		transactions = get_bank_transactions(self.bank_account, from_date, to_date)
+		self.assertEqual(len(transactions), 1)
+		self.assertEqual(transactions[0].name, bank_transaction.name)
+
+		# auto reconcile
+		auto_reconcile_vouchers(
+			bank_account=self.bank_account,
+			from_date=from_date,
+			to_date=to_date,
+			filter_by_reference_date=False,
+		)
+
+		# assert API output post reconciliation
+		transactions = get_bank_transactions(self.bank_account, from_date, to_date)
+		self.assertEqual(len(transactions), 0)
diff --git a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
index 59905da..0c328ff 100644
--- a/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/test_bank_transaction.py
@@ -47,7 +47,7 @@
 			from_date=bank_transaction.date,
 			to_date=utils.today(),
 		)
-		self.assertTrue(linked_payments[0][6] == "Conrad Electronic")
+		self.assertTrue(linked_payments[0]["party"] == "Conrad Electronic")
 
 	# This test validates a simple reconciliation leading to the clearance of the bank transaction and the payment
 	def test_reconcile(self):
@@ -93,7 +93,7 @@
 			from_date=bank_transaction.date,
 			to_date=utils.today(),
 		)
-		self.assertTrue(linked_payments[0][3])
+		self.assertTrue(linked_payments[0]["paid_amount"])
 
 	# Check error if already reconciled
 	def test_already_reconciled(self):
@@ -188,7 +188,7 @@
 		repayment_entry = create_loan_and_repayment()
 
 		linked_payments = get_linked_payments(bank_transaction.name, ["loan_repayment", "exact_match"])
-		self.assertEqual(linked_payments[0][2], repayment_entry.name)
+		self.assertEqual(linked_payments[0]["name"], repayment_entry.name)
 
 
 @if_lending_app_installed
diff --git a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
index 381083b..362d273 100644
--- a/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
+++ b/erpnext/accounts/doctype/ledger_merge/ledger_merge.py
@@ -48,9 +48,6 @@
 				merge_account(
 					row.account,
 					ledger_merge.account,
-					ledger_merge.is_group,
-					ledger_merge.root_type,
-					ledger_merge.company,
 				)
 				row.db_set("merged", 1)
 				frappe.db.commit()
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index feacb0f..b16b03e 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -1588,6 +1588,14 @@
 				fieldname, args.get(date_fields[0]), args.get(date_fields[1])
 			)
 			posting_and_due_date.append(ple[fieldname][args.get(date_fields[0]) : args.get(date_fields[1])])
+		elif args.get(date_fields[0]):
+			# if only from date is supplied
+			condition += " and {0} >= '{1}'".format(fieldname, args.get(date_fields[0]))
+			posting_and_due_date.append(ple[fieldname].gte(args.get(date_fields[0])))
+		elif args.get(date_fields[1]):
+			# if only to date is supplied
+			condition += " and {0} <= '{1}'".format(fieldname, args.get(date_fields[1]))
+			posting_and_due_date.append(ple[fieldname].lte(args.get(date_fields[1])))
 
 	if args.get("company"):
 		condition += " and company = {0}".format(frappe.db.escape(args.get("company")))
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 93ba90a..62b342a 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,6 +5,10 @@
 
 import frappe
 
+from erpnext.accounts.doctype.accounting_dimension.test_accounting_dimension import (
+	create_dimension,
+	disable_dimension,
+)
 from erpnext.accounts.doctype.pos_closing_entry.pos_closing_entry import (
 	make_closing_entry_from_opening,
 )
@@ -140,6 +144,43 @@
 		pos_inv1.load_from_db()
 		self.assertEqual(pos_inv1.status, "Paid")
 
+	def test_pos_closing_for_required_accounting_dimension_in_pos_profile(self):
+		"""
+		test case to check whether we can create POS Closing Entry without mandatory accounting dimension
+		"""
+
+		create_dimension()
+		pos_profile = make_pos_profile(do_not_insert=1, do_not_set_accounting_dimension=1)
+
+		self.assertRaises(frappe.ValidationError, pos_profile.insert)
+
+		pos_profile.location = "Block 1"
+		pos_profile.insert()
+		self.assertTrue(frappe.db.exists("POS Profile", pos_profile.name))
+
+		test_user = init_user_and_profile(do_not_create_pos_profile=1)
+
+		opening_entry = create_opening_entry(pos_profile, test_user.name)
+		pos_inv1 = create_pos_invoice(rate=350, do_not_submit=1, pos_profile=pos_profile.name)
+		pos_inv1.append("payments", {"mode_of_payment": "Cash", "account": "Cash - _TC", "amount": 3500})
+		pos_inv1.submit()
+
+		# if in between a mandatory accounting dimension is added to the POS Profile then
+		accounting_dimension_department = frappe.get_doc("Accounting Dimension", {"name": "Department"})
+		accounting_dimension_department.dimension_defaults[0].mandatory_for_bs = 1
+		accounting_dimension_department.save()
+
+		pcv_doc = make_closing_entry_from_opening(opening_entry)
+		# will assert coz the new mandatory accounting dimension bank is not set in POS Profile
+		self.assertRaises(frappe.ValidationError, pcv_doc.submit)
+
+		accounting_dimension_department = frappe.get_doc(
+			"Accounting Dimension Detail", {"parent": "Department"}
+		)
+		accounting_dimension_department.mandatory_for_bs = 0
+		accounting_dimension_department.save()
+		disable_dimension()
+
 
 def init_user_and_profile(**args):
 	user = "test@example.com"
@@ -149,6 +190,9 @@
 	test_user.add_roles(*roles)
 	frappe.set_user(user)
 
+	if args.get("do_not_create_pos_profile"):
+		return test_user
+
 	pos_profile = make_pos_profile(**args)
 	pos_profile.append("applicable_for_users", {"default": 1, "user": user})
 
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 b587ce6..d42b1e4 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,8 @@
 from frappe.utils.background_jobs import enqueue, is_job_enqueued
 from frappe.utils.scheduler import is_scheduler_inactive
 
+from erpnext.accounts.doctype.pos_profile.pos_profile import required_accounting_dimensions
+
 
 class POSInvoiceMergeLog(Document):
 	def validate(self):
@@ -163,7 +165,8 @@
 				for i in items:
 					if (
 						i.item_code == item.item_code
-						and not i.serial_and_batch_bundle
+						and not i.serial_no
+						and not i.batch_no
 						and i.uom == item.uom
 						and i.net_rate == item.net_rate
 						and i.warehouse == item.warehouse
@@ -238,6 +241,22 @@
 		invoice.disable_rounded_total = cint(
 			frappe.db.get_value("POS Profile", invoice.pos_profile, "disable_rounded_total")
 		)
+		accounting_dimensions = required_accounting_dimensions()
+		dimension_values = frappe.db.get_value(
+			"POS Profile", {"name": invoice.pos_profile}, accounting_dimensions, as_dict=1
+		)
+		for dimension in accounting_dimensions:
+			dimension_value = dimension_values.get(dimension)
+
+			if not dimension_value:
+				frappe.throw(
+					_("Please set Accounting Dimension {} in {}").format(
+						frappe.bold(frappe.unscrub(dimension)),
+						frappe.get_desk_link("POS Profile", invoice.pos_profile),
+					)
+				)
+
+			invoice.set(dimension, dimension_value)
 
 		if self.merge_invoices_based_on == "Customer Group":
 			invoice.flags.ignore_pos_profile = True
@@ -424,11 +443,9 @@
 				)
 				merge_log.customer = customer
 				merge_log.pos_closing_entry = closing_entry.get("name") if closing_entry else 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.db_set("error_message", "")
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.js b/erpnext/accounts/doctype/pos_profile/pos_profile.js
index 0a89aee..ceaafaa 100755
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.js
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.js
@@ -1,6 +1,5 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
-
 frappe.ui.form.on('POS Profile', {
 	setup: function(frm) {
 		frm.set_query("selling_price_list", function() {
@@ -140,6 +139,7 @@
 	company: function(frm) {
 		frm.trigger("toggle_display_account_head");
 		erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
+
 	},
 
 	toggle_display_account_head: function(frm) {
diff --git a/erpnext/accounts/doctype/pos_profile/pos_profile.py b/erpnext/accounts/doctype/pos_profile/pos_profile.py
index e8aee73..58be2d3 100644
--- a/erpnext/accounts/doctype/pos_profile/pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/pos_profile.py
@@ -3,7 +3,7 @@
 
 
 import frappe
-from frappe import _, msgprint
+from frappe import _, msgprint, scrub, unscrub
 from frappe.model.document import Document
 from frappe.utils import get_link_to_form, now
 
@@ -14,6 +14,21 @@
 		self.validate_all_link_fields()
 		self.validate_duplicate_groups()
 		self.validate_payment_methods()
+		self.validate_accounting_dimensions()
+
+	def validate_accounting_dimensions(self):
+		acc_dim_names = required_accounting_dimensions()
+		for acc_dim in acc_dim_names:
+			if not self.get(acc_dim):
+				frappe.throw(
+					_(
+						"{0} is a mandatory Accounting Dimension. <br>"
+						"Please set a value for {0} in Accounting Dimensions section."
+					).format(
+						unscrub(frappe.bold(acc_dim)),
+					),
+					title=_("Mandatory Accounting Dimension"),
+				)
 
 	def validate_default_profile(self):
 		for row in self.applicable_for_users:
@@ -152,6 +167,24 @@
 	)
 
 
+def required_accounting_dimensions():
+
+	p = frappe.qb.DocType("Accounting Dimension")
+	c = frappe.qb.DocType("Accounting Dimension Detail")
+
+	acc_dim_doc = (
+		frappe.qb.from_(p)
+		.inner_join(c)
+		.on(p.name == c.parent)
+		.select(c.parent)
+		.where((c.mandatory_for_bs == 1) | (c.mandatory_for_pl == 1))
+		.where(p.disabled == 0)
+	).run(as_dict=1)
+
+	acc_dim_names = [scrub(d.parent) for d in acc_dim_doc]
+	return acc_dim_names
+
+
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
 def pos_profile_query(doctype, txt, searchfield, start, page_len, filters):
diff --git a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
index 788aa62..b468ad3 100644
--- a/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
+++ b/erpnext/accounts/doctype/pos_profile/test_pos_profile.py
@@ -5,7 +5,10 @@
 
 import frappe
 
-from erpnext.accounts.doctype.pos_profile.pos_profile import get_child_nodes
+from erpnext.accounts.doctype.pos_profile.pos_profile import (
+	get_child_nodes,
+	required_accounting_dimensions,
+)
 from erpnext.stock.get_item_details import get_pos_profile
 
 test_dependencies = ["Item"]
@@ -118,6 +121,7 @@
 			"warehouse": args.warehouse or "_Test Warehouse - _TC",
 			"write_off_account": args.write_off_account or "_Test Write Off - _TC",
 			"write_off_cost_center": args.write_off_cost_center or "_Test Write Off Cost Center - _TC",
+			"location": "Block 1" if not args.do_not_set_accounting_dimension else None,
 		}
 	)
 
@@ -132,6 +136,7 @@
 	pos_profile.append("payments", {"mode_of_payment": "Cash", "default": 1})
 
 	if not frappe.db.exists("POS Profile", args.name or "_Test POS Profile"):
-		pos_profile.insert()
+		if not args.get("do_not_insert"):
+			pos_profile.insert()
 
 	return pos_profile
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index efe9741..c8c9ad1 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -161,6 +161,7 @@
 		}
 
 		this.frm.set_df_property("tax_withholding_category", "hidden", doc.apply_tds ? 0 : 1);
+		erpnext.accounts.unreconcile_payments.add_unreconcile_btn(me.frm);
 	}
 
 	unblock_invoice() {
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index ce7ada3..b4dd75a 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1164,7 +1164,7 @@
 
 		item = create_item("_Test Item for Deferred Accounting", is_purchase_item=True)
 		item.enable_deferred_expense = 1
-		item.deferred_expense_account = deferred_account
+		item.item_defaults[0].deferred_expense_account = deferred_account
 		item.save()
 
 		pi = make_purchase_invoice(item=item.name, qty=1, rate=100, do_not_save=True)
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 21b39d7..9ffdaf6 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2322,7 +2322,7 @@
 
 		item = create_item("_Test Item for Deferred Accounting")
 		item.enable_deferred_revenue = 1
-		item.deferred_revenue_account = deferred_account
+		item.item_defaults[0].deferred_revenue_account = deferred_account
 		item.no_of_months = 12
 		item.save()
 
@@ -3102,7 +3102,7 @@
 
 		item = create_item("_Test Item for Deferred Accounting")
 		item.enable_deferred_expense = 1
-		item.deferred_revenue_account = deferred_account
+		item.item_defaults[0].deferred_revenue_account = deferred_account
 		item.save()
 
 		si = create_sales_invoice(
diff --git a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
index 28d0c20..7b1a902 100644
--- a/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
+++ b/erpnext/accounts/report/deferred_revenue_and_expense/test_deferred_revenue_and_expense.py
@@ -81,7 +81,7 @@
 		self.create_item("_Test Internet Subscription", 0, self.warehouse, self.company)
 		item = frappe.get_doc("Item", self.item)
 		item.enable_deferred_revenue = 1
-		item.deferred_revenue_account = self.deferred_revenue_account
+		item.item_defaults[0].deferred_revenue_account = self.deferred_revenue_account
 		item.no_of_months = 3
 		item.save()
 
@@ -150,7 +150,7 @@
 		self.create_item("_Test Office Desk", 0, self.warehouse, self.company)
 		item = frappe.get_doc("Item", self.item)
 		item.enable_deferred_expense = 1
-		item.deferred_expense_account = self.deferred_expense_account
+		item.item_defaults[0].deferred_expense_account = self.deferred_expense_account
 		item.no_of_months_exp = 3
 		item.save()
 
diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py
index 0868860..d503f7b 100644
--- a/erpnext/accounts/test/accounts_mixin.py
+++ b/erpnext/accounts/test/accounts_mixin.py
@@ -158,6 +158,8 @@
 			"Journal Entry",
 			"Sales Order",
 			"Exchange Rate Revaluation",
+			"Bank Account",
+			"Bank Transaction",
 		]
 		for doctype in doctype_list:
 			qb.from_(qb.DocType(doctype)).delete().where(qb.DocType(doctype).company == self.company).run()
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.js b/erpnext/assets/doctype/asset_repair/asset_repair.js
index dae993a..03afcb9 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.js
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.js
@@ -40,16 +40,6 @@
 				}
 			}
 		});
-
-		let sbb_field = frm.get_docfield('stock_items', 'serial_and_batch_bundle');
-		if (sbb_field) {
-			sbb_field.get_route_options_for_new_doc = (row) => {
-				return {
-					'item_code': row.doc.item_code,
-					'voucher_type': frm.doc.doctype,
-				}
-			};
-		}
 	},
 
 	refresh: function(frm) {
@@ -61,6 +51,16 @@
 				frappe.set_route("query-report", "General Ledger");
 			});
 		}
+
+		let sbb_field = frm.get_docfield('stock_items', 'serial_and_batch_bundle');
+		if (sbb_field) {
+			sbb_field.get_route_options_for_new_doc = (row) => {
+				return {
+					'item_code': row.doc.item_code,
+					'voucher_type': frm.doc.doctype,
+				}
+			};
+		}
 	},
 
 	repair_status: (frm) => {
diff --git a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
index 21241e0..0718735 100644
--- a/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
+++ b/erpnext/buying/report/requested_items_to_order_and_receive/requested_items_to_order_and_receive.py
@@ -7,7 +7,7 @@
 import frappe
 from frappe import _
 from frappe.query_builder.functions import Coalesce, Sum
-from frappe.utils import date_diff, flt, getdate
+from frappe.utils import cint, date_diff, flt, getdate
 
 
 def execute(filters=None):
@@ -47,8 +47,10 @@
 			mr.transaction_date.as_("date"),
 			mr_item.schedule_date.as_("required_date"),
 			mr_item.item_code.as_("item_code"),
-			Sum(Coalesce(mr_item.stock_qty, 0)).as_("qty"),
-			Coalesce(mr_item.stock_uom, "").as_("uom"),
+			Sum(Coalesce(mr_item.qty, 0)).as_("qty"),
+			Sum(Coalesce(mr_item.stock_qty, 0)).as_("stock_qty"),
+			Coalesce(mr_item.uom, "").as_("uom"),
+			Coalesce(mr_item.stock_uom, "").as_("stock_uom"),
 			Sum(Coalesce(mr_item.ordered_qty, 0)).as_("ordered_qty"),
 			Sum(Coalesce(mr_item.received_qty, 0)).as_("received_qty"),
 			(Sum(Coalesce(mr_item.stock_qty, 0)) - Sum(Coalesce(mr_item.received_qty, 0))).as_(
@@ -96,7 +98,7 @@
 
 
 def update_qty_columns(row_to_update, data_row):
-	fields = ["qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]
+	fields = ["qty", "stock_qty", "ordered_qty", "received_qty", "qty_to_receive", "qty_to_order"]
 	for field in fields:
 		row_to_update[field] += flt(data_row[field])
 
@@ -104,16 +106,20 @@
 def prepare_data(data, filters):
 	"""Prepare consolidated Report data and Chart data"""
 	material_request_map, item_qty_map = {}, {}
+	precision = cint(frappe.db.get_default("float_precision")) or 2
 
 	for row in data:
 		# item wise map for charts
 		if not row["item_code"] in item_qty_map:
 			item_qty_map[row["item_code"]] = {
-				"qty": row["qty"],
-				"ordered_qty": row["ordered_qty"],
-				"received_qty": row["received_qty"],
-				"qty_to_receive": row["qty_to_receive"],
-				"qty_to_order": row["qty_to_order"],
+				"qty": flt(row["stock_qty"], precision),
+				"stock_qty": flt(row["stock_qty"], precision),
+				"stock_uom": row["stock_uom"],
+				"uom": row["uom"],
+				"ordered_qty": flt(row["ordered_qty"], precision),
+				"received_qty": flt(row["received_qty"], precision),
+				"qty_to_receive": flt(row["qty_to_receive"], precision),
+				"qty_to_order": flt(row["qty_to_order"], precision),
 			}
 		else:
 			item_entry = item_qty_map[row["item_code"]]
@@ -200,21 +206,34 @@
 				{"label": _("Item Name"), "fieldname": "item_name", "fieldtype": "Data", "width": 100},
 				{"label": _("Description"), "fieldname": "description", "fieldtype": "Data", "width": 200},
 				{
-					"label": _("Stock UOM"),
+					"label": _("UOM"),
 					"fieldname": "uom",
 					"fieldtype": "Data",
 					"width": 100,
 				},
+				{
+					"label": _("Stock UOM"),
+					"fieldname": "stock_uom",
+					"fieldtype": "Data",
+					"width": 100,
+				},
 			]
 		)
 
 	columns.extend(
 		[
 			{
-				"label": _("Stock Qty"),
+				"label": _("Qty"),
 				"fieldname": "qty",
 				"fieldtype": "Float",
-				"width": 120,
+				"width": 140,
+				"convertible": "qty",
+			},
+			{
+				"label": _("Qty in Stock UOM"),
+				"fieldname": "stock_qty",
+				"fieldtype": "Float",
+				"width": 140,
 				"convertible": "qty",
 			},
 			{
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index b1ce539..c302ece 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -190,10 +190,13 @@
 		purchase_doc_field = (
 			"purchase_receipt" if self.doctype == "Purchase Receipt" else "purchase_invoice"
 		)
-		not_cancelled_asset = [
-			d.name
-			for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1})
-		]
+		not_cancelled_asset = []
+		if self.return_against:
+			not_cancelled_asset = [
+				d.name
+				for d in frappe.db.get_all("Asset", {purchase_doc_field: self.return_against, "docstatus": 1})
+			]
+
 		if self.is_return and len(not_cancelled_asset):
 			frappe.throw(
 				_(
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index c7398cc..7bf8fb4 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -555,8 +555,6 @@
 	"erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_matching_queries"
 )
 
-get_matching_vouchers_for_bank_reconciliation = "erpnext.accounts.doctype.bank_reconciliation_tool.bank_reconciliation_tool.get_matching_vouchers_for_bank_reconciliation"
-
 get_amounts_not_reflected_in_system_for_bank_reconciliation_statement = "erpnext.accounts.report.bank_reconciliation_statement.bank_reconciliation_statement.get_amounts_not_reflected_in_system_for_bank_reconciliation_statement"
 
 get_payment_entries_for_bank_clearance = (
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index f1e6094..6621275 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -23,17 +23,6 @@
 			}
 		});
 
-		let sbb_field = frm.get_docfield('serial_and_batch_bundle');
-		if (sbb_field) {
-			sbb_field.get_route_options_for_new_doc = () => {
-				return {
-					'item_code': frm.doc.production_item,
-					'warehouse': frm.doc.wip_warehouse,
-					'voucher_type': frm.doc.doctype,
-				}
-			};
-		}
-
 		frm.set_indicator_formatter('sub_operation',
 			function(doc) {
 				if (doc.status == "Pending") {
@@ -124,6 +113,17 @@
 				}
 			});
 		}
+
+		let sbb_field = frm.get_docfield('serial_and_batch_bundle');
+		if (sbb_field) {
+			sbb_field.get_route_options_for_new_doc = () => {
+				return {
+					'item_code': frm.doc.production_item,
+					'warehouse': frm.doc.wip_warehouse,
+					'voucher_type': frm.doc.doctype,
+				}
+			};
+		}
 	},
 
 	setup_quality_inspection: function(frm) {
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 7a07f56..dda4d36 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -339,10 +339,8 @@
 erpnext.patches.v15_0.remove_exotel_integration
 erpnext.patches.v14_0.single_to_multi_dunning
 execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
-execute:frappe.delete_doc('DocType', 'Twitter Settings', ignore_missing=True)
-execute:frappe.delete_doc('DocType', 'LinkedIn Settings', ignore_missing=True)
-execute:frappe.delete_doc('DocType', 'Social Media Post', ignore_missing=True)
 erpnext.patches.v15_0.correct_asset_value_if_je_with_workflow
 erpnext.patches.v15_0.delete_woocommerce_settings_doctype
+erpnext.patches.v14_0.migrate_deferred_accounts_to_item_defaults
 # below migration patch should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v14_0/delete_education_doctypes.py b/erpnext/patches/v14_0/delete_education_doctypes.py
index 56a596a..aeeda70 100644
--- a/erpnext/patches/v14_0/delete_education_doctypes.py
+++ b/erpnext/patches/v14_0/delete_education_doctypes.py
@@ -47,13 +47,16 @@
 	for doctype in doctypes:
 		frappe.delete_doc("DocType", doctype, ignore_missing=True)
 
-	portal_settings = frappe.get_doc("Portal Settings")
-
-	for row in portal_settings.get("menu"):
-		if row.reference_doctype in doctypes:
-			row.delete()
-
-	portal_settings.save()
+	titles = [
+		"Fees",
+		"Student Admission",
+		"Grant Application",
+		"Chapter",
+		"Certification Application",
+	]
+	items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name")
+	for item in items:
+		frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True)
 
 	frappe.delete_doc("Module Def", "Education", ignore_missing=True, force=True)
 
diff --git a/erpnext/patches/v14_0/delete_healthcare_doctypes.py b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
index 2c699e4..896a440 100644
--- a/erpnext/patches/v14_0/delete_healthcare_doctypes.py
+++ b/erpnext/patches/v14_0/delete_healthcare_doctypes.py
@@ -41,7 +41,7 @@
 	for card in cards:
 		frappe.delete_doc("Number Card", card, ignore_missing=True, force=True)
 
-	titles = ["Lab Test", "Prescription", "Patient Appointment"]
+	titles = ["Lab Test", "Prescription", "Patient Appointment", "Patient"]
 	items = frappe.get_all("Portal Menu Item", filters=[["title", "in", titles]], pluck="name")
 	for item in items:
 		frappe.delete_doc("Portal Menu Item", item, ignore_missing=True, force=True)
diff --git a/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py
new file mode 100644
index 0000000..44b830b
--- /dev/null
+++ b/erpnext/patches/v14_0/migrate_deferred_accounts_to_item_defaults.py
@@ -0,0 +1,39 @@
+import frappe
+
+
+def execute():
+	try:
+		item_dict = get_deferred_accounts()
+		add_to_item_defaults(item_dict)
+	except Exception:
+		frappe.db.rollback()
+		frappe.log_error("Failed to migrate deferred accounts in Item Defaults.")
+
+
+def get_deferred_accounts():
+	item = frappe.qb.DocType("Item")
+	return (
+		frappe.qb.from_(item)
+		.select(item.name, item.deferred_expense_account, item.deferred_revenue_account)
+		.where((item.enable_deferred_expense == 1) | (item.enable_deferred_revenue == 1))
+		.run(as_dict=True)
+	)
+
+
+def add_to_item_defaults(item_dict):
+	for item in item_dict:
+		add_company_wise_item_default(item, "deferred_expense_account")
+		add_company_wise_item_default(item, "deferred_revenue_account")
+
+
+def add_company_wise_item_default(item, account_type):
+	company = frappe.get_cached_value("Account", item[account_type], "company")
+	if company and item[account_type]:
+		item_defaults = frappe.get_cached_value("Item", item["name"], "item_defaults")
+		for item_row in item_defaults:
+			if item_row.company == company:
+				frappe.set_value("Item Default", item_row.name, account_type, item[account_type])
+				break
+		else:
+			item_defaults.append({"company": company, account_type: item[account_type]})
+			frappe.set_value("Item", item["name"], "item_defaults", item_defaults)
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index 52fa8ab..1f47347 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -134,12 +134,12 @@
 
 	format_row(row) {
 		return [
-			row[1], // Document Type
-			row[2], // Document Name
-			row[5] || row[8], // Reference Date
-			format_currency(row[3], row[9]), // Remaining
-			row[4], // Reference Number
-			row[6], // Party
+			row["doctype"],
+			row["name"],
+			row["reference_date"] || row["posting_date"],
+			format_currency(row["paid_amount"], row["currency"]),
+			row["reference_no"],
+			row["party"],
 		];
 	}
 
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 80d7b79..975adc2 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -134,15 +134,6 @@
 					}
 				}
 			});
-
-			let sbb_field = this.frm.get_docfield('items', 'serial_and_batch_bundle');
-			if (sbb_field) {
-				sbb_field.get_route_options_for_new_doc = (row) => {
-					return {
-						'item_code': row.doc.item_code,
-					}
-				};
-			}
 		}
 
 		if(
@@ -207,15 +198,6 @@
 			});
 		}
 
-		let batch_no_field = this.frm.get_docfield("items", "batch_no");
-		if (batch_no_field) {
-			batch_no_field.get_route_options_for_new_doc = function(row) {
-				return {
-					"item": row.doc.item_code
-				}
-			};
-		}
-
 		if (this.frm.fields_dict["items"].grid.get_field('blanket_order')) {
 			this.frm.set_query("blanket_order", "items", function(doc, cdt, cdn) {
 				var item = locals[cdt][cdn];
@@ -268,6 +250,28 @@
 				}
 			]);
 		}
+
+		if(this.frm.fields_dict['items'].grid.get_field('serial_and_batch_bundle')) {
+			let sbb_field = this.frm.get_docfield('items', 'serial_and_batch_bundle');
+			if (sbb_field) {
+				sbb_field.get_route_options_for_new_doc = (row) => {
+					return {
+						'item_code': row.doc.item_code,
+					}
+				};
+			}
+		}
+
+		if(this.frm.fields_dict['items'].grid.get_field('batch_no')) {
+			let batch_no_field = this.frm.get_docfield('items', 'batch_no');
+			if (batch_no_field) {
+				batch_no_field.get_route_options_for_new_doc = function(row) {
+					return {
+						'item': row.doc.item_code
+					}
+				};
+			}
+		}
 	}
 
 	is_return() {
@@ -358,7 +362,6 @@
 	}
 
 	refresh() {
-
 		erpnext.toggle_naming_series();
 		erpnext.hide_company();
 		this.set_dynamic_labels();
diff --git a/erpnext/public/js/utils/demo.js b/erpnext/public/js/utils/demo.js
index 3ebc5ef..ef83cf6 100644
--- a/erpnext/public/js/utils/demo.js
+++ b/erpnext/public/js/utils/demo.js
@@ -1,91 +1,34 @@
+frappe.provide("erpnext.demo");
+
 $(document).on("toolbar_setup", function () {
 	if (frappe.boot.sysdefaults.demo_company) {
-		render_clear_demo_button();
+		render_clear_demo_action();
 	}
-
-	// for first load after setup.
-	frappe.realtime.on("demo_data_complete", () => {
-		render_clear_demo_button();
-	});
 });
 
-function render_clear_demo_button() {
-	let wait_for_onboaring_tours = setInterval(() => {
-		if ($("#driver-page-overlay").length || $("#show-dialog").length) {
-			return;
-		}
-		setup_clear_demo_button();
-		clearInterval(wait_for_onboaring_tours);
-	}, 2000);
-}
-
-function setup_clear_demo_button() {
-	let message_string = __(
-		"Demo data is present on the system, erase data before starting real usage."
+function render_clear_demo_action() {
+	let demo_action = $(
+		`<a class="dropdown-item" onclick="return erpnext.demo.clear_demo()">
+			${__("Clear Demo Data")}
+		</a>`
 	);
-	let $floatingBar = $(`
-		<div class="flex justify-content-center" style="width: 100%;">
-			<div class="flex justify-content-center flex-col shadow rounded p-2"
-					style="
-						background-color: #e0f2fe;
-						position: fixed;
-						bottom: 20px;
-						z-index: 1;">
-			<p style="margin: auto 0; padding-left: 10px; margin-right: 20px; font-size: 15px;">
-					${message_string}
-				</p>
-				<button id="clear-demo" type="button"
-					class="
-						px-4
-						py-2
-						border
-						border-transparent
-						text-white
-					"
-					style="
-						margin: auto 0;
-						height: fit-content;
-						background-color: #007bff;
-						border-radius: 5px;
-						margin-right: 10px
-					"
-				>
-					Clear Demo Data
-				</button>
 
-	 			<a type="button" id="dismiss-demo-banner" class="text-muted" style="align-self: center">
-					<svg class="icon" style="">
-						<use class="" href="#icon-close"></use>
-					</svg>
-				</a>
-			</div>
-		</div>
-	`);
-
-	$("footer").append($floatingBar);
-
-	$("#clear-demo").on("click", function () {
-		frappe.confirm(
-			__("Are you sure you want to clear all demo data?"),
-			() => {
-				frappe.call({
-					method: "erpnext.setup.demo.clear_demo_data",
-					freeze: true,
-					freeze_message: __("Clearing Demo Data..."),
-					callback: function (r) {
-						frappe.ui.toolbar.clear_cache();
-						frappe.show_alert({
-							message: __("Demo data cleared"),
-							indicator: "green",
-						});
-						$("footer").remove($floatingBar);
-					},
-				});
-			}
-		);
-	});
-
-	$("#dismiss-demo-banner").on("click", function () {
-		$floatingBar.remove();
-	});
+	demo_action.appendTo($("#toolbar-user"));
 }
+
+erpnext.demo.clear_demo = function () {
+	frappe.confirm(__("Are you sure you want to clear all demo data?"), () => {
+		frappe.call({
+			method: "erpnext.setup.demo.clear_demo_data",
+			freeze: true,
+			freeze_message: __("Clearing Demo Data..."),
+			callback: function (r) {
+				frappe.ui.toolbar.clear_cache();
+				frappe.show_alert({
+					message: __("Demo data cleared"),
+					indicator: "green",
+				});
+			},
+		});
+	});
+};
diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js
index acc77a6..bbdd51d 100644
--- a/erpnext/public/js/utils/unreconcile.js
+++ b/erpnext/public/js/utils/unreconcile.js
@@ -19,7 +19,7 @@
 					if (r.message) {
 						frm.add_custom_button(__("Un-Reconcile"), function() {
 							erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm);
-						});
+						}, __('Actions'));
 					}
 				}
 			});
diff --git a/erpnext/selling/doctype/installation_note/installation_note.js b/erpnext/selling/doctype/installation_note/installation_note.js
index dd6f8a8..8128c77 100644
--- a/erpnext/selling/doctype/installation_note/installation_note.js
+++ b/erpnext/selling/doctype/installation_note/installation_note.js
@@ -18,6 +18,14 @@
 				}
 			}
 		});
+	},
+	onload: function(frm) {
+		if(!frm.doc.status) {
+			frm.set_value({ status:'Draft'});
+		}
+		if(frm.doc.__islocal) {
+			frm.set_value({inst_date: frappe.datetime.get_today()});
+		}
 
 		let sbb_field = frm.get_docfield('items', 'serial_and_batch_bundle');
 		if (sbb_field) {
@@ -29,14 +37,6 @@
 			};
 		}
 	},
-	onload: function(frm) {
-		if(!frm.doc.status) {
-			frm.set_value({ status:'Draft'});
-		}
-		if(frm.doc.__islocal) {
-			frm.set_value({inst_date: frappe.datetime.get_today()});
-		}
-	},
 	customer: function(frm) {
 		erpnext.utils.get_party_details(frm);
 	},
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index d18f70a..1bc8d68 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -48,6 +48,11 @@
 				}
 			}
 		});
+	},
+
+	refresh: function(frm) {
+		frm.trigger("set_label");
+		frm.trigger("set_dynamic_field_label");
 
 		let sbb_field = frm.get_docfield('packed_items', 'serial_and_batch_bundle');
 		if (sbb_field) {
@@ -61,11 +66,6 @@
 		}
 	},
 
-	refresh: function(frm) {
-		frm.trigger("set_label");
-		frm.trigger("set_dynamic_field_label");
-	},
-
 	quotation_to: function(frm) {
 		frm.trigger("set_label");
 		frm.trigger("toggle_reqd_lead_customer");
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 76e8866..4ae9bf5 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -350,18 +350,20 @@
 			}
 		}
 
-		frm.fields_dict['deferred_revenue_account'].get_query = function() {
+		frm.fields_dict["item_defaults"].grid.get_field("deferred_revenue_account").get_query = function(doc, cdt, cdn) {
 			return {
 				filters: {
+					"company": locals[cdt][cdn].company,
 					'root_type': 'Liability',
 					"is_group": 0
 				}
 			}
 		}
 
-		frm.fields_dict['deferred_expense_account'].get_query = function() {
+		frm.fields_dict["item_defaults"].grid.get_field("deferred_expense_account").get_query = function(doc, cdt, cdn) {
 			return {
 				filters: {
+					"company": locals[cdt][cdn].company,
 					'root_type': 'Asset',
 					"is_group": 0
 				}
diff --git a/erpnext/stock/doctype/item/item.json b/erpnext/stock/doctype/item/item.json
index 756d004..1bcddfa 100644
--- a/erpnext/stock/doctype/item/item.json
+++ b/erpnext/stock/doctype/item/item.json
@@ -69,6 +69,13 @@
   "variant_based_on",
   "attributes",
   "accounting",
+  "deferred_accounting_section",
+  "enable_deferred_expense",
+  "no_of_months_exp",
+  "column_break_9s9o",
+  "enable_deferred_revenue",
+  "no_of_months",
+  "section_break_avcp",
   "item_defaults",
   "purchasing_tab",
   "purchase_uom",
@@ -84,10 +91,6 @@
   "delivered_by_supplier",
   "column_break2",
   "supplier_items",
-  "deferred_expense_section",
-  "enable_deferred_expense",
-  "deferred_expense_account",
-  "no_of_months_exp",
   "foreign_trade_details",
   "country_of_origin",
   "column_break_59",
@@ -98,10 +101,6 @@
   "is_sales_item",
   "column_break3",
   "max_discount",
-  "deferred_revenue",
-  "enable_deferred_revenue",
-  "deferred_revenue_account",
-  "no_of_months",
   "customer_details",
   "customer_items",
   "item_tax_section_break",
@@ -658,20 +657,6 @@
    "oldfieldtype": "Currency"
   },
   {
-   "collapsible": 1,
-   "fieldname": "deferred_revenue",
-   "fieldtype": "Section Break",
-   "label": "Deferred Revenue"
-  },
-  {
-   "depends_on": "enable_deferred_revenue",
-   "fieldname": "deferred_revenue_account",
-   "fieldtype": "Link",
-   "ignore_user_permissions": 1,
-   "label": "Deferred Revenue Account",
-   "options": "Account"
-  },
-  {
    "default": "0",
    "fieldname": "enable_deferred_revenue",
    "fieldtype": "Check",
@@ -681,21 +666,7 @@
    "depends_on": "enable_deferred_revenue",
    "fieldname": "no_of_months",
    "fieldtype": "Int",
-   "label": "No of Months"
-  },
-  {
-   "collapsible": 1,
-   "fieldname": "deferred_expense_section",
-   "fieldtype": "Section Break",
-   "label": "Deferred Expense"
-  },
-  {
-   "depends_on": "enable_deferred_expense",
-   "fieldname": "deferred_expense_account",
-   "fieldtype": "Link",
-   "ignore_user_permissions": 1,
-   "label": "Deferred Expense Account",
-   "options": "Account"
+   "label": "No of Months (Revenue)"
   },
   {
    "default": "0",
@@ -904,6 +875,20 @@
    "fieldname": "accounting",
    "fieldtype": "Tab Break",
    "label": "Accounting"
+  },
+  {
+   "fieldname": "column_break_9s9o",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "section_break_avcp",
+   "fieldtype": "Section Break"
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "deferred_accounting_section",
+   "fieldtype": "Section Break",
+   "label": "Deferred Accounting"
   }
  ],
  "icon": "fa fa-tag",
@@ -912,7 +897,7 @@
  "index_web_pages_for_search": 1,
  "links": [],
  "make_attachments_public": 1,
- "modified": "2023-08-28 22:16:40.305094",
+ "modified": "2023-09-11 13:46:32.688051",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item",
diff --git a/erpnext/stock/doctype/item_default/item_default.json b/erpnext/stock/doctype/item_default/item_default.json
index 042d398..2895661 100644
--- a/erpnext/stock/doctype/item_default/item_default.json
+++ b/erpnext/stock/doctype/item_default/item_default.json
@@ -19,7 +19,11 @@
   "selling_defaults",
   "selling_cost_center",
   "column_break_12",
-  "income_account"
+  "income_account",
+  "deferred_accounting_defaults_section",
+  "deferred_expense_account",
+  "column_break_kwad",
+  "deferred_revenue_account"
  ],
  "fields": [
   {
@@ -108,11 +112,34 @@
    "fieldtype": "Link",
    "label": "Default Provisional Account",
    "options": "Account"
+  },
+  {
+   "fieldname": "deferred_accounting_defaults_section",
+   "fieldtype": "Section Break",
+   "label": "Deferred Accounting Defaults"
+  },
+  {
+   "depends_on": "eval: parent.enable_deferred_expense",
+   "fieldname": "deferred_expense_account",
+   "fieldtype": "Link",
+   "label": "Deferred Expense Account",
+   "options": "Account"
+  },
+  {
+   "depends_on": "eval: parent.enable_deferred_revenue",
+   "fieldname": "deferred_revenue_account",
+   "fieldtype": "Link",
+   "label": "Deferred Revenue Account",
+   "options": "Account"
+  },
+  {
+   "fieldname": "column_break_kwad",
+   "fieldtype": "Column Break"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2022-04-10 20:18:54.148195",
+ "modified": "2023-09-04 12:33:14.607267",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item Default",
diff --git a/erpnext/stock/doctype/material_request/material_request.json b/erpnext/stock/doctype/material_request/material_request.json
index ffec57c..25c765b 100644
--- a/erpnext/stock/doctype/material_request/material_request.json
+++ b/erpnext/stock/doctype/material_request/material_request.json
@@ -296,6 +296,7 @@
    "depends_on": "eval:doc.material_request_type == 'Material Transfer'",
    "fieldname": "set_from_warehouse",
    "fieldtype": "Link",
+   "ignore_user_permissions": 1,
    "label": "Set Source Warehouse",
    "options": "Warehouse"
   },
@@ -356,7 +357,7 @@
  "idx": 70,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-07-25 17:19:31.662662",
+ "modified": "2023-09-15 12:07:24.789471",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Material Request",
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 4eed285..ae05b80 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -65,17 +65,6 @@
 				}
 			}
 		});
-
-		let sbb_field = frm.get_docfield('locations', 'serial_and_batch_bundle');
-		if (sbb_field) {
-			sbb_field.get_route_options_for_new_doc = (row) => {
-				return {
-					'item_code': row.doc.item_code,
-					'warehouse': row.doc.warehouse,
-					'voucher_type': frm.doc.doctype,
-				}
-			};
-		}
 	},
 	set_item_locations:(frm, save) => {
 		if (!(frm.doc.locations && frm.doc.locations.length)) {
@@ -132,6 +121,17 @@
 				}
 			}
 		}
+
+		let sbb_field = frm.get_docfield('locations', 'serial_and_batch_bundle');
+		if (sbb_field) {
+			sbb_field.get_route_options_for_new_doc = (row) => {
+				return {
+					'item_code': row.doc.item_code,
+					'warehouse': row.doc.warehouse,
+					'voucher_type': frm.doc.doctype,
+				}
+			};
+		}
 	},
 	work_order: (frm) => {
 		frappe.db.get_value('Work Order',
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 4fb8a10..d37e8ee 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -117,15 +117,6 @@
 			}
 		});
 
-		let sbb_field = frm.get_docfield('items', 'serial_and_batch_bundle');
-		if (sbb_field) {
-			sbb_field.get_route_options_for_new_doc = (row) => {
-				return {
-					'item_code': row.doc.item_code,
-					'voucher_type': frm.doc.doctype,
-				}
-			};
-		}
 
 		frm.add_fetch("bom_no", "inspection_required", "inspection_required");
 		erpnext.accounts.dimensions.setup_dimension_filters(frm, frm.doctype);
@@ -362,6 +353,16 @@
 		if(!check_should_not_attach_bom_items(frm.doc.bom_no)) {
 			erpnext.accounts.dimensions.update_dimension(frm, frm.doctype);
 		}
+
+		let sbb_field = frm.get_docfield('items', 'serial_and_batch_bundle');
+		if (sbb_field) {
+			sbb_field.get_route_options_for_new_doc = (row) => {
+				return {
+					'item_code': row.doc.item_code,
+					'voucher_type': frm.doc.doctype,
+				}
+			};
+		}
 	},
 
 	get_items_from_transit_entry: function(frm) {
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 4f85ac0..79e6488 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -696,7 +696,11 @@
 def get_default_deferred_account(args, item, fieldname=None):
 	if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
 		return (
-			item.get(fieldname)
+			frappe.get_cached_value(
+				"Item Default",
+				{"parent": args.item_code, "company": args.get("company")},
+				fieldname,
+			)
 			or args.get(fieldname)
 			or frappe.get_cached_value("Company", args.company, "default_" + fieldname)
 		)
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index 8ac22e6..19a1c93 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -77,6 +77,7 @@
 		}
 
 		frm.trigger('setup_quality_inspection');
+		frm.trigger('set_route_options_for_new_doc');
 	},
 
 	set_warehouse: (frm) => {
@@ -87,6 +88,23 @@
 		set_warehouse_in_children(frm.doc.items, 'rejected_warehouse', frm.doc.rejected_warehouse);
 	},
 
+	get_scrap_items: (frm) => {
+		frappe.call({
+			doc: frm.doc,
+			method: 'get_scrap_items',
+			args: {
+				recalculate_rate: true
+			},
+			freeze: true,
+			freeze_message: __('Getting Scrap Items'),
+			callback: (r) => {
+				if (!r.exc) {
+					frm.refresh();
+				}
+			}
+		});
+	},
+
 	set_queries: (frm) => {
 		frm.set_query('set_warehouse', () => {
 			return {
@@ -173,7 +191,28 @@
 				}
 			}
 		});
+	},
 
+	get_serial_and_batch_bundle_filters: (doc, cdt, cdn) => {
+		let row = locals[cdt][cdn];
+		return {
+			filters: {
+				'item_code': row.item_code,
+				'voucher_type': doc.doctype,
+				'voucher_no': ['in', [doc.name, '']],
+				'is_cancelled': 0,
+			}
+		}
+	},
+
+	setup_quality_inspection: (frm) => {
+		if (!frm.is_new() && frm.doc.docstatus === 0 && !frm.doc.is_return) {
+			let transaction_controller = new erpnext.TransactionController({ frm: frm });
+			transaction_controller.setup_quality_inspection();
+		}
+	},
+
+	set_route_options_for_new_doc: (frm) => {
 		let batch_no_field = frm.get_docfield('items', 'batch_no');
 		if (batch_no_field) {
 			batch_no_field.get_route_options_for_new_doc = (row) => {
@@ -213,42 +252,6 @@
 			}
 		}
 	},
-
-	get_serial_and_batch_bundle_filters: (doc, cdt, cdn) => {
-		let row = locals[cdt][cdn];
-		return {
-			filters: {
-				'item_code': row.item_code,
-				'voucher_type': doc.doctype,
-				'voucher_no': ['in', [doc.name, '']],
-				'is_cancelled': 0,
-			}
-		}
-	},
-
-	setup_quality_inspection: (frm) => {
-		if (!frm.is_new() && frm.doc.docstatus === 0 && !frm.doc.is_return) {
-			let transaction_controller = new erpnext.TransactionController({ frm: frm });
-			transaction_controller.setup_quality_inspection();
-		}
-	},
-
-	get_scrap_items: (frm) => {
-		frappe.call({
-			doc: frm.doc,
-			method: 'get_scrap_items',
-			args: {
-				recalculate_rate: true
-			},
-			freeze: true,
-			freeze_message: __('Getting Scrap Items'),
-			callback: (r) => {
-				if (!r.exc) {
-					frm.refresh();
-				}
-			}
-		});
-	},
 });
 
 frappe.ui.form.on('Landed Cost Taxes and Charges', {
@@ -303,4 +306,4 @@
 			if (!r.exc) frm.refresh();
 		},
 	});
-}
\ No newline at end of file
+}