Merge remote-tracking branch 'upstream/develop' into move-exotel-to-separate-app
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 0c71b41..48337ce 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -8,8 +8,9 @@
 
 pip install frappe-bench
 
+githubbranch=${GITHUB_BASE_REF:-${GITHUB_REF##*/}}
 frappeuser=${FRAPPE_USER:-"frappe"}
-frappebranch=${FRAPPE_BRANCH:-${GITHUB_BASE_REF:-${GITHUB_REF##*/}}}
+frappebranch=${FRAPPE_BRANCH:-$githubbranch}
 
 git clone "https://github.com/${frappeuser}/frappe" --branch "${frappebranch}" --depth 1
 bench init --skip-assets --frappe-path ~/frappe --python "$(which python)" frappe-bench
@@ -60,7 +61,7 @@
 sed -i 's/socketio:/# socketio:/g' Procfile
 sed -i 's/redis_socketio:/# redis_socketio:/g' Procfile
 
-bench get-app payments
+bench get-app payments --branch ${githubbranch%"-hotfix"}
 bench get-app erpnext "${GITHUB_WORKSPACE}"
 
 if [ "$TYPE" == "server" ]; then bench setup requirements --dev; fi
diff --git a/.github/workflows/server-tests-mariadb.yml b/.github/workflows/server-tests-mariadb.yml
index c70c76f..8959f7f 100644
--- a/.github/workflows/server-tests-mariadb.yml
+++ b/.github/workflows/server-tests-mariadb.yml
@@ -7,7 +7,6 @@
       - '**.css'
       - '**.md'
       - '**.html'
-      - '**.csv'
   push:
     branches: [ develop ]
     paths-ignore:
diff --git a/CODEOWNERS b/CODEOWNERS
index c4ea163..7f8c4d1 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -3,13 +3,13 @@
 # These owners will be the default owners for everything in
 # the repo. Unless a later match takes precedence,
 
-erpnext/accounts/               @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
+erpnext/accounts/               @deepeshgarg007 @ruthra-kumar
 erpnext/assets/                 @anandbaburajan @deepeshgarg007
-erpnext/loan_management/        @nextchamp-saqib @deepeshgarg007
-erpnext/regional                @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
-erpnext/selling                 @nextchamp-saqib @deepeshgarg007 @ruthra-kumar
-erpnext/support/                @nextchamp-saqib @deepeshgarg007
-pos*                            @nextchamp-saqib
+erpnext/loan_management/        @deepeshgarg007
+erpnext/regional                @deepeshgarg007 @ruthra-kumar
+erpnext/selling                 @deepeshgarg007 @ruthra-kumar
+erpnext/support/                @deepeshgarg007
+pos*                            
 
 erpnext/buying/                 @rohitwaghchaure @s-aga-r
 erpnext/maintenance/            @rohitwaghchaure @s-aga-r
@@ -18,12 +18,8 @@
 erpnext/stock/                  @rohitwaghchaure @s-aga-r
 erpnext/subcontracting          @rohitwaghchaure @s-aga-r
 
-erpnext/crm/                    @NagariaHussain
-erpnext/education/              @rutwikhdev
-erpnext/projects/               @ruchamahabal
+erpnext/controllers/            @deepeshgarg007 @rohitwaghchaure
+erpnext/patches/                @deepeshgarg007 
 
-erpnext/controllers/            @deepeshgarg007 @nextchamp-saqib @rohitwaghchaure
-erpnext/patches/                @deepeshgarg007 @nextchamp-saqib
-
-.github/                        @ankush
+.github/                        @deepeshgarg007
 pyproject.toml                  @ankush
diff --git a/erpnext/accounts/doctype/account/account.json b/erpnext/accounts/doctype/account/account.json
index d2659d4..e79fb66 100644
--- a/erpnext/accounts/doctype/account/account.json
+++ b/erpnext/accounts/doctype/account/account.json
@@ -18,7 +18,6 @@
   "root_type",
   "report_type",
   "account_currency",
-  "inter_company_account",
   "column_break1",
   "parent_account",
   "account_type",
@@ -34,15 +33,11 @@
   {
    "fieldname": "properties",
    "fieldtype": "Section Break",
-   "oldfieldtype": "Section Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "oldfieldtype": "Section Break"
   },
   {
    "fieldname": "column_break0",
    "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -53,9 +48,7 @@
    "no_copy": 1,
    "oldfieldname": "account_name",
    "oldfieldtype": "Data",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "account_number",
@@ -63,17 +56,13 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Account Number",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "default": "0",
    "fieldname": "is_group",
    "fieldtype": "Check",
-   "label": "Is Group",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Is Group"
   },
   {
    "fieldname": "company",
@@ -85,9 +74,7 @@
    "options": "Company",
    "read_only": 1,
    "remember_last_selected_value": 1,
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "root_type",
@@ -95,9 +82,7 @@
    "in_standard_filter": 1,
    "label": "Root Type",
    "options": "\nAsset\nLiability\nIncome\nExpense\nEquity",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "fieldname": "report_type",
@@ -105,32 +90,18 @@
    "in_standard_filter": 1,
    "label": "Report Type",
    "options": "\nBalance Sheet\nProfit and Loss",
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "depends_on": "eval:doc.is_group==0",
    "fieldname": "account_currency",
    "fieldtype": "Link",
    "label": "Currency",
-   "options": "Currency",
-   "show_days": 1,
-   "show_seconds": 1
-  },
-  {
-   "default": "0",
-   "fieldname": "inter_company_account",
-   "fieldtype": "Check",
-   "label": "Inter Company Account",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "Currency"
   },
   {
    "fieldname": "column_break1",
    "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1,
    "width": "50%"
   },
   {
@@ -142,9 +113,7 @@
    "oldfieldtype": "Link",
    "options": "Account",
    "reqd": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "description": "Setting Account Type helps in selecting this Account in transactions.",
@@ -154,9 +123,7 @@
    "label": "Account Type",
    "oldfieldname": "account_type",
    "oldfieldtype": "Select",
-   "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "\nAccumulated Depreciation\nAsset Received But Not Billed\nBank\nCash\nChargeable\nCapital Work in Progress\nCost of Goods Sold\nDepreciation\nEquity\nExpense Account\nExpenses Included In Asset Valuation\nExpenses Included In Valuation\nFixed Asset\nIncome Account\nPayable\nReceivable\nRound Off\nStock\nStock Adjustment\nStock Received But Not Billed\nService Received But Not Billed\nTax\nTemporary"
   },
   {
    "description": "Rate at which this tax is applied",
@@ -164,9 +131,7 @@
    "fieldtype": "Float",
    "label": "Rate",
    "oldfieldname": "tax_rate",
-   "oldfieldtype": "Currency",
-   "show_days": 1,
-   "show_seconds": 1
+   "oldfieldtype": "Currency"
   },
   {
    "description": "If the account is frozen, entries are allowed to restricted users.",
@@ -175,17 +140,13 @@
    "label": "Frozen",
    "oldfieldname": "freeze_account",
    "oldfieldtype": "Select",
-   "options": "No\nYes",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "No\nYes"
   },
   {
    "fieldname": "balance_must_be",
    "fieldtype": "Select",
    "label": "Balance must be",
-   "options": "\nDebit\nCredit",
-   "show_days": 1,
-   "show_seconds": 1
+   "options": "\nDebit\nCredit"
   },
   {
    "fieldname": "lft",
@@ -194,9 +155,7 @@
    "label": "Lft",
    "print_hide": 1,
    "read_only": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "fieldname": "rgt",
@@ -205,9 +164,7 @@
    "label": "Rgt",
    "print_hide": 1,
    "read_only": 1,
-   "search_index": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "search_index": 1
   },
   {
    "fieldname": "old_parent",
@@ -215,33 +172,27 @@
    "hidden": 1,
    "label": "Old Parent",
    "print_hide": 1,
-   "read_only": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "read_only": 1
   },
   {
    "default": "0",
    "depends_on": "eval:(doc.report_type == 'Profit and Loss' && !doc.is_group)",
    "fieldname": "include_in_gross",
    "fieldtype": "Check",
-   "label": "Include in gross",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Include in gross"
   },
   {
    "default": "0",
    "fieldname": "disabled",
    "fieldtype": "Check",
-   "label": "Disable",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Disable"
   }
  ],
  "icon": "fa fa-money",
  "idx": 1,
  "is_tree": 1,
  "links": [],
- "modified": "2020-06-11 15:15:54.338622",
+ "modified": "2023-04-11 16:08:46.983677",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Account",
@@ -301,5 +252,6 @@
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index ec0ba08..0404d1c 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -394,7 +394,13 @@
 
 	if ancestors and not allow_independent_account_creation:
 		for ancestor in ancestors:
-			if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
+			old_name = frappe.db.get_value(
+				"Account",
+				{"account_number": old_acc_number, "account_name": old_acc_name, "company": ancestor},
+				"name",
+			)
+
+			if old_name:
 				# same account in parent company exists
 				allow_child_account_creation = _("Allow Account Creation Against Child Company")
 
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
index 75f8f06..9e67c4c 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/chart_of_accounts.py
@@ -29,6 +29,7 @@
 					"root_type",
 					"is_group",
 					"tax_rate",
+					"account_currency",
 				]:
 
 					account_number = cstr(child.get("account_number")).strip()
@@ -95,7 +96,17 @@
 		is_group = child.get("is_group")
 	elif len(
 		set(child.keys())
-		- set(["account_name", "account_type", "root_type", "is_group", "tax_rate", "account_number"])
+		- set(
+			[
+				"account_name",
+				"account_type",
+				"root_type",
+				"is_group",
+				"tax_rate",
+				"account_number",
+				"account_currency",
+			]
+		)
 	):
 		is_group = 1
 	else:
@@ -185,6 +196,7 @@
 			"root_type",
 			"tax_rate",
 			"account_number",
+			"account_currency",
 		],
 		order_by="lft, rgt",
 	)
@@ -267,6 +279,7 @@
 				"root_type",
 				"is_group",
 				"tax_rate",
+				"account_currency",
 			]:
 				continue
 
diff --git a/erpnext/accounts/report/tax_detail/__init__.py b/erpnext/accounts/doctype/account_closing_balance/__init__.py
similarity index 100%
rename from erpnext/accounts/report/tax_detail/__init__.py
rename to erpnext/accounts/doctype/account_closing_balance/__init__.py
diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.js b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.js
new file mode 100644
index 0000000..e355914
--- /dev/null
+++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+// frappe.ui.form.on("Account Closing Balance", {
+// 	refresh(frm) {
+
+// 	},
+// });
diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json
new file mode 100644
index 0000000..8dacb96
--- /dev/null
+++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.json
@@ -0,0 +1,164 @@
+{
+ "actions": [],
+ "creation": "2023-02-21 15:20:59.586811",
+ "default_view": "List",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+  "closing_date",
+  "account",
+  "cost_center",
+  "debit",
+  "credit",
+  "account_currency",
+  "debit_in_account_currency",
+  "credit_in_account_currency",
+  "project",
+  "company",
+  "finance_book",
+  "period_closing_voucher",
+  "is_period_closing_voucher_entry"
+ ],
+ "fields": [
+  {
+   "fieldname": "closing_date",
+   "fieldtype": "Date",
+   "in_filter": 1,
+   "in_list_view": 1,
+   "label": "Closing Date",
+   "oldfieldname": "posting_date",
+   "oldfieldtype": "Date",
+   "search_index": 1
+  },
+  {
+   "fieldname": "account",
+   "fieldtype": "Link",
+   "in_filter": 1,
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Account",
+   "oldfieldname": "account",
+   "oldfieldtype": "Link",
+   "options": "Account",
+   "search_index": 1
+  },
+  {
+   "fieldname": "cost_center",
+   "fieldtype": "Link",
+   "in_filter": 1,
+   "in_list_view": 1,
+   "label": "Cost Center",
+   "oldfieldname": "cost_center",
+   "oldfieldtype": "Link",
+   "options": "Cost Center"
+  },
+  {
+   "fieldname": "debit",
+   "fieldtype": "Currency",
+   "label": "Debit Amount",
+   "oldfieldname": "debit",
+   "oldfieldtype": "Currency",
+   "options": "Company:company:default_currency"
+  },
+  {
+   "fieldname": "credit",
+   "fieldtype": "Currency",
+   "label": "Credit Amount",
+   "oldfieldname": "credit",
+   "oldfieldtype": "Currency",
+   "options": "Company:company:default_currency"
+  },
+  {
+   "fieldname": "account_currency",
+   "fieldtype": "Link",
+   "label": "Account Currency",
+   "options": "Currency"
+  },
+  {
+   "fieldname": "debit_in_account_currency",
+   "fieldtype": "Currency",
+   "label": "Debit Amount in Account Currency",
+   "options": "account_currency"
+  },
+  {
+   "fieldname": "credit_in_account_currency",
+   "fieldtype": "Currency",
+   "label": "Credit Amount in Account Currency",
+   "options": "account_currency"
+  },
+  {
+   "fieldname": "project",
+   "fieldtype": "Link",
+   "label": "Project",
+   "options": "Project"
+  },
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "in_filter": 1,
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Company",
+   "oldfieldname": "company",
+   "oldfieldtype": "Link",
+   "options": "Company",
+   "search_index": 1
+  },
+  {
+   "fieldname": "finance_book",
+   "fieldtype": "Link",
+   "label": "Finance Book",
+   "options": "Finance Book"
+  },
+  {
+   "fieldname": "period_closing_voucher",
+   "fieldtype": "Link",
+   "in_standard_filter": 1,
+   "label": "Period Closing Voucher",
+   "options": "Period Closing Voucher",
+   "search_index": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "is_period_closing_voucher_entry",
+   "fieldtype": "Check",
+   "label": "Is Period Closing Voucher Entry"
+  }
+ ],
+ "icon": "fa fa-list",
+ "in_create": 1,
+ "links": [],
+ "modified": "2023-03-06 08:56:36.393237",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Account Closing Balance",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts User"
+  },
+  {
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Accounts Manager"
+  },
+  {
+   "export": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Auditor"
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py
new file mode 100644
index 0000000..7c84237
--- /dev/null
+++ b/erpnext/accounts/doctype/account_closing_balance/account_closing_balance.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe.model.document import Document
+from frappe.utils import cint, cstr
+
+from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
+	get_accounting_dimensions,
+)
+
+
+class AccountClosingBalance(Document):
+	pass
+
+
+def make_closing_entries(closing_entries, voucher_name):
+	accounting_dimensions = get_accounting_dimensions()
+	company = closing_entries[0].get("company")
+	closing_date = closing_entries[0].get("closing_date")
+
+	previous_closing_entries = get_previous_closing_entries(
+		company, closing_date, accounting_dimensions
+	)
+	combined_entries = closing_entries + previous_closing_entries
+
+	merged_entries = aggregate_with_last_account_closing_balance(
+		combined_entries, accounting_dimensions
+	)
+
+	for key, value in merged_entries.items():
+		cle = frappe.new_doc("Account Closing Balance")
+		cle.update(value)
+		cle.update(value["dimensions"])
+		cle.update(
+			{
+				"period_closing_voucher": voucher_name,
+				"closing_date": closing_date,
+			}
+		)
+		cle.submit()
+
+
+def aggregate_with_last_account_closing_balance(entries, accounting_dimensions):
+	merged_entries = {}
+	for entry in entries:
+		key, key_values = generate_key(entry, accounting_dimensions)
+		merged_entries.setdefault(
+			key,
+			{
+				"debit": 0,
+				"credit": 0,
+				"debit_in_account_currency": 0,
+				"credit_in_account_currency": 0,
+			},
+		)
+
+		merged_entries[key]["dimensions"] = key_values
+		merged_entries[key]["debit"] += entry.get("debit")
+		merged_entries[key]["credit"] += entry.get("credit")
+		merged_entries[key]["debit_in_account_currency"] += entry.get("debit_in_account_currency")
+		merged_entries[key]["credit_in_account_currency"] += entry.get("credit_in_account_currency")
+
+	return merged_entries
+
+
+def generate_key(entry, accounting_dimensions):
+	key = [
+		cstr(entry.get("account")),
+		cstr(entry.get("account_currency")),
+		cstr(entry.get("cost_center")),
+		cstr(entry.get("project")),
+		cstr(entry.get("finance_book")),
+		cint(entry.get("is_period_closing_voucher_entry")),
+	]
+
+	key_values = {
+		"company": cstr(entry.get("company")),
+		"account": cstr(entry.get("account")),
+		"account_currency": cstr(entry.get("account_currency")),
+		"cost_center": cstr(entry.get("cost_center")),
+		"project": cstr(entry.get("project")),
+		"finance_book": cstr(entry.get("finance_book")),
+		"is_period_closing_voucher_entry": cint(entry.get("is_period_closing_voucher_entry")),
+	}
+	for dimension in accounting_dimensions:
+		key.append(cstr(entry.get(dimension)))
+		key_values[dimension] = cstr(entry.get(dimension))
+
+	return tuple(key), key_values
+
+
+def get_previous_closing_entries(company, closing_date, accounting_dimensions):
+	entries = []
+	last_period_closing_voucher = frappe.db.get_all(
+		"Period Closing Voucher",
+		filters={"docstatus": 1, "company": company, "posting_date": ("<", closing_date)},
+		fields=["name"],
+		order_by="posting_date desc",
+		limit=1,
+	)
+
+	if last_period_closing_voucher:
+		account_closing_balance = frappe.qb.DocType("Account Closing Balance")
+		query = frappe.qb.from_(account_closing_balance).select(
+			account_closing_balance.company,
+			account_closing_balance.account,
+			account_closing_balance.account_currency,
+			account_closing_balance.debit,
+			account_closing_balance.credit,
+			account_closing_balance.debit_in_account_currency,
+			account_closing_balance.credit_in_account_currency,
+			account_closing_balance.cost_center,
+			account_closing_balance.project,
+			account_closing_balance.finance_book,
+			account_closing_balance.is_period_closing_voucher_entry,
+		)
+
+		for dimension in accounting_dimensions:
+			query = query.select(account_closing_balance[dimension])
+
+		query = query.where(
+			account_closing_balance.period_closing_voucher == last_period_closing_voucher[0].name
+		)
+		entries = query.run(as_dict=1)
+
+	return entries
diff --git a/erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py b/erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py
new file mode 100644
index 0000000..fc42677
--- /dev/null
+++ b/erpnext/accounts/doctype/account_closing_balance/test_account_closing_balance.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+# import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestAccountClosingBalance(FrappeTestCase):
+	pass
diff --git a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
index 3f985b6..c0eed18 100644
--- a/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
+++ b/erpnext/accounts/doctype/accounts_settings/accounts_settings.json
@@ -31,6 +31,7 @@
   "determine_address_tax_category_from",
   "column_break_19",
   "add_taxes_from_item_tax_template",
+  "book_tax_discount_loss",
   "print_settings",
   "show_inclusive_tax_in_print",
   "column_break_12",
@@ -360,6 +361,13 @@
    "fieldname": "show_balance_in_coa",
    "fieldtype": "Check",
    "label": "Show Balances in Chart Of Accounts"
+  },
+  {
+   "default": "0",
+   "description": "Split Early Payment Discount Loss into Income and Tax Loss",
+   "fieldname": "book_tax_discount_loss",
+   "fieldtype": "Check",
+   "label": "Book Tax Loss on Early Payment Discount"
   }
  ],
  "icon": "icon-cog",
@@ -367,7 +375,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-01-02 12:07:42.434214",
+ "modified": "2023-03-28 09:50:20.375233",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounts Settings",
diff --git a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
index 80878ac..0817187 100644
--- a/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
+++ b/erpnext/accounts/doctype/bank_clearance/bank_clearance.py
@@ -81,7 +81,7 @@
 
 		loan_disbursement = frappe.qb.DocType("Loan Disbursement")
 
-		loan_disbursements = (
+		query = (
 			frappe.qb.from_(loan_disbursement)
 			.select(
 				ConstantColumn("Loan Disbursement").as_("payment_document"),
@@ -90,17 +90,22 @@
 				ConstantColumn(0).as_("debit"),
 				loan_disbursement.reference_number.as_("cheque_number"),
 				loan_disbursement.reference_date.as_("cheque_date"),
+				loan_disbursement.clearance_date.as_("clearance_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, order=frappe.qb.desc)
-		).run(as_dict=1)
+		)
+
+		if not self.include_reconciled_entries:
+			query = query.where(loan_disbursement.clearance_date.isnull())
+
+		loan_disbursements = query.run(as_dict=1)
 
 		loan_repayment = frappe.qb.DocType("Loan Repayment")
 
@@ -113,16 +118,19 @@
 				ConstantColumn(0).as_("credit"),
 				loan_repayment.reference_number.as_("cheque_number"),
 				loan_repayment.reference_date.as_("cheque_date"),
+				loan_repayment.clearance_date.as_("clearance_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]))
 		)
 
+		if not self.include_reconciled_entries:
+			query = query.where(loan_repayment.clearance_date.isnull())
+
 		if frappe.db.has_column("Loan Repayment", "repay_from_salary"):
 			query = query.where((loan_repayment.repay_from_salary == 0))
 
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
index ae84154..d977261 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.js
@@ -18,6 +18,10 @@
 	},
 
 	onload: function (frm) {
+		// Set default filter dates
+		today = frappe.datetime.get_today()
+		frm.doc.bank_statement_from_date = frappe.datetime.add_months(today, -1);
+		frm.doc.bank_statement_to_date = today;
 		frm.trigger('bank_account');
 	},
 
@@ -32,6 +36,7 @@
 	},
 
 	refresh: function (frm) {
+		frm.disable_save();
 		frappe.require("bank-reconciliation-tool.bundle.js", () =>
 			frm.trigger("make_reconciliation_tool")
 		);
@@ -72,10 +77,12 @@
 				},
 			})
 		});
-	},
 
-	after_save: function (frm) {
-		frm.trigger("make_reconciliation_tool");
+		frm.add_custom_button(__('Get Unreconciled Entries'), function() {
+			frm.trigger("make_reconciliation_tool");
+		});
+		frm.change_custom_button_type('Get Unreconciled Entries', null, 'primary');
+
 	},
 
 	bank_account: function (frm) {
@@ -89,7 +96,7 @@
 					r.account,
 					"account_currency",
 					(r) => {
-						frm.currency = r.account_currency;
+						frm.doc.account_currency = r.account_currency;
 						frm.trigger("render_chart");
 					}
 				);
@@ -162,9 +169,9 @@
 					"reconciliation_tool_cards"
 				).$wrapper,
 				bank_statement_closing_balance:
-					frm.doc.bank_statement_closing_balance,
+				frm.doc.bank_statement_closing_balance,
 				cleared_balance: frm.cleared_balance,
-				currency: frm.currency,
+				currency: frm.doc.account_currency,
 			}
 		);
 	},
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
index 80993d6..93fc443 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/bank_reconciliation_tool.json
@@ -14,6 +14,7 @@
   "to_reference_date",
   "filter_by_reference_date",
   "column_break_2",
+  "account_currency",
   "account_opening_balance",
   "bank_statement_closing_balance",
   "section_break_1",
@@ -59,7 +60,7 @@
    "fieldname": "account_opening_balance",
    "fieldtype": "Currency",
    "label": "Account Opening Balance",
-   "options": "Currency",
+   "options": "account_currency",
    "read_only": 1
   },
   {
@@ -67,7 +68,7 @@
    "fieldname": "bank_statement_closing_balance",
    "fieldtype": "Currency",
    "label": "Closing Balance",
-   "options": "Currency"
+   "options": "account_currency"
   },
   {
    "fieldname": "section_break_1",
@@ -104,13 +105,20 @@
    "fieldname": "filter_by_reference_date",
    "fieldtype": "Check",
    "label": "Filter by Reference Date"
+  },
+  {
+   "fieldname": "account_currency",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Account Currency",
+   "options": "Currency"
   }
  ],
  "hide_toolbar": 1,
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-01-13 13:00:02.022919",
+ "modified": "2023-03-07 11:02:24.535714",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Bank Reconciliation Tool",
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 1516237..fcbaf32 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -46,7 +46,7 @@
 	def add_payment_entries(self, vouchers):
 		"Add the vouchers with zero allocation. Save() will perform the allocations and clearance"
 		if 0.0 >= self.unallocated_amount:
-			frappe.throw(frappe._(f"Bank Transaction {self.name} is already fully reconciled"))
+			frappe.throw(frappe._("Bank Transaction {0} is already fully reconciled").format(self.name))
 
 		added = False
 		for voucher in vouchers:
@@ -114,9 +114,7 @@
 
 				elif 0.0 > unallocated_amount:
 					self.db_delete_payment_entry(payment_entry)
-					frappe.throw(
-						frappe._(f"Voucher {payment_entry.payment_entry} is over-allocated by {unallocated_amount}")
-					)
+					frappe.throw(frappe._("Voucher {0} is over-allocated by {1}").format(unallocated_amount))
 
 		self.reload()
 
@@ -178,7 +176,9 @@
 		if gle["gl_account"] == gl_bank_account:
 			if gle["amount"] <= 0.0:
 				frappe.throw(
-					frappe._(f"Voucher {payment_entry.payment_entry} value is broken: {gle['amount']}")
+					frappe._("Voucher {0} value is broken: {1}").format(
+						payment_entry.payment_entry, gle["amount"]
+					)
 				)
 
 			unmatched_gles -= 1
diff --git a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
index 220b747..d6e1be4 100644
--- a/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
+++ b/erpnext/accounts/doctype/chart_of_accounts_importer/chart_of_accounts_importer.py
@@ -36,7 +36,7 @@
 
 	no_of_columns = max([len(d) for d in data])
 
-	if no_of_columns > 7:
+	if no_of_columns > 8:
 		frappe.throw(
 			_("More columns found than expected. Please compare the uploaded file with standard template"),
 			title=(_("Wrong Template")),
@@ -233,6 +233,7 @@
 			is_group,
 			account_type,
 			root_type,
+			account_currency,
 		) = i
 
 		if not account_name:
@@ -253,6 +254,8 @@
 			charts_map[account_name]["account_type"] = account_type
 		if root_type:
 			charts_map[account_name]["root_type"] = root_type
+		if account_currency:
+			charts_map[account_name]["account_currency"] = account_currency
 		path = return_parent(data, account_name)[::-1]
 		paths.append(path)  # List of path is created
 		line_no += 1
@@ -315,20 +318,21 @@
 		"Is Group",
 		"Account Type",
 		"Root Type",
+		"Account Currency",
 	]
 	writer = UnicodeWriter()
 	writer.writerow(fields)
 
 	if template_type == "Blank Template":
 		for root_type in get_root_types():
-			writer.writerow(["", "", "", 1, "", root_type])
+			writer.writerow(["", "", "", "", 1, "", root_type])
 
 		for account in get_mandatory_group_accounts():
-			writer.writerow(["", "", "", 1, account, "Asset"])
+			writer.writerow(["", "", "", "", 1, account, "Asset"])
 
 		for account_type in get_mandatory_account_types():
 			writer.writerow(
-				["", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
+				["", "", "", "", 0, account_type.get("account_type"), account_type.get("root_type")]
 			)
 	else:
 		writer = get_sample_template(writer)
diff --git a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
index d67d59b..81c2d8b 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -211,8 +211,7 @@
 			# Handle Accounts with '0' balance in Account/Base Currency
 			for d in [x for x in account_details if x.zero_balance]:
 
-				# TODO: Set new balance in Base/Account currency
-				if d.balance > 0:
+				if d.balance != 0:
 					current_exchange_rate = new_exchange_rate = 0
 
 					new_balance_in_account_currency = 0  # this will be '0'
@@ -399,6 +398,9 @@
 
 		journal_entry_accounts = []
 		for d in accounts:
+			if not flt(d.get("balance_in_account_currency"), d.precision("balance_in_account_currency")):
+				continue
+
 			dr_or_cr = (
 				"debit_in_account_currency"
 				if d.get("balance_in_account_currency") > 0
@@ -448,7 +450,13 @@
 				}
 			)
 
-		journal_entry_accounts.append(
+		journal_entry.set("accounts", journal_entry_accounts)
+		journal_entry.set_amounts_in_company_currency()
+		journal_entry.set_total_debit_credit()
+
+		self.gain_loss_unbooked += journal_entry.difference - self.gain_loss_unbooked
+		journal_entry.append(
+			"accounts",
 			{
 				"account": unrealized_exchange_gain_loss_account,
 				"balance": get_balance_on(unrealized_exchange_gain_loss_account),
@@ -460,10 +468,9 @@
 				"exchange_rate": 1,
 				"reference_type": "Exchange Rate Revaluation",
 				"reference_name": self.name,
-			}
+			},
 		)
 
-		journal_entry.set("accounts", journal_entry_accounts)
 		journal_entry.set_amounts_in_company_currency()
 		journal_entry.set_total_debit_credit()
 		journal_entry.save()
@@ -483,6 +490,8 @@
 		conditions.append(gl.company == company)
 		conditions.append(gl.account == account)
 		conditions.append(gl.is_cancelled == 0)
+		conditions.append((gl.debit > 0) | (gl.credit > 0))
+		conditions.append((gl.debit_in_account_currency > 0) | (gl.credit_in_account_currency > 0))
 		if party_type:
 			conditions.append(gl.party_type == party_type)
 		if party:
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.json b/erpnext/accounts/doctype/journal_entry/journal_entry.json
index 498fc7c..80e7222 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.json
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.json
@@ -137,7 +137,8 @@
    "fieldname": "finance_book",
    "fieldtype": "Link",
    "label": "Finance Book",
-   "options": "Finance Book"
+   "options": "Finance Book",
+   "read_only": 1
   },
   {
    "fieldname": "2_add_edit_gl_entries",
@@ -538,7 +539,7 @@
  "idx": 176,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-17 12:53:53.280620",
+ "modified": "2023-03-01 14:58:59.286591",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Journal Entry",
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.py b/erpnext/accounts/doctype/journal_entry/journal_entry.py
index db399b7..68364be 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.py
@@ -51,7 +51,7 @@
 		self.validate_multi_currency()
 		self.set_amounts_in_company_currency()
 		self.validate_debit_credit_amount()
-
+		self.set_total_debit_credit()
 		# Do not validate while importing via data import
 		if not frappe.flags.in_import:
 			self.validate_total_debit_and_credit()
@@ -666,7 +666,6 @@
 					frappe.throw(_("Row {0}: Both Debit and Credit values cannot be zero").format(d.idx))
 
 	def validate_total_debit_and_credit(self):
-		self.set_total_debit_credit()
 		if not (self.voucher_type == "Exchange Gain Or Loss" and self.multi_currency):
 			if self.difference:
 				frappe.throw(
diff --git a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
index 2cc5378..f7297d1 100644
--- a/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
+++ b/erpnext/accounts/doctype/journal_entry/test_journal_entry.py
@@ -287,10 +287,6 @@
 		jv.submit()
 
 	def test_inter_company_jv(self):
-		frappe.db.set_value("Account", "Sales Expenses - _TC", "inter_company_account", 1)
-		frappe.db.set_value("Account", "Buildings - _TC", "inter_company_account", 1)
-		frappe.db.set_value("Account", "Sales Expenses - _TC1", "inter_company_account", 1)
-		frappe.db.set_value("Account", "Buildings - _TC1", "inter_company_account", 1)
 		jv = make_journal_entry(
 			"Sales Expenses - _TC",
 			"Buildings - _TC",
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 91374ae..f8969b8 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -217,7 +217,6 @@
 		frm.toggle_display("set_exchange_gain_loss",
 			frm.doc.paid_amount && frm.doc.received_amount && frm.doc.difference_amount);
 
-		frm.refresh_fields();
 	},
 
 	set_dynamic_labels: function(frm) {
@@ -245,8 +244,6 @@
 		frm.set_currency_labels(["total_amount", "outstanding_amount", "allocated_amount"],
 			party_account_currency, "references");
 
-		frm.set_currency_labels(["amount"], company_currency, "deductions");
-
 		cur_frm.set_df_property("source_exchange_rate", "description",
 			("1 " + frm.doc.paid_from_account_currency + " = [?] " + company_currency));
 
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index cd5b6d5..c34bddd 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -416,7 +416,7 @@
 
 		for ref in self.get("references"):
 			if ref.payment_term and ref.reference_name:
-				key = (ref.payment_term, ref.reference_name)
+				key = (ref.payment_term, ref.reference_name, ref.reference_doctype)
 				invoice_payment_amount_map.setdefault(key, 0.0)
 				invoice_payment_amount_map[key] += ref.allocated_amount
 
@@ -424,20 +424,37 @@
 					payment_schedule = frappe.get_all(
 						"Payment Schedule",
 						filters={"parent": ref.reference_name},
-						fields=["paid_amount", "payment_amount", "payment_term", "discount", "outstanding"],
+						fields=[
+							"paid_amount",
+							"payment_amount",
+							"payment_term",
+							"discount",
+							"outstanding",
+							"discount_type",
+						],
 					)
 					for term in payment_schedule:
-						invoice_key = (term.payment_term, ref.reference_name)
+						invoice_key = (term.payment_term, ref.reference_name, ref.reference_doctype)
 						invoice_paid_amount_map.setdefault(invoice_key, {})
 						invoice_paid_amount_map[invoice_key]["outstanding"] = term.outstanding
-						invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
-							term.discount / 100
-						)
+						if not (term.discount_type and term.discount):
+							continue
+
+						if term.discount_type == "Percentage":
+							invoice_paid_amount_map[invoice_key]["discounted_amt"] = ref.total_amount * (
+								term.discount / 100
+							)
+						else:
+							invoice_paid_amount_map[invoice_key]["discounted_amt"] = term.discount
 
 		for idx, (key, allocated_amount) in enumerate(invoice_payment_amount_map.items(), 1):
 			if not invoice_paid_amount_map.get(key):
 				frappe.throw(_("Payment term {0} not used in {1}").format(key[0], key[1]))
 
+			allocated_amount = self.get_allocated_amount_in_transaction_currency(
+				allocated_amount, key[2], key[1]
+			)
+
 			outstanding = flt(invoice_paid_amount_map.get(key, {}).get("outstanding"))
 			discounted_amt = flt(invoice_paid_amount_map.get(key, {}).get("discounted_amt"))
 
@@ -472,6 +489,33 @@
 						(allocated_amount - discounted_amt, discounted_amt, allocated_amount, key[1], key[0]),
 					)
 
+	def get_allocated_amount_in_transaction_currency(
+		self, allocated_amount, reference_doctype, reference_docname
+	):
+		"""
+		Payment Entry could be in base currency while reference's payment schedule
+		is always in transaction currency.
+		E.g.
+		* SI with base=INR and currency=USD
+		* SI with payment schedule in USD
+		* PE in INR (accounting done in base currency)
+		"""
+		ref_currency, ref_exchange_rate = frappe.db.get_value(
+			reference_doctype, reference_docname, ["currency", "conversion_rate"]
+		)
+		is_single_currency = self.paid_from_account_currency == self.paid_to_account_currency
+		# PE in different currency
+		reference_is_multi_currency = self.paid_from_account_currency != ref_currency
+
+		if not (is_single_currency and reference_is_multi_currency):
+			return allocated_amount
+
+		allocated_amount = flt(
+			allocated_amount / ref_exchange_rate, self.precision("total_allocated_amount")
+		)
+
+		return allocated_amount
+
 	def set_status(self):
 		if self.docstatus == 2:
 			self.status = "Cancelled"
@@ -1642,7 +1686,14 @@
 
 @frappe.whitelist()
 def get_payment_entry(
-	dt, dn, party_amount=None, bank_account=None, bank_amount=None, party_type=None, payment_type=None
+	dt,
+	dn,
+	party_amount=None,
+	bank_account=None,
+	bank_amount=None,
+	party_type=None,
+	payment_type=None,
+	reference_date=None,
 ):
 	reference_doc = None
 	doc = frappe.get_doc(dt, dn)
@@ -1669,8 +1720,9 @@
 		dt, party_account_currency, bank, outstanding_amount, payment_type, bank_amount, doc
 	)
 
-	paid_amount, received_amount, discount_amount = apply_early_payment_discount(
-		paid_amount, received_amount, doc
+	reference_date = getdate(reference_date)
+	paid_amount, received_amount, discount_amount, valid_discounts = apply_early_payment_discount(
+		paid_amount, received_amount, doc, party_account_currency, reference_date
 	)
 
 	pe = frappe.new_doc("Payment Entry")
@@ -1678,6 +1730,7 @@
 	pe.company = doc.company
 	pe.cost_center = doc.get("cost_center")
 	pe.posting_date = nowdate()
+	pe.reference_date = reference_date
 	pe.mode_of_payment = doc.get("mode_of_payment")
 	pe.party_type = party_type
 	pe.party = doc.get(scrub(party_type))
@@ -1718,7 +1771,7 @@
 		):
 
 			for reference in get_reference_as_per_payment_terms(
-				doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+				doc.payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
 			):
 				pe.append("references", reference)
 		else:
@@ -1769,16 +1822,17 @@
 	if party_account and bank:
 		pe.set_exchange_rate(ref_doc=reference_doc)
 		pe.set_amounts()
+
 		if discount_amount:
-			pe.set_gain_or_loss(
-				account_details={
-					"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
-					"cost_center": pe.cost_center
-					or frappe.get_cached_value("Company", pe.company, "cost_center"),
-					"amount": discount_amount * (-1 if payment_type == "Pay" else 1),
-				}
+			base_total_discount_loss = 0
+			if frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss"):
+				base_total_discount_loss = split_early_payment_discount_loss(pe, doc, valid_discounts)
+
+			set_pending_discount_loss(
+				pe, doc, discount_amount, base_total_discount_loss, party_account_currency
 			)
-			pe.set_difference_amount()
+
+		pe.set_difference_amount()
 
 	return pe
 
@@ -1889,20 +1943,28 @@
 	return paid_amount, received_amount
 
 
-def apply_early_payment_discount(paid_amount, received_amount, doc):
+def apply_early_payment_discount(
+	paid_amount, received_amount, doc, party_account_currency, reference_date
+):
 	total_discount = 0
+	valid_discounts = []
 	eligible_for_payments = ["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"]
 	has_payment_schedule = hasattr(doc, "payment_schedule") and doc.payment_schedule
+	is_multi_currency = party_account_currency != doc.company_currency
 
 	if doc.doctype in eligible_for_payments and has_payment_schedule:
 		for term in doc.payment_schedule:
-			if not term.discounted_amount and term.discount and getdate(nowdate()) <= term.discount_date:
+			if not term.discounted_amount and term.discount and reference_date <= term.discount_date:
+
 				if term.discount_type == "Percentage":
-					discount_amount = flt(doc.get("grand_total")) * (term.discount / 100)
+					grand_total = doc.get("grand_total") if is_multi_currency else doc.get("base_grand_total")
+					discount_amount = flt(grand_total) * (term.discount / 100)
 				else:
 					discount_amount = term.discount
 
-				discount_amount_in_foreign_currency = discount_amount * doc.get("conversion_rate", 1)
+				# if accounting is done in the same currency, paid_amount = received_amount
+				conversion_rate = doc.get("conversion_rate", 1) if is_multi_currency else 1
+				discount_amount_in_foreign_currency = discount_amount * conversion_rate
 
 				if doc.doctype == "Sales Invoice":
 					paid_amount -= discount_amount
@@ -1911,23 +1973,151 @@
 					received_amount -= discount_amount
 					paid_amount -= discount_amount_in_foreign_currency
 
+				valid_discounts.append({"type": term.discount_type, "discount": term.discount})
 				total_discount += discount_amount
 
 		if total_discount:
-			money = frappe.utils.fmt_money(total_discount, currency=doc.get("currency"))
+			currency = doc.get("currency") if is_multi_currency else doc.company_currency
+			money = frappe.utils.fmt_money(total_discount, currency=currency)
 			frappe.msgprint(_("Discount of {} applied as per Payment Term").format(money), alert=1)
 
-	return paid_amount, received_amount, total_discount
+	return paid_amount, received_amount, total_discount, valid_discounts
+
+
+def set_pending_discount_loss(
+	pe, doc, discount_amount, base_total_discount_loss, party_account_currency
+):
+	# If multi-currency, get base discount amount to adjust with base currency deductions/losses
+	if party_account_currency != doc.company_currency:
+		discount_amount = discount_amount * doc.get("conversion_rate", 1)
+
+	# Avoid considering miniscule losses
+	discount_amount = flt(discount_amount - base_total_discount_loss, doc.precision("grand_total"))
+
+	# Set base discount amount (discount loss/pending rounding loss) in deductions
+	if discount_amount > 0.0:
+		positive_negative = -1 if pe.payment_type == "Pay" else 1
+
+		# If tax loss booking is enabled, pending loss will be rounding loss.
+		# Otherwise it will be the total discount loss.
+		book_tax_loss = frappe.db.get_single_value("Accounts Settings", "book_tax_discount_loss")
+		account_type = "round_off_account" if book_tax_loss else "default_discount_account"
+
+		pe.set_gain_or_loss(
+			account_details={
+				"account": frappe.get_cached_value("Company", pe.company, account_type),
+				"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+				"amount": discount_amount * positive_negative,
+			}
+		)
+
+
+def split_early_payment_discount_loss(pe, doc, valid_discounts) -> float:
+	"""Split early payment discount into Income Loss & Tax Loss."""
+	total_discount_percent = get_total_discount_percent(doc, valid_discounts)
+
+	if not total_discount_percent:
+		return 0.0
+
+	base_loss_on_income = add_income_discount_loss(pe, doc, total_discount_percent)
+	base_loss_on_taxes = add_tax_discount_loss(pe, doc, total_discount_percent)
+
+	# Round off total loss rather than individual losses to reduce rounding error
+	return flt(base_loss_on_income + base_loss_on_taxes, doc.precision("grand_total"))
+
+
+def get_total_discount_percent(doc, valid_discounts) -> float:
+	"""Get total percentage and amount discount applied as a percentage."""
+	total_discount_percent = (
+		sum(
+			discount.get("discount") for discount in valid_discounts if discount.get("type") == "Percentage"
+		)
+		or 0.0
+	)
+
+	# Operate in percentages only as it makes the income & tax split easier
+	total_discount_amount = (
+		sum(discount.get("discount") for discount in valid_discounts if discount.get("type") == "Amount")
+		or 0.0
+	)
+
+	if total_discount_amount:
+		discount_percentage = (total_discount_amount / doc.get("grand_total")) * 100
+		total_discount_percent += discount_percentage
+		return total_discount_percent
+
+	return total_discount_percent
+
+
+def add_income_discount_loss(pe, doc, total_discount_percent) -> float:
+	"""Add loss on income discount in base currency."""
+	precision = doc.precision("total")
+	base_loss_on_income = doc.get("base_total") * (total_discount_percent / 100)
+
+	pe.append(
+		"deductions",
+		{
+			"account": frappe.get_cached_value("Company", pe.company, "default_discount_account"),
+			"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+			"amount": flt(base_loss_on_income, precision),
+		},
+	)
+
+	return base_loss_on_income  # Return loss without rounding
+
+
+def add_tax_discount_loss(pe, doc, total_discount_percentage) -> float:
+	"""Add loss on tax discount in base currency."""
+	tax_discount_loss = {}
+	base_total_tax_loss = 0
+	precision = doc.precision("tax_amount_after_discount_amount", "taxes")
+
+	# The same account head could be used more than once
+	for tax in doc.get("taxes", []):
+		base_tax_loss = tax.get("base_tax_amount_after_discount_amount") * (
+			total_discount_percentage / 100
+		)
+
+		account = tax.get("account_head")
+		if not tax_discount_loss.get(account):
+			tax_discount_loss[account] = base_tax_loss
+		else:
+			tax_discount_loss[account] += base_tax_loss
+
+	for account, loss in tax_discount_loss.items():
+		base_total_tax_loss += loss
+		if loss == 0.0:
+			continue
+
+		pe.append(
+			"deductions",
+			{
+				"account": account,
+				"cost_center": pe.cost_center or frappe.get_cached_value("Company", pe.company, "cost_center"),
+				"amount": flt(loss, precision),
+			},
+		)
+
+	return base_total_tax_loss  # Return loss without rounding
 
 
 def get_reference_as_per_payment_terms(
-	payment_schedule, dt, dn, doc, grand_total, outstanding_amount
+	payment_schedule, dt, dn, doc, grand_total, outstanding_amount, party_account_currency
 ):
 	references = []
+	is_multi_currency_acc = (doc.currency != doc.company_currency) and (
+		party_account_currency != doc.company_currency
+	)
+
 	for payment_term in payment_schedule:
 		payment_term_outstanding = flt(
 			payment_term.payment_amount - payment_term.paid_amount, payment_term.precision("payment_amount")
 		)
+		if not is_multi_currency_acc:
+			# If accounting is done in company currency for multi-currency transaction
+			payment_term_outstanding = flt(
+				payment_term_outstanding * doc.get("conversion_rate"), payment_term.precision("payment_amount")
+			)
 
 		if payment_term_outstanding:
 			references.append(
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 123b5df..67049c4 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -5,7 +5,7 @@
 
 import frappe
 from frappe import qb
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import flt, nowdate
 
 from erpnext.accounts.doctype.payment_entry.payment_entry import (
@@ -256,10 +256,25 @@
 			},
 		)
 		si.save()
-
 		si.submit()
 
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
+		pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+
+		self.assertEqual(pe_with_tax_loss.references[0].payment_term, "30 Credit Days with 10% Discount")
+		self.assertEqual(pe_with_tax_loss.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe_with_tax_loss.paid_amount, 212.4)
+		self.assertEqual(pe_with_tax_loss.deductions[0].amount, 20.0)  # Loss on Income
+		self.assertEqual(pe_with_tax_loss.deductions[1].amount, 3.6)  # Loss on Tax
+		self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
+
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
 		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+
+		self.assertEqual(pe.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe.paid_amount, 212.4)
+		self.assertEqual(pe.deductions[0].amount, 23.6)
+
 		pe.submit()
 		si.load_from_db()
 
@@ -269,6 +284,190 @@
 		self.assertEqual(si.payment_schedule[0].outstanding, 0)
 		self.assertEqual(si.payment_schedule[0].discounted_amount, 23.6)
 
+	def test_payment_entry_against_payment_terms_with_discount_amount(self):
+		si = create_sales_invoice(do_not_save=1, qty=1, rate=200)
+
+		si.payment_terms_template = "Test Discount Amount Template"
+		create_payment_terms_template_with_discount(
+			name="30 Credit Days with Rs.50 Discount",
+			discount_type="Amount",
+			discount=50,
+			template_name="Test Discount Amount Template",
+		)
+		frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
+
+		si.append(
+			"taxes",
+			{
+				"charge_type": "On Net Total",
+				"account_head": "_Test Account Service Tax - _TC",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "Service Tax",
+				"rate": 18,
+			},
+		)
+		si.save()
+		si.submit()
+
+		# Set reference date past discount cut off date
+		pe_1 = get_payment_entry(
+			"Sales Invoice",
+			si.name,
+			bank_account="_Test Cash - _TC",
+			reference_date=frappe.utils.add_days(si.posting_date, 2),
+		)
+		self.assertEqual(pe_1.paid_amount, 236.0)  # discount not applied
+
+		# Test if tax loss is booked on enabling configuration
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 1)
+		pe_with_tax_loss = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+		self.assertEqual(pe_with_tax_loss.deductions[0].amount, 42.37)  # Loss on Income
+		self.assertEqual(pe_with_tax_loss.deductions[1].amount, 7.63)  # Loss on Tax
+		self.assertEqual(pe_with_tax_loss.deductions[1].account, "_Test Account Service Tax - _TC")
+
+		frappe.db.set_single_value("Accounts Settings", "book_tax_discount_loss", 0)
+		pe = get_payment_entry("Sales Invoice", si.name, bank_account="_Test Cash - _TC")
+		self.assertEqual(pe.references[0].allocated_amount, 236.0)
+		self.assertEqual(pe.paid_amount, 186)
+		self.assertEqual(pe.deductions[0].amount, 50.0)
+
+		pe.submit()
+		si.load_from_db()
+
+		self.assertEqual(si.payment_schedule[0].payment_amount, 236.0)
+		self.assertEqual(si.payment_schedule[0].paid_amount, 186)
+		self.assertEqual(si.payment_schedule[0].outstanding, 0)
+		self.assertEqual(si.payment_schedule[0].discounted_amount, 50)
+
+	@change_settings(
+		"Accounts Settings",
+		{
+			"allow_multi_currency_invoices_against_single_party_account": 1,
+			"book_tax_discount_loss": 1,
+		},
+	)
+	def test_payment_entry_multicurrency_si_with_base_currency_accounting_early_payment_discount(
+		self,
+	):
+		"""
+		1. Multi-currency SI with single currency accounting (company currency)
+		2. PE with early payment discount
+		3. Test if Paid Amount is calculated in company currency
+		4. Test if deductions are calculated in company currency
+
+		SI is in USD to document agreed amounts that are in USD, but the accounting is in base currency.
+		"""
+		si = create_sales_invoice(
+			customer="_Test Customer",
+			currency="USD",
+			conversion_rate=50,
+			do_not_save=1,
+		)
+		create_payment_terms_template_with_discount()
+		si.payment_terms_template = "Test Discount Template"
+
+		frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
+		si.save()
+		si.submit()
+
+		pe = get_payment_entry(
+			"Sales Invoice",
+			si.name,
+			bank_account="_Test Bank - _TC",
+		)
+		pe.reference_no = si.name
+		pe.reference_date = nowdate()
+
+		# Early payment discount loss on income
+		self.assertEqual(pe.paid_amount, 4500.0)  # Amount in company currency
+		self.assertEqual(pe.received_amount, 4500.0)
+		self.assertEqual(pe.deductions[0].amount, 500.0)
+		self.assertEqual(pe.deductions[0].account, "Write Off - _TC")
+		self.assertEqual(pe.difference_amount, 0.0)
+
+		pe.insert()
+		pe.submit()
+
+		expected_gle = dict(
+			(d[0], d)
+			for d in [
+				["Debtors - _TC", 0, 5000, si.name],
+				["_Test Bank - _TC", 4500, 0, None],
+				["Write Off - _TC", 500.0, 0, None],
+			]
+		)
+
+		self.validate_gl_entries(pe.name, expected_gle)
+
+		outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
+		self.assertEqual(outstanding_amount, 0)
+
+	def test_payment_entry_multicurrency_accounting_si_with_early_payment_discount(self):
+		"""
+		1. Multi-currency SI with multi-currency accounting
+		2. PE with early payment discount and also exchange loss
+		3. Test if Paid Amount is calculated in transaction currency
+		4. Test if deductions are calculated in base/company currency
+		5. Test if exchange loss is reflected in difference
+		"""
+		si = create_sales_invoice(
+			customer="_Test Customer USD",
+			debit_to="_Test Receivable USD - _TC",
+			currency="USD",
+			conversion_rate=50,
+			do_not_save=1,
+		)
+		create_payment_terms_template_with_discount()
+		si.payment_terms_template = "Test Discount Template"
+
+		frappe.db.set_value("Company", si.company, "default_discount_account", "Write Off - _TC")
+		si.save()
+		si.submit()
+
+		pe = get_payment_entry(
+			"Sales Invoice", si.name, bank_account="_Test Bank - _TC", bank_amount=4700
+		)
+		pe.reference_no = si.name
+		pe.reference_date = nowdate()
+
+		# Early payment discount loss on income
+		self.assertEqual(pe.paid_amount, 90.0)
+		self.assertEqual(pe.received_amount, 4200.0)  # 5000 - 500 (discount) - 300 (exchange loss)
+		self.assertEqual(pe.deductions[0].amount, 500.0)
+		self.assertEqual(pe.deductions[0].account, "Write Off - _TC")
+
+		# Exchange loss
+		self.assertEqual(pe.difference_amount, 300.0)
+
+		pe.append(
+			"deductions",
+			{
+				"account": "_Test Exchange Gain/Loss - _TC",
+				"cost_center": "_Test Cost Center - _TC",
+				"amount": 300.0,
+			},
+		)
+
+		pe.insert()
+		pe.submit()
+
+		self.assertEqual(pe.difference_amount, 0.0)
+
+		expected_gle = dict(
+			(d[0], d)
+			for d in [
+				["_Test Receivable USD - _TC", 0, 5000, si.name],
+				["_Test Bank - _TC", 4200, 0, None],
+				["Write Off - _TC", 500.0, 0, None],
+				["_Test Exchange Gain/Loss - _TC", 300.0, 0, None],
+			]
+		)
+
+		self.validate_gl_entries(pe.name, expected_gle)
+
+		outstanding_amount = flt(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"))
+		self.assertEqual(outstanding_amount, 0)
+
 	def test_payment_against_purchase_invoice_to_check_status(self):
 		pi = make_purchase_invoice(
 			supplier="_Test Supplier USD",
@@ -839,24 +1038,27 @@
 		).insert()
 
 
-def create_payment_terms_template_with_discount():
+def create_payment_terms_template_with_discount(
+	name=None, discount_type=None, discount=None, template_name=None
+):
+	create_payment_term(name or "30 Credit Days with 10% Discount")
+	template_name = template_name or "Test Discount Template"
 
-	create_payment_term("30 Credit Days with 10% Discount")
-
-	if not frappe.db.exists("Payment Terms Template", "Test Discount Template"):
-		payment_term_template = frappe.get_doc(
+	if not frappe.db.exists("Payment Terms Template", template_name):
+		frappe.get_doc(
 			{
 				"doctype": "Payment Terms Template",
-				"template_name": "Test Discount Template",
+				"template_name": template_name,
 				"allocate_payment_based_on_payment_terms": 1,
 				"terms": [
 					{
 						"doctype": "Payment Terms Template Detail",
-						"payment_term": "30 Credit Days with 10% Discount",
+						"payment_term": name or "30 Credit Days with 10% Discount",
 						"invoice_portion": 100,
 						"credit_days_based_on": "Day(s) after invoice date",
 						"credit_days": 2,
-						"discount": 10,
+						"discount_type": discount_type or "Percentage",
+						"discount": discount or 10,
 						"discount_validity_based_on": "Day(s) after invoice date",
 						"discount_validity": 1,
 					}
diff --git a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
index 61a1462..1c31829 100644
--- a/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
+++ b/erpnext/accounts/doctype/payment_entry_deduction/payment_entry_deduction.json
@@ -3,6 +3,7 @@
  "creation": "2016-06-15 15:56:30.815503",
  "doctype": "DocType",
  "editable_grid": 1,
+ "engine": "InnoDB",
  "field_order": [
   "account",
   "cost_center",
@@ -17,9 +18,7 @@
    "in_list_view": 1,
    "label": "Account",
    "options": "Account",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "cost_center",
@@ -28,37 +27,30 @@
    "label": "Cost Center",
    "options": "Cost Center",
    "print_hide": 1,
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "reqd": 1
   },
   {
    "fieldname": "amount",
    "fieldtype": "Currency",
    "in_list_view": 1,
-   "label": "Amount",
-   "reqd": 1,
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Amount (Company Currency)",
+   "options": "Company:company:default_currency",
+   "reqd": 1
   },
   {
    "fieldname": "column_break_2",
-   "fieldtype": "Column Break",
-   "show_days": 1,
-   "show_seconds": 1
+   "fieldtype": "Column Break"
   },
   {
    "fieldname": "description",
    "fieldtype": "Small Text",
-   "label": "Description",
-   "show_days": 1,
-   "show_seconds": 1
+   "label": "Description"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2020-09-12 20:38:08.110674",
+ "modified": "2023-03-06 07:11:57.739619",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Entry Deduction",
@@ -66,5 +58,6 @@
  "permissions": [],
  "quick_entry": 1,
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index d986f32..caffac5 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -272,4 +272,32 @@
 	}
 };
 
+frappe.ui.form.on('Payment Reconciliation Allocation', {
+	allocated_amount: function(frm, cdt, cdn) {
+		let row = locals[cdt][cdn];
+		// filter invoice
+		let invoice = frm.doc.invoices.filter((x) => (x.invoice_number == row.invoice_number));
+		// filter payment
+		let payment = frm.doc.payments.filter((x) => (x.reference_name == row.reference_name));
+
+		frm.call({
+			doc: frm.doc,
+			method: 'calculate_difference_on_allocation_change',
+			args: {
+				payment_entry: payment,
+				invoice: invoice,
+				allocated_amount: row.allocated_amount
+			},
+			callback: (r) => {
+				if (r.message) {
+					row.difference_amount = r.message;
+					frm.refresh();
+				}
+			}
+		});
+	}
+});
+
+
+
 extend_cscript(cur_frm.cscript, new erpnext.accounts.PaymentReconciliationController({frm: cur_frm}));
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index e3d9c26..d8082d0 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -221,16 +221,28 @@
 
 	def get_difference_amount(self, payment_entry, invoice, allocated_amount):
 		difference_amount = 0
-		if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
-			"exchange_rate", 1
-		):
-			allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount
-			allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount
-			difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
+		if frappe.get_cached_value(
+			"Account", self.receivable_payable_account, "account_currency"
+		) != frappe.get_cached_value("Company", self.company, "default_currency"):
+			if invoice.get("exchange_rate") and payment_entry.get("exchange_rate", 1) != invoice.get(
+				"exchange_rate", 1
+			):
+				allocated_amount_in_ref_rate = payment_entry.get("exchange_rate", 1) * allocated_amount
+				allocated_amount_in_inv_rate = invoice.get("exchange_rate", 1) * allocated_amount
+				difference_amount = allocated_amount_in_ref_rate - allocated_amount_in_inv_rate
 
 		return difference_amount
 
 	@frappe.whitelist()
+	def calculate_difference_on_allocation_change(self, payment_entry, invoice, allocated_amount):
+		invoice_exchange_map = self.get_invoice_exchange_map(invoice, payment_entry)
+		invoice[0]["exchange_rate"] = invoice_exchange_map.get(invoice[0].get("invoice_number"))
+		new_difference_amount = self.get_difference_amount(
+			payment_entry[0], invoice[0], allocated_amount
+		)
+		return new_difference_amount
+
+	@frappe.whitelist()
 	def allocate_entries(self, args):
 		self.validate_entries()
 
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index f9dda05..3be11ae 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -5,7 +5,7 @@
 
 import frappe
 from frappe import qb
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import add_days, flt, nowdate
 
 from erpnext import get_default_cost_center
@@ -349,6 +349,11 @@
 		invoices = [x.as_dict() for x in pr.get("invoices")]
 		payments = [x.as_dict() for x in pr.get("payments")]
 		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+		# Difference amount should not be calculated for base currency accounts
+		for row in pr.allocation:
+			self.assertEqual(flt(row.get("difference_amount")), 0.0)
+
 		pr.reconcile()
 
 		si.reload()
@@ -390,6 +395,11 @@
 		invoices = [x.as_dict() for x in pr.get("invoices")]
 		payments = [x.as_dict() for x in pr.get("payments")]
 		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+		# Difference amount should not be calculated for base currency accounts
+		for row in pr.allocation:
+			self.assertEqual(flt(row.get("difference_amount")), 0.0)
+
 		pr.reconcile()
 
 		# check PR tool output
@@ -414,6 +424,11 @@
 		invoices = [x.as_dict() for x in pr.get("invoices")]
 		payments = [x.as_dict() for x in pr.get("payments")]
 		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+		# Difference amount should not be calculated for base currency accounts
+		for row in pr.allocation:
+			self.assertEqual(flt(row.get("difference_amount")), 0.0)
+
 		pr.reconcile()
 
 		# assert outstanding
@@ -450,6 +465,11 @@
 		invoices = [x.as_dict() for x in pr.get("invoices")]
 		payments = [x.as_dict() for x in pr.get("payments")]
 		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+		# Difference amount should not be calculated for base currency accounts
+		for row in pr.allocation:
+			self.assertEqual(flt(row.get("difference_amount")), 0.0)
+
 		pr.reconcile()
 
 		self.assertEqual(pr.get("invoices"), [])
@@ -824,6 +844,52 @@
 		payment_vouchers = [x.get("reference_name") for x in pr.get("payments")]
 		self.assertCountEqual(payment_vouchers, [je2.name, pe2.name])
 
+	@change_settings(
+		"Accounts Settings",
+		{
+			"allow_multi_currency_invoices_against_single_party_account": 1,
+		},
+	)
+	def test_no_difference_amount_for_base_currency_accounts(self):
+		# Make Sale Invoice
+		si = self.create_sales_invoice(
+			qty=1, rate=1, posting_date=nowdate(), do_not_save=True, do_not_submit=True
+		)
+		si.customer = self.customer
+		si.currency = "EUR"
+		si.conversion_rate = 85
+		si.debit_to = self.debit_to
+		si.save().submit()
+
+		# Make payment using Payment Entry
+		pe1 = create_payment_entry(
+			company=self.company,
+			payment_type="Receive",
+			party_type="Customer",
+			party=self.customer,
+			paid_from=self.debit_to,
+			paid_to=self.bank,
+			paid_amount=100,
+		)
+
+		pe1.save()
+		pe1.submit()
+
+		pr = self.create_payment_reconciliation()
+		pr.party = self.customer
+		pr.receivable_payable_account = self.debit_to
+		pr.get_unreconciled_entries()
+
+		self.assertEqual(len(pr.invoices), 1)
+		self.assertEqual(len(pr.payments), 1)
+
+		invoices = [x.as_dict() for x in pr.invoices]
+		payments = [pr.payments[0].as_dict()]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+
+		self.assertEqual(pr.allocation[0].allocated_amount, 85)
+		self.assertEqual(pr.allocation[0].difference_amount, 0)
+
 
 def make_customer(customer_name, currency=None):
 	if not frappe.db.exists("Customer", customer_name):
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.py b/erpnext/accounts/doctype/payment_request/payment_request.py
index 2f43914..11d6d5f 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/payment_request.py
@@ -495,26 +495,28 @@
 	"""get amount based on doctype"""
 	dt = ref_doc.doctype
 	if dt in ["Sales Order", "Purchase Order"]:
-		grand_total = flt(ref_doc.rounded_total) - flt(ref_doc.advance_paid)
-
+		grand_total = flt(ref_doc.rounded_total) or flt(ref_doc.grand_total)
 	elif dt in ["Sales Invoice", "Purchase Invoice"]:
-		if ref_doc.party_account_currency == ref_doc.currency:
-			grand_total = flt(ref_doc.outstanding_amount)
-		else:
-			grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
-
+		if not ref_doc.get("is_pos"):
+			if ref_doc.party_account_currency == ref_doc.currency:
+				grand_total = flt(ref_doc.outstanding_amount)
+			else:
+				grand_total = flt(ref_doc.outstanding_amount) / ref_doc.conversion_rate
+		elif dt == "Sales Invoice":
+			for pay in ref_doc.payments:
+				if pay.type == "Phone" and pay.account == payment_account:
+					grand_total = pay.amount
+					break
 	elif dt == "POS Invoice":
 		for pay in ref_doc.payments:
 			if pay.type == "Phone" and pay.account == payment_account:
 				grand_total = pay.amount
 				break
-
 	elif dt == "Fees":
 		grand_total = ref_doc.outstanding_amount
 
 	if grand_total > 0:
 		return grand_total
-
 	else:
 		frappe.throw(_("Payment Entry is already created"))
 
diff --git a/erpnext/accounts/doctype/payment_request/test_payment_request.py b/erpnext/accounts/doctype/payment_request/test_payment_request.py
index 477c726..e17a846 100644
--- a/erpnext/accounts/doctype/payment_request/test_payment_request.py
+++ b/erpnext/accounts/doctype/payment_request/test_payment_request.py
@@ -6,6 +6,7 @@
 import frappe
 
 from erpnext.accounts.doctype.payment_request.payment_request import make_payment_request
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
 from erpnext.setup.utils import get_exchange_rate
@@ -45,7 +46,10 @@
 				frappe.get_doc(method).insert(ignore_permissions=True)
 
 	def test_payment_request_linkings(self):
-		so_inr = make_sales_order(currency="INR")
+		so_inr = make_sales_order(currency="INR", do_not_save=True)
+		so_inr.disable_rounded_total = 1
+		so_inr.save()
+
 		pr = make_payment_request(
 			dt="Sales Order",
 			dn=so_inr.name,
@@ -71,6 +75,29 @@
 		self.assertEqual(pr.reference_name, si_usd.name)
 		self.assertEqual(pr.currency, "USD")
 
+	def test_payment_entry_against_purchase_invoice(self):
+		si_usd = make_purchase_invoice(
+			customer="_Test Supplier USD",
+			debit_to="_Test Payable USD - _TC",
+			currency="USD",
+			conversion_rate=50,
+		)
+
+		pr = make_payment_request(
+			dt="Purchase Invoice",
+			dn=si_usd.name,
+			recipient_id="user@example.com",
+			mute_email=1,
+			payment_gateway_account="_Test Gateway - USD",
+			submit_doc=1,
+			return_doc=1,
+		)
+
+		pe = pr.create_payment_entry()
+		pr.load_from_db()
+
+		self.assertEqual(pr.status, "Paid")
+
 	def test_payment_entry(self):
 		frappe.db.set_value(
 			"Company", "_Test Company", "exchange_gain_loss_account", "_Test Exchange Gain/Loss - _TC"
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index ca98bee..9d636ad 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -4,12 +4,13 @@
 
 import frappe
 from frappe import _
-from frappe.utils import flt
+from frappe.query_builder.functions import Sum
+from frappe.utils import add_days, flt
 
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_accounting_dimensions,
 )
-from erpnext.accounts.utils import get_account_currency
+from erpnext.accounts.utils import get_account_currency, get_fiscal_year, validate_fiscal_year
 from erpnext.controllers.accounts_controller import AccountsController
 
 
@@ -20,9 +21,17 @@
 
 	def on_submit(self):
 		self.db_set("gle_processing_status", "In Progress")
-		self.make_gl_entries()
+		get_opening_entries = False
+
+		if not frappe.db.exists(
+			"Period Closing Voucher", {"company": self.company, "docstatus": 1, "name": ("!=", self.name)}
+		):
+			get_opening_entries = True
+
+		self.make_gl_entries(get_opening_entries=get_opening_entries)
 
 	def on_cancel(self):
+		self.validate_future_closing_vouchers()
 		self.db_set("gle_processing_status", "In Progress")
 		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
 		gle_count = frappe.db.count(
@@ -42,6 +51,25 @@
 		else:
 			make_reverse_gl_entries(voucher_type="Period Closing Voucher", voucher_no=self.name)
 
+		self.delete_closing_entries()
+
+	def validate_future_closing_vouchers(self):
+		if frappe.db.exists(
+			"Period Closing Voucher",
+			{"posting_date": (">", self.posting_date), "docstatus": 1, "company": self.company},
+		):
+			frappe.throw(
+				_(
+					"You can not cancel this Period Closing Voucher, please cancel the future Period Closing Vouchers first"
+				)
+			)
+
+	def delete_closing_entries(self):
+		closing_balance = frappe.qb.DocType("Account Closing Balance")
+		frappe.qb.from_(closing_balance).delete().where(
+			closing_balance.period_closing_voucher == self.name
+		).run()
+
 	def validate_account_head(self):
 		closing_account_type = frappe.get_cached_value("Account", self.closing_account_head, "root_type")
 
@@ -56,8 +84,6 @@
 			frappe.throw(_("Currency of the Closing Account must be {0}").format(company_currency))
 
 	def validate_posting_date(self):
-		from erpnext.accounts.utils import get_fiscal_year, validate_fiscal_year
-
 		validate_fiscal_year(
 			self.posting_date, self.fiscal_year, self.company, label=_("Posting Date"), doc=self
 		)
@@ -66,6 +92,8 @@
 			self.posting_date, self.fiscal_year, company=self.company
 		)[1]
 
+		self.check_if_previous_year_closed()
+
 		pce = frappe.db.sql(
 			"""select name from `tabPeriod Closing Voucher`
 			where posting_date > %s and fiscal_year = %s and docstatus = 1 and company = %s""",
@@ -78,28 +106,64 @@
 				)
 			)
 
-	def make_gl_entries(self):
+	def check_if_previous_year_closed(self):
+		last_year_closing = add_days(self.year_start_date, -1)
+
+		previous_fiscal_year = get_fiscal_year(last_year_closing, company=self.company, boolean=True)
+
+		if previous_fiscal_year and not frappe.db.exists(
+			"GL Entry", {"posting_date": ("<=", last_year_closing), "company": self.company}
+		):
+			return
+
+		if previous_fiscal_year and not frappe.db.exists(
+			"Period Closing Voucher",
+			{"posting_date": ("<=", last_year_closing), "docstatus": 1, "company": self.company},
+		):
+			frappe.throw(_("Previous Year is not closed, please close it first"))
+
+	def make_gl_entries(self, get_opening_entries=False):
 		gl_entries = self.get_gl_entries()
+		closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
 		if gl_entries:
 			if len(gl_entries) > 5000:
-				frappe.enqueue(process_gl_entries, gl_entries=gl_entries, queue="long")
+				frappe.enqueue(
+					process_gl_entries,
+					gl_entries=gl_entries,
+					closing_entries=closing_entries,
+					voucher_name=self.name,
+					queue="long",
+				)
 				frappe.msgprint(
 					_("The GL Entries will be processed in the background, it can take a few minutes."),
 					alert=True,
 				)
 			else:
-				process_gl_entries(gl_entries)
+				process_gl_entries(gl_entries, closing_entries, voucher_name=self.name)
+
+	def get_grouped_gl_entries(self, get_opening_entries=False):
+		closing_entries = []
+		for acc in self.get_balances_based_on_dimensions(
+			group_by_account=True, for_aggregation=True, get_opening_entries=get_opening_entries
+		):
+			closing_entries.append(self.get_closing_entries(acc))
+
+		return closing_entries
 
 	def get_gl_entries(self):
 		gl_entries = []
 
 		# pl account
-		for acc in self.get_pl_balances_based_on_dimensions(group_by_account=True):
+		for acc in self.get_balances_based_on_dimensions(
+			group_by_account=True, report_type="Profit and Loss"
+		):
 			if flt(acc.bal_in_company_currency):
 				gl_entries.append(self.get_gle_for_pl_account(acc))
 
 		# closing liability account
-		for acc in self.get_pl_balances_based_on_dimensions(group_by_account=False):
+		for acc in self.get_balances_based_on_dimensions(
+			group_by_account=False, report_type="Profit and Loss"
+		):
 			if flt(acc.bal_in_company_currency):
 				gl_entries.append(self.get_gle_for_closing_account(acc))
 
@@ -108,6 +172,8 @@
 	def get_gle_for_pl_account(self, acc):
 		gl_entry = self.get_gl_dict(
 			{
+				"company": self.company,
+				"closing_date": self.posting_date,
 				"account": acc.account,
 				"cost_center": acc.cost_center,
 				"finance_book": acc.finance_book,
@@ -120,6 +186,7 @@
 				if flt(acc.bal_in_account_currency) > 0
 				else 0,
 				"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) > 0 else 0,
+				"is_period_closing_voucher_entry": 1,
 			},
 			item=acc,
 		)
@@ -129,6 +196,8 @@
 	def get_gle_for_closing_account(self, acc):
 		gl_entry = self.get_gl_dict(
 			{
+				"company": self.company,
+				"closing_date": self.posting_date,
 				"account": self.closing_account_head,
 				"cost_center": acc.cost_center,
 				"finance_book": acc.finance_book,
@@ -141,12 +210,36 @@
 				if flt(acc.bal_in_account_currency) < 0
 				else 0,
 				"credit": abs(flt(acc.bal_in_company_currency)) if flt(acc.bal_in_company_currency) < 0 else 0,
+				"is_period_closing_voucher_entry": 1,
 			},
 			item=acc,
 		)
 		self.update_default_dimensions(gl_entry, acc)
 		return gl_entry
 
+	def get_closing_entries(self, acc):
+		closing_entry = self.get_gl_dict(
+			{
+				"company": self.company,
+				"closing_date": self.posting_date,
+				"period_closing_voucher": self.name,
+				"account": acc.account,
+				"cost_center": acc.cost_center,
+				"finance_book": acc.finance_book,
+				"account_currency": acc.account_currency,
+				"debit_in_account_currency": flt(acc.debit_in_account_currency),
+				"debit": flt(acc.debit),
+				"credit_in_account_currency": flt(acc.credit_in_account_currency),
+				"credit": flt(acc.credit),
+			},
+			item=acc,
+		)
+
+		for dimension in self.accounting_dimensions:
+			closing_entry.update({dimension: acc.get(dimension)})
+
+		return closing_entry
+
 	def update_default_dimensions(self, gl_entry, acc):
 		if not self.accounting_dimensions:
 			self.accounting_dimensions = get_accounting_dimensions()
@@ -154,47 +247,88 @@
 		for dimension in self.accounting_dimensions:
 			gl_entry.update({dimension: acc.get(dimension)})
 
-	def get_pl_balances_based_on_dimensions(self, group_by_account=False):
+	def get_balances_based_on_dimensions(
+		self, group_by_account=False, report_type=None, for_aggregation=False, get_opening_entries=False
+	):
 		"""Get balance for dimension-wise pl accounts"""
 
-		dimension_fields = ["t1.cost_center", "t1.finance_book"]
+		qb_dimension_fields = ["cost_center", "finance_book", "project"]
 
 		self.accounting_dimensions = get_accounting_dimensions()
 		for dimension in self.accounting_dimensions:
-			dimension_fields.append("t1.{0}".format(dimension))
+			qb_dimension_fields.append(dimension)
 
 		if group_by_account:
-			dimension_fields.append("t1.account")
+			qb_dimension_fields.append("account")
 
-		return frappe.db.sql(
-			"""
-			select
-				t2.account_currency,
-				{dimension_fields},
-				sum(t1.debit_in_account_currency) - sum(t1.credit_in_account_currency) as bal_in_account_currency,
-				sum(t1.debit) - sum(t1.credit) as bal_in_company_currency
-			from `tabGL Entry` t1, `tabAccount` t2
-			where
-				t1.is_cancelled = 0
-				and t1.account = t2.name
-				and t2.report_type = 'Profit and Loss'
-				and t2.docstatus < 2
-				and t2.company = %s
-				and t1.posting_date between %s and %s
-			group by {dimension_fields}
-		""".format(
-				dimension_fields=", ".join(dimension_fields)
-			),
-			(self.company, self.get("year_start_date"), self.posting_date),
-			as_dict=1,
+		account_filters = {
+			"company": self.company,
+			"is_group": 0,
+		}
+
+		if report_type:
+			account_filters.update({"report_type": report_type})
+
+		accounts = frappe.get_all("Account", filters=account_filters, pluck="name")
+
+		gl_entry = frappe.qb.DocType("GL Entry")
+		query = frappe.qb.from_(gl_entry).select(gl_entry.account, gl_entry.account_currency)
+
+		if not for_aggregation:
+			query = query.select(
+				(Sum(gl_entry.debit_in_account_currency) - Sum(gl_entry.credit_in_account_currency)).as_(
+					"bal_in_account_currency"
+				),
+				(Sum(gl_entry.debit) - Sum(gl_entry.credit)).as_("bal_in_company_currency"),
+			)
+		else:
+			query = query.select(
+				(Sum(gl_entry.debit_in_account_currency)).as_("debit_in_account_currency"),
+				(Sum(gl_entry.credit_in_account_currency)).as_("credit_in_account_currency"),
+				(Sum(gl_entry.debit)).as_("debit"),
+				(Sum(gl_entry.credit)).as_("credit"),
+			)
+
+		for dimension in qb_dimension_fields:
+			query = query.select(gl_entry[dimension])
+
+		query = query.where(
+			(gl_entry.company == self.company)
+			& (gl_entry.is_cancelled == 0)
+			& (gl_entry.account.isin(accounts))
 		)
 
+		if get_opening_entries:
+			query = query.where(
+				gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
+				| gl_entry.is_opening
+				== "Yes"
+			)
+		else:
+			query = query.where(
+				gl_entry.posting_date.between(self.get("year_start_date"), self.posting_date)
+				& gl_entry.is_opening
+				== "No"
+			)
 
-def process_gl_entries(gl_entries):
+		if for_aggregation:
+			query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
+
+		for dimension in qb_dimension_fields:
+			query = query.groupby(gl_entry[dimension])
+
+		return query.run(as_dict=1)
+
+
+def process_gl_entries(gl_entries, closing_entries, voucher_name=None):
+	from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
+		make_closing_entries,
+	)
 	from erpnext.accounts.general_ledger import make_gl_entries
 
 	try:
 		make_gl_entries(gl_entries, merge_entries=False)
+		make_closing_entries(gl_entries + closing_entries, voucher_name=voucher_name)
 		frappe.db.set_value(
 			"Period Closing Voucher", gl_entries[0].get("voucher_no"), "gle_processing_status", "Completed"
 		)
diff --git a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
index e9ed2e4..62ae857 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/test_period_closing_voucher.py
@@ -16,16 +16,17 @@
 class TestPeriodClosingVoucher(unittest.TestCase):
 	def test_closing_entry(self):
 		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
+		frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
 
 		company = create_company()
 		cost_center = create_cost_center("Test Cost Center 1")
 
 		jv1 = make_journal_entry(
+			posting_date="2021-03-15",
 			amount=400,
 			account1="Cash - TPC",
 			account2="Sales - TPC",
 			cost_center=cost_center,
-			posting_date=now(),
 			save=False,
 		)
 		jv1.company = company
@@ -33,18 +34,18 @@
 		jv1.submit()
 
 		jv2 = make_journal_entry(
+			posting_date="2021-03-15",
 			amount=600,
 			account1="Cost of Goods Sold - TPC",
 			account2="Cash - TPC",
 			cost_center=cost_center,
-			posting_date=now(),
 			save=False,
 		)
 		jv2.company = company
 		jv2.save()
 		jv2.submit()
 
-		pcv = self.make_period_closing_voucher()
+		pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
 		surplus_account = pcv.closing_account_head
 
 		expected_gle = (
@@ -65,6 +66,7 @@
 
 	def test_cost_center_wise_posting(self):
 		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
+		frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
 
 		company = create_company()
 		surplus_account = create_account()
@@ -81,6 +83,7 @@
 			debit_to="Debtors - TPC",
 			currency="USD",
 			customer="_Test Customer USD",
+			posting_date="2021-03-15",
 		)
 		create_sales_invoice(
 			company=company,
@@ -91,9 +94,10 @@
 			debit_to="Debtors - TPC",
 			currency="USD",
 			customer="_Test Customer USD",
+			posting_date="2021-03-15",
 		)
 
-		pcv = self.make_period_closing_voucher(submit=False)
+		pcv = self.make_period_closing_voucher(posting_date="2021-03-31", submit=False)
 		pcv.save()
 		pcv.submit()
 		surplus_account = pcv.closing_account_head
@@ -128,12 +132,13 @@
 
 	def test_period_closing_with_finance_book_entries(self):
 		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
+		frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
 
 		company = create_company()
 		surplus_account = create_account()
 		cost_center = create_cost_center("Test Cost Center 1")
 
-		si = create_sales_invoice(
+		create_sales_invoice(
 			company=company,
 			income_account="Sales - TPC",
 			expense_account="Cost of Goods Sold - TPC",
@@ -142,6 +147,7 @@
 			debit_to="Debtors - TPC",
 			currency="USD",
 			customer="_Test Customer USD",
+			posting_date="2021-03-15",
 		)
 
 		jv = make_journal_entry(
@@ -149,14 +155,14 @@
 			account2="Sales - TPC",
 			amount=400,
 			cost_center=cost_center,
-			posting_date=now(),
+			posting_date="2021-03-15",
 		)
 		jv.company = company
 		jv.finance_book = create_finance_book().name
 		jv.save()
 		jv.submit()
 
-		pcv = self.make_period_closing_voucher()
+		pcv = self.make_period_closing_voucher(posting_date="2021-03-31")
 		surplus_account = pcv.closing_account_head
 
 		expected_gle = (
@@ -177,14 +183,130 @@
 
 		self.assertSequenceEqual(pcv_gle, expected_gle)
 
-	def make_period_closing_voucher(self, submit=True):
+	def test_gl_entries_restrictions(self):
+		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
+		frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
+
+		company = create_company()
+		cost_center = create_cost_center("Test Cost Center 1")
+
+		self.make_period_closing_voucher(posting_date="2021-03-31")
+
+		jv1 = make_journal_entry(
+			posting_date="2021-03-15",
+			amount=400,
+			account1="Cash - TPC",
+			account2="Sales - TPC",
+			cost_center=cost_center,
+			save=False,
+		)
+		jv1.company = company
+		jv1.save()
+
+		self.assertRaises(frappe.ValidationError, jv1.submit)
+
+	def test_closing_balance_with_dimensions(self):
+		frappe.db.sql("delete from `tabGL Entry` where company='Test PCV Company'")
+		frappe.db.sql("delete from `tabPeriod Closing Voucher` where company='Test PCV Company'")
+		frappe.db.sql("delete from `tabAccount Closing Balance` where company='Test PCV Company'")
+
+		company = create_company()
+		cost_center1 = create_cost_center("Test Cost Center 1")
+		cost_center2 = create_cost_center("Test Cost Center 2")
+
+		jv1 = make_journal_entry(
+			posting_date="2021-03-15",
+			amount=400,
+			account1="Cash - TPC",
+			account2="Sales - TPC",
+			cost_center=cost_center1,
+			save=False,
+		)
+		jv1.company = company
+		jv1.save()
+		jv1.submit()
+
+		jv2 = make_journal_entry(
+			posting_date="2021-03-15",
+			amount=200,
+			account1="Cash - TPC",
+			account2="Sales - TPC",
+			cost_center=cost_center2,
+			save=False,
+		)
+		jv2.company = company
+		jv2.save()
+		jv2.submit()
+
+		pcv1 = self.make_period_closing_voucher(posting_date="2021-03-31")
+
+		closing_balance = frappe.db.get_value(
+			"Account Closing Balance",
+			{
+				"account": "Sales - TPC",
+				"cost_center": cost_center1,
+				"period_closing_voucher": pcv1.name,
+				"is_period_closing_voucher_entry": 0,
+			},
+			["credit", "credit_in_account_currency"],
+			as_dict=1,
+		)
+
+		self.assertEqual(closing_balance.credit, 400)
+		self.assertEqual(closing_balance.credit_in_account_currency, 400)
+
+		jv3 = make_journal_entry(
+			posting_date="2022-03-15",
+			amount=300,
+			account1="Cash - TPC",
+			account2="Sales - TPC",
+			cost_center=cost_center2,
+			save=False,
+		)
+
+		jv3.company = company
+		jv3.save()
+		jv3.submit()
+
+		pcv2 = self.make_period_closing_voucher(posting_date="2022-03-31")
+
+		cc1_closing_balance = frappe.db.get_value(
+			"Account Closing Balance",
+			{
+				"account": "Sales - TPC",
+				"cost_center": cost_center1,
+				"period_closing_voucher": pcv2.name,
+				"is_period_closing_voucher_entry": 0,
+			},
+			["credit", "credit_in_account_currency"],
+			as_dict=1,
+		)
+
+		cc2_closing_balance = frappe.db.get_value(
+			"Account Closing Balance",
+			{
+				"account": "Sales - TPC",
+				"cost_center": cost_center2,
+				"period_closing_voucher": pcv2.name,
+				"is_period_closing_voucher_entry": 0,
+			},
+			["credit", "credit_in_account_currency"],
+			as_dict=1,
+		)
+
+		self.assertEqual(cc1_closing_balance.credit, 400)
+		self.assertEqual(cc1_closing_balance.credit_in_account_currency, 400)
+		self.assertEqual(cc2_closing_balance.credit, 500)
+		self.assertEqual(cc2_closing_balance.credit_in_account_currency, 500)
+
+	def make_period_closing_voucher(self, posting_date=None, submit=True):
 		surplus_account = create_account()
 		cost_center = create_cost_center("Test Cost Center 1")
 		pcv = frappe.get_doc(
 			{
 				"doctype": "Period Closing Voucher",
-				"transaction_date": today(),
-				"posting_date": today(),
+				"transaction_date": posting_date or today(),
+				"posting_date": posting_date or today(),
 				"company": "Test PCV Company",
 				"fiscal_year": get_fiscal_year(today(), company="Test PCV Company")[0],
 				"cost_center": cost_center,
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
index 56b8579..cced375 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.js
@@ -112,7 +112,8 @@
 				party_type: "Customer",
 				account: this.frm.doc.debit_to,
 				price_list: this.frm.doc.selling_price_list,
-				pos_profile: pos_profile
+				pos_profile: pos_profile,
+				company_address: this.frm.doc.company_address
 			}, () => {
 				this.apply_pricing_rule();
 			});
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index a1239d6..b40649b 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -161,7 +161,7 @@
 
 		bold_item_name = frappe.bold(item.item_name)
 		bold_extra_batch_qty_needed = frappe.bold(
-			abs(available_batch_qty - reserved_batch_qty - item.qty)
+			abs(available_batch_qty - reserved_batch_qty - item.stock_qty)
 		)
 		bold_invalid_batch_no = frappe.bold(item.batch_no)
 
@@ -172,7 +172,7 @@
 				).format(item.idx, bold_invalid_batch_no, bold_item_name),
 				title=_("Item Unavailable"),
 			)
-		elif (available_batch_qty - reserved_batch_qty - item.qty) < 0:
+		elif (available_batch_qty - reserved_batch_qty - item.stock_qty) < 0:
 			frappe.throw(
 				_(
 					"Row #{}: Batch No. {} of item {} has less than required stock available, {} more required"
@@ -246,7 +246,7 @@
 						),
 						title=_("Item Unavailable"),
 					)
-				elif is_stock_item and flt(available_stock) < flt(d.qty):
+				elif is_stock_item and flt(available_stock) < flt(d.stock_qty):
 					frappe.throw(
 						_(
 							"Row #{}: Stock quantity not enough for Item Code: {} under warehouse {}. Available quantity {}."
@@ -651,7 +651,7 @@
 		item_pos_reserved_qty = get_pos_reserved_qty(item.item_code, warehouse)
 		available_qty = item_bin_qty - item_pos_reserved_qty
 
-		max_available_bundles = available_qty / item.qty
+		max_available_bundles = available_qty / item.stock_qty
 		if bundle_bin_qty > max_available_bundles and frappe.get_value(
 			"Item", item.item_code, "is_stock_item"
 		):
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
index 3920d4c..b9680df 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.html
@@ -15,7 +15,7 @@
 	</div>
 	<h2 class="text-center">{{ _("STATEMENTS OF ACCOUNTS") }}</h2>
 	<div>
-		<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party[0] }}</b></h5>
+		<h5 style="float: left;">{{ _("Customer: ") }} <b>{{filters.party_name[0] }}</b></h5>
 		<h5 style="float: right;">
 			{{ _("Date: ") }}
 			<b>{{ frappe.format(filters.from_date, 'Date')}}
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
index a48c027..a482931 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
+++ b/erpnext/accounts/doctype/process_statement_of_accounts/process_statement_of_accounts.py
@@ -24,7 +24,7 @@
 class ProcessStatementOfAccounts(Document):
 	def validate(self):
 		if not self.subject:
-			self.subject = "Statement Of Accounts for {{ customer.name }}"
+			self.subject = "Statement Of Accounts for {{ customer.customer_name }}"
 		if not self.body:
 			self.body = "Hello {{ customer.name }},<br>PFA your Statement Of Accounts from {{ doc.from_date }} to {{ doc.to_date }}."
 
@@ -87,6 +87,7 @@
 				"account": [doc.account] if doc.account else None,
 				"party_type": "Customer",
 				"party": [entry.customer],
+				"party_name": [entry.customer_name] if entry.customer_name else None,
 				"presentation_currency": presentation_currency,
 				"group_by": doc.group_by,
 				"currency": doc.currency,
@@ -156,7 +157,7 @@
 	]
 	return frappe.get_list(
 		"Customer",
-		fields=["name", "email_id"],
+		fields=["name", "customer_name", "email_id"],
 		filters=[[fields_dict[customer_collection], "IN", selected]],
 	)
 
@@ -179,7 +180,7 @@
 	if sales_person_records.get("Customer"):
 		return frappe.get_list(
 			"Customer",
-			fields=["name", "email_id"],
+			fields=["name", "customer_name", "email_id"],
 			filters=[["name", "in", list(sales_person_records["Customer"])]],
 		)
 	else:
@@ -228,7 +229,7 @@
 		if customer_collection == "Sales Partner":
 			customers = frappe.get_list(
 				"Customer",
-				fields=["name", "email_id"],
+				fields=["name", "customer_name", "email_id"],
 				filters=[["default_sales_partner", "=", collection_name]],
 			)
 		else:
@@ -245,7 +246,12 @@
 				continue
 
 		customer_list.append(
-			{"name": customer.name, "primary_email": primary_email, "billing_email": billing_email}
+			{
+				"name": customer.name,
+				"customer_name": customer.customer_name,
+				"primary_email": primary_email,
+				"billing_email": billing_email,
+			}
 		)
 	return customer_list
 
diff --git a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
index dd04dc1..8bffd6a 100644
--- a/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
+++ b/erpnext/accounts/doctype/process_statement_of_accounts_customer/process_statement_of_accounts_customer.json
@@ -1,12 +1,12 @@
 {
  "actions": [],
- "allow_workflow": 1,
  "creation": "2020-08-03 16:35:21.852178",
  "doctype": "DocType",
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
   "customer",
+  "customer_name",
   "billing_email",
   "primary_email"
  ],
@@ -30,11 +30,18 @@
    "fieldtype": "Read Only",
    "in_list_view": 1,
    "label": "Billing Email"
+  },
+  {
+   "fetch_from": "customer.customer_name",
+   "fieldname": "customer_name",
+   "fieldtype": "Data",
+   "label": "Customer Name",
+   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2020-08-03 22:55:38.875601",
+ "modified": "2023-03-13 00:12:34.508086",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Process Statement Of Accounts Customer",
@@ -43,5 +50,6 @@
  "quick_entry": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index e2b4a1a..5c9168b 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -82,7 +82,11 @@
 
 		if(doc.docstatus == 1 && doc.outstanding_amount != 0
 			&& !(doc.is_return && doc.return_against) && !doc.on_hold) {
-			this.frm.add_custom_button(__('Payment'), this.make_payment_entry, __('Create'));
+			this.frm.add_custom_button(
+				__('Payment'),
+				() => this.make_payment_entry(),
+				__('Create')
+			);
 			cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index 54caf6f..b4d369e 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -118,6 +118,7 @@
   "paid_amount",
   "advances_section",
   "allocate_advances_automatically",
+  "only_include_allocated_payments",
   "get_advances",
   "advances",
   "advance_tax",
@@ -1550,17 +1551,24 @@
    "fieldname": "named_place",
    "fieldtype": "Data",
    "label": "Named Place"
+  },
+  {
+   "default": "0",
+   "depends_on": "allocate_advances_automatically",
+   "description": "Advance payments allocated against orders will only be fetched",
+   "fieldname": "only_include_allocated_payments",
+   "fieldtype": "Check",
+   "label": "Only Include Allocated Payments"
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-28 19:18:56.586321",
+ "modified": "2023-04-03 22:57:14.074982",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
- "name_case": "Title Case",
  "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 21addab..a617447 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -117,7 +117,7 @@
 		self.validate_expense_account()
 		self.set_against_expense_account()
 		self.validate_write_off_account()
-		self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount", "items")
+		self.validate_multiple_billing("Purchase Receipt", "pr_detail", "amount")
 		self.create_remarks()
 		self.set_status()
 		self.validate_purchase_receipt_if_update_stock()
@@ -232,7 +232,7 @@
 		)
 
 		if (
-			cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate"))
+			cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate"))
 			and not self.is_return
 			and not self.is_internal_supplier
 		):
@@ -581,6 +581,7 @@
 
 		self.make_supplier_gl_entry(gl_entries)
 		self.make_item_gl_entries(gl_entries)
+		self.make_precision_loss_gl_entry(gl_entries)
 
 		if self.check_asset_cwip_enabled():
 			self.get_asset_gl_entry(gl_entries)
@@ -975,6 +976,28 @@
 							item.item_tax_amount, item.precision("item_tax_amount")
 						)
 
+	def make_precision_loss_gl_entry(self, gl_entries):
+		round_off_account, round_off_cost_center = get_round_off_account_and_cost_center(
+			self.company, "Purchase Invoice", self.name
+		)
+
+		precision_loss = self.get("base_net_total") - flt(
+			self.get("net_total") * self.conversion_rate, self.precision("net_total")
+		)
+
+		if precision_loss:
+			gl_entries.append(
+				self.get_gl_dict(
+					{
+						"account": round_off_account,
+						"against": self.supplier,
+						"credit": precision_loss,
+						"cost_center": self.cost_center or round_off_cost_center,
+						"remarks": _("Net total calculation precision loss"),
+					}
+				)
+			)
+
 	def get_asset_gl_entry(self, gl_entries):
 		arbnb_account = self.get_company_default("asset_received_but_not_billed")
 		eiiav_account = self.get_company_default("expenses_included_in_asset_valuation")
@@ -1485,11 +1508,17 @@
 		if po_details:
 			updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
 
+		adjust_incoming_rate = frappe.db.get_single_value(
+			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate"
+		)
+
 		for pr in set(updated_pr):
 			from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_billing_percentage
 
 			pr_doc = frappe.get_doc("Purchase Receipt", pr)
-			update_billing_percentage(pr_doc, update_modified=update_modified)
+			update_billing_percentage(
+				pr_doc, update_modified=update_modified, adjust_incoming_rate=adjust_incoming_rate
+			)
 
 	def get_pr_details_billed_amt(self):
 		# Get billed amount based on purchase receipt item reference (pr_detail) in purchase invoice
diff --git a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
index f901257..a6d7df6 100644
--- a/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/test_purchase_invoice.py
@@ -1523,6 +1523,94 @@
 		company.enable_provisional_accounting_for_non_stock_items = 0
 		company.save()
 
+	def test_adjust_incoming_rate(self):
+		frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 0)
+
+		frappe.db.set_single_value(
+			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 1
+		)
+
+		# Increase the cost of the item
+
+		pr = make_purchase_receipt(qty=1, rate=100)
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		for row in pi.items:
+			row.rate = 150
+
+		pi.save()
+		pi.submit()
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 150)
+
+		# Reduce the cost of the item
+
+		pr = make_purchase_receipt(qty=1, rate=100)
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		for row in pi.items:
+			row.rate = 50
+
+		pi.save()
+		pi.submit()
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 50)
+
+		frappe.db.set_single_value(
+			"Buying Settings", "set_landed_cost_based_on_purchase_invoice_rate", 0
+		)
+
+		# Don't adjust incoming rate
+
+		pr = make_purchase_receipt(qty=1, rate=100)
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		pi = create_purchase_invoice_from_receipt(pr.name)
+		for row in pi.items:
+			row.rate = 50
+
+		pi.save()
+		pi.submit()
+
+		stock_value_difference = frappe.db.get_value(
+			"Stock Ledger Entry",
+			{"voucher_type": "Purchase Receipt", "voucher_no": pr.name},
+			"stock_value_difference",
+		)
+		self.assertEqual(stock_value_difference, 100)
+
+		frappe.db.set_single_value("Buying Settings", "maintain_same_rate", 1)
+
 	def test_item_less_defaults(self):
 
 		pi = frappe.new_doc("Purchase Invoice")
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 47e3f9b..56e412b 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -93,9 +93,12 @@
 
 		if (doc.docstatus == 1 && doc.outstanding_amount!=0
 			&& !(cint(doc.is_return) && doc.return_against)) {
-			cur_frm.add_custom_button(__('Payment'),
-				this.make_payment_entry, __('Create'));
-			cur_frm.page.set_inner_btn_group_as_primary(__('Create'));
+			this.frm.add_custom_button(
+				__('Payment'),
+				() => this.make_payment_entry(),
+				__('Create')
+			);
+			this.frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
 		if(doc.docstatus==1 && !doc.is_return) {
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 2f4e45e..a41e13c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -32,9 +32,6 @@
   "cost_center",
   "dimension_col_break",
   "project",
-  "column_break_27",
-  "campaign",
-  "source",
   "currency_and_price_list",
   "currency",
   "conversion_rate",
@@ -123,6 +120,7 @@
   "account_for_change_amount",
   "advances_section",
   "allocate_advances_automatically",
+  "only_include_allocated_payments",
   "get_advances",
   "advances",
   "write_off_section",
@@ -203,7 +201,9 @@
   "more_information",
   "status",
   "inter_company_invoice_reference",
+  "campaign",
   "represents_company",
+  "source",
   "customer_group",
   "col_break23",
   "is_internal_customer",
@@ -2084,10 +2084,6 @@
    "fieldtype": "Column Break"
   },
   {
-   "fieldname": "column_break_27",
-   "fieldtype": "Column Break"
-  },
-  {
    "fieldname": "column_break_52",
    "fieldtype": "Column Break"
   },
@@ -2131,6 +2127,14 @@
    "fieldname": "named_place",
    "fieldtype": "Data",
    "label": "Named Place"
+  },
+  {
+   "default": "0",
+   "depends_on": "allocate_advances_automatically",
+   "description": "Advance payments allocated against orders will only be fetched",
+   "fieldname": "only_include_allocated_payments",
+   "fieldtype": "Check",
+   "label": "Only Include Allocated Payments"
   }
  ],
  "icon": "fa fa-file-text",
@@ -2143,11 +2147,10 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2023-01-28 19:45:47.538163",
+ "modified": "2023-04-03 22:55:14.206473",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
- "name_case": "Title Case",
  "naming_rule": "By \"Naming Series\" field",
  "owner": "Administrator",
  "permissions": [
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 5cda276..db61995 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -145,7 +145,7 @@
 
 		self.set_against_income_account()
 		self.validate_time_sheets_are_submitted()
-		self.validate_multiple_billing("Delivery Note", "dn_detail", "amount", "items")
+		self.validate_multiple_billing("Delivery Note", "dn_detail", "amount")
 		if not self.is_return:
 			self.validate_serial_numbers()
 		else:
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 0ffd946..6051c99 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -266,16 +266,16 @@
 			"_Test Account Education Cess - _TC": [3, 1618, 0.06, 32.36],
 			"_Test Account S&H Education Cess - _TC": [1.5, 1619.5, 0.03, 32.39],
 			"_Test Account CST - _TC": [32.5, 1652, 0.65, 33.04],
-			"_Test Account VAT - _TC": [156.5, 1808.5, 3.13, 36.17],
-			"_Test Account Discount - _TC": [-181.0, 1627.5, -3.62, 32.55],
+			"_Test Account VAT - _TC": [156.0, 1808.0, 3.12, 36.16],
+			"_Test Account Discount - _TC": [-181.0, 1627.0, -3.62, 32.54],
 		}
 
 		for d in si.get("taxes"):
 			for i, k in enumerate(expected_values["keys"]):
 				self.assertEqual(d.get(k), expected_values[d.account_head][i])
 
-		self.assertEqual(si.base_grand_total, 1627.5)
-		self.assertEqual(si.grand_total, 32.55)
+		self.assertEqual(si.base_grand_total, 1627.0)
+		self.assertEqual(si.grand_total, 32.54)
 
 	def test_sales_invoice_with_discount_and_inclusive_tax(self):
 		si = create_sales_invoice(qty=100, rate=50, do_not_save=True)
@@ -401,10 +401,10 @@
 			"_Test Account S&H Education Cess - _TC": [1.4, 1.30, 1297.67],
 			"_Test Account CST - _TC": [27.88, 25.95, 1323.62],
 			"_Test Account VAT - _TC": [156.25, 145.43, 1469.05],
-			"_Test Account Customs Duty - _TC": [125, 116.35, 1585.40],
-			"_Test Account Shipping Charges - _TC": [100, 100, 1685.40],
-			"_Test Account Discount - _TC": [-180.33, -168.54, 1516.86],
-			"_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.01],
+			"_Test Account Customs Duty - _TC": [125, 116.34, 1585.39],
+			"_Test Account Shipping Charges - _TC": [100, 100, 1685.39],
+			"_Test Account Discount - _TC": [-180.33, -168.54, 1516.85],
+			"_Test Account Service Tax - _TC": [-18.03, -16.85, 1500.00],
 		}
 
 		for d in si.get("taxes"):
@@ -413,7 +413,7 @@
 
 		self.assertEqual(si.base_grand_total, 1500)
 		self.assertEqual(si.grand_total, 1500)
-		self.assertEqual(si.rounding_adjustment, -0.01)
+		self.assertEqual(si.rounding_adjustment, 0.0)
 
 	def test_discount_amount_gl_entry(self):
 		frappe.db.set_value("Company", "_Test Company", "round_off_account", "Round Off - _TC")
@@ -454,7 +454,7 @@
 				[test_records[3]["taxes"][2]["account_head"], 0.0, 1.30],
 				[test_records[3]["taxes"][3]["account_head"], 0.0, 25.95],
 				[test_records[3]["taxes"][4]["account_head"], 0.0, 145.43],
-				[test_records[3]["taxes"][5]["account_head"], 0.0, 116.35],
+				[test_records[3]["taxes"][5]["account_head"], 0.0, 116.34],
 				[test_records[3]["taxes"][6]["account_head"], 0.0, 100],
 				[test_records[3]["taxes"][7]["account_head"], 168.54, 0.0],
 				["_Test Account Service Tax - _TC", 16.85, 0.0],
@@ -1614,7 +1614,7 @@
 			"_Test Account Education Cess - _TC": [1.4, 1.4, 1.4],
 			"_Test Account S&H Education Cess - _TC": [0.7, 0.7, 0.7],
 			"_Test Account CST - _TC": [17.19, 17.19, 17.19],
-			"_Test Account VAT - _TC": [78.13, 78.13, 78.13],
+			"_Test Account VAT - _TC": [78.12, 78.12, 78.12],
 			"_Test Account Discount - _TC": [-95.49, -95.49, -95.49],
 		}
 
@@ -1623,9 +1623,9 @@
 				if expected_values.get(d.account_head):
 					self.assertEqual(d.get(k), expected_values[d.account_head][i])
 
-		self.assertEqual(si.total_taxes_and_charges, 234.43)
-		self.assertEqual(si.base_grand_total, 859.43)
-		self.assertEqual(si.grand_total, 859.43)
+		self.assertEqual(si.total_taxes_and_charges, 234.42)
+		self.assertEqual(si.base_grand_total, 859.42)
+		self.assertEqual(si.grand_total, 859.42)
 
 	def test_multi_currency_gle(self):
 		si = create_sales_invoice(
@@ -1985,17 +1985,17 @@
 			)
 		si.save()
 		si.submit()
-		self.assertEqual(si.net_total, 19453.13)
+		self.assertEqual(si.net_total, 19453.12)
 		self.assertEqual(si.grand_total, 24900)
 		self.assertEqual(si.total_taxes_and_charges, 5446.88)
-		self.assertEqual(si.rounding_adjustment, -0.01)
+		self.assertEqual(si.rounding_adjustment, 0.0)
 
 		expected_values = dict(
 			(d[0], d)
 			for d in [
 				[si.debit_to, 24900, 0.0],
 				["_Test Account Service Tax - _TC", 0.0, 5446.88],
-				["Sales - _TC", 0.0, 19453.13],
+				["Sales - _TC", 0.0, 19453.12],
 				["Round Off - _TC", 0.01, 0.0],
 			]
 		)
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 41fdb6a..6b2546e 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -300,6 +300,9 @@
 
 	if gl_map:
 		check_freezing_date(gl_map[0]["posting_date"], adv_adj)
+		is_opening = any(d.get("is_opening") == "Yes" for d in gl_map)
+		if gl_map[0]["voucher_type"] != "Period Closing Voucher":
+			validate_against_pcv(is_opening, gl_map[0]["posting_date"], gl_map[0]["company"])
 
 	for entry in gl_map:
 		make_entry(entry, adv_adj, update_outstanding, from_repost)
@@ -519,6 +522,9 @@
 		)
 		validate_accounting_period(gl_entries)
 		check_freezing_date(gl_entries[0]["posting_date"], adv_adj)
+
+		is_opening = any(d.get("is_opening") == "Yes" for d in gl_entries)
+		validate_against_pcv(is_opening, gl_entries[0]["posting_date"], gl_entries[0]["company"])
 		set_as_cancel(gl_entries[0]["voucher_type"], gl_entries[0]["voucher_no"])
 
 		for entry in gl_entries:
@@ -566,6 +572,28 @@
 				)
 
 
+def validate_against_pcv(is_opening, posting_date, company):
+	if is_opening and frappe.db.exists(
+		"Period Closing Voucher", {"docstatus": 1, "company": company}
+	):
+		frappe.throw(
+			_("Opening Entry can not be created after Period Closing Voucher is created."),
+			title=_("Invalid Opening Entry"),
+		)
+
+	last_pcv_date = frappe.db.get_value(
+		"Period Closing Voucher", {"docstatus": 1, "company": company}, "max(posting_date)"
+	)
+
+	if last_pcv_date and getdate(posting_date) <= getdate(last_pcv_date):
+		message = _("Books have been closed till the period ending on {0}").format(
+			formatdate(last_pcv_date)
+		)
+		message += "</br >"
+		message += _("You cannot create/amend any accounting entries till this date.")
+		frappe.throw(message, title=_("Period Closed"))
+
+
 def set_as_cancel(voucher_type, voucher_no):
 	"""
 	Set is_cancelled=1 in all original gl entries for the voucher
diff --git a/erpnext/accounts/party.py b/erpnext/accounts/party.py
index 01cfb58..ac9368e 100644
--- a/erpnext/accounts/party.py
+++ b/erpnext/accounts/party.py
@@ -32,6 +32,16 @@
 from erpnext.accounts.utils import get_fiscal_year
 from erpnext.exceptions import InvalidAccountCurrency, PartyDisabled, PartyFrozen
 
+PURCHASE_TRANSACTION_TYPES = {"Purchase Order", "Purchase Receipt", "Purchase Invoice"}
+SALES_TRANSACTION_TYPES = {
+	"Quotation",
+	"Sales Order",
+	"Delivery Note",
+	"Sales Invoice",
+	"POS Invoice",
+}
+TRANSACTION_TYPES = PURCHASE_TRANSACTION_TYPES | SALES_TRANSACTION_TYPES
+
 
 class DuplicatePartyAccountError(frappe.ValidationError):
 	pass
@@ -124,12 +134,6 @@
 	set_other_values(party_details, party, party_type)
 	set_price_list(party_details, party, party_type, price_list, pos_profile)
 
-	party_details["tax_category"] = get_address_tax_category(
-		party.get("tax_category"),
-		party_address,
-		shipping_address if party_type != "Supplier" else party_address,
-	)
-
 	tax_template = set_taxes(
 		party.name,
 		party_type,
@@ -170,6 +174,9 @@
 			party_type, party.name, "tax_withholding_category"
 		)
 
+	if not party_details.get("tax_category") and pos_profile:
+		party_details["tax_category"] = frappe.get_value("POS Profile", pos_profile, "tax_category")
+
 	return party_details
 
 
@@ -211,20 +218,10 @@
 	else:
 		party_details.update(get_company_address(company))
 
-	if doctype and doctype in [
-		"Delivery Note",
-		"Sales Invoice",
-		"Sales Order",
-		"Quotation",
-		"POS Invoice",
-	]:
-		if party_details.company_address:
-			party_details.update(
-				get_fetch_values(doctype, "company_address", party_details.company_address)
-			)
-		get_regional_address_details(party_details, doctype, company)
+	if doctype in SALES_TRANSACTION_TYPES and party_details.company_address:
+		party_details.update(get_fetch_values(doctype, "company_address", party_details.company_address))
 
-	elif doctype and doctype in ["Purchase Invoice", "Purchase Order", "Purchase Receipt"]:
+	if doctype in PURCHASE_TRANSACTION_TYPES:
 		if shipping_address:
 			party_details.update(
 				shipping_address=shipping_address,
@@ -250,9 +247,21 @@
 					**get_fetch_values(doctype, "shipping_address", party_details.billing_address)
 				)
 
+	party_address, shipping_address = (
+		party_details.get(billing_address_field),
+		party_details.shipping_address_name,
+	)
+
+	party_details["tax_category"] = get_address_tax_category(
+		party.get("tax_category"),
+		party_address,
+		shipping_address if party_type != "Supplier" else party_address,
+	)
+
+	if doctype in TRANSACTION_TYPES:
 		get_regional_address_details(party_details, doctype, company)
 
-	return party_details.get(billing_address_field), party_details.shipping_address_name
+	return party_address, shipping_address
 
 
 @erpnext.allow_regional
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 94a1510..11de9a0 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -859,7 +859,7 @@
 						)
 					else:
 						self.qb_selection_filter.append(
-							self.ple[dimension.fieldname] == self.filters[dimension.fieldname]
+							self.ple[dimension.fieldname].isin(self.filters[dimension.fieldname])
 						)
 
 	def is_invoice(self, ple):
diff --git a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
index 57d8049..f21c94b 100644
--- a/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
+++ b/erpnext/accounts/report/asset_depreciation_ledger/asset_depreciation_ledger.py
@@ -25,6 +25,7 @@
 		["posting_date", "<=", filters.get("to_date")],
 		["against_voucher_type", "=", "Asset"],
 		["account", "in", depreciation_accounts],
+		["is_cancelled", "=", 0],
 	]
 
 	if filters.get("asset"):
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.py b/erpnext/accounts/report/balance_sheet/balance_sheet.py
index 07552e3..b225aac 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.py
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.py
@@ -25,6 +25,8 @@
 		company=filters.company,
 	)
 
+	filters.period_start_date = period_list[0]["year_start_date"]
+
 	currency = filters.presentation_currency or frappe.get_cached_value(
 		"Company", filters.company, "default_currency"
 	)
@@ -96,7 +98,7 @@
 	chart = get_chart_data(filters, columns, asset, liability, equity)
 
 	report_summary = get_report_summary(
-		period_list, asset, liability, equity, provisional_profit_loss, total_credit, currency, filters
+		period_list, asset, liability, equity, provisional_profit_loss, currency, filters
 	)
 
 	return columns, data, message, chart, report_summary
@@ -174,7 +176,6 @@
 	liability,
 	equity,
 	provisional_profit_loss,
-	total_credit,
 	currency,
 	filters,
 	consolidated=False,
diff --git a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
index 449ebdc..306af72 100644
--- a/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
+++ b/erpnext/accounts/report/bank_clearance_summary/bank_clearance_summary.py
@@ -4,6 +4,7 @@
 
 import frappe
 from frappe import _
+from frappe.query_builder.custom import ConstantColumn
 from frappe.utils import getdate, nowdate
 
 
@@ -91,4 +92,65 @@
 		as_list=1,
 	)
 
-	return sorted(journal_entries + payment_entries, key=lambda k: k[2] or getdate(nowdate()))
+	# Loan Disbursement
+	loan_disbursement = frappe.qb.DocType("Loan Disbursement")
+
+	query = (
+		frappe.qb.from_(loan_disbursement)
+		.select(
+			ConstantColumn("Loan Disbursement").as_("payment_document_type"),
+			loan_disbursement.name.as_("payment_entry"),
+			loan_disbursement.disbursement_date.as_("posting_date"),
+			loan_disbursement.reference_number.as_("cheque_no"),
+			loan_disbursement.clearance_date.as_("clearance_date"),
+			loan_disbursement.applicant.as_("against"),
+			-loan_disbursement.disbursed_amount.as_("amount"),
+		)
+		.where(loan_disbursement.docstatus == 1)
+		.where(loan_disbursement.disbursement_date >= filters["from_date"])
+		.where(loan_disbursement.disbursement_date <= filters["to_date"])
+		.where(loan_disbursement.disbursement_account == filters["account"])
+		.orderby(loan_disbursement.disbursement_date, order=frappe.qb.desc)
+		.orderby(loan_disbursement.name, order=frappe.qb.desc)
+	)
+
+	if filters.get("from_date"):
+		query = query.where(loan_disbursement.disbursement_date >= filters["from_date"])
+	if filters.get("to_date"):
+		query = query.where(loan_disbursement.disbursement_date <= filters["to_date"])
+
+	loan_disbursements = query.run(as_list=1)
+
+	# Loan Repayment
+	loan_repayment = frappe.qb.DocType("Loan Repayment")
+
+	query = (
+		frappe.qb.from_(loan_repayment)
+		.select(
+			ConstantColumn("Loan Repayment").as_("payment_document_type"),
+			loan_repayment.name.as_("payment_entry"),
+			loan_repayment.posting_date.as_("posting_date"),
+			loan_repayment.reference_number.as_("cheque_no"),
+			loan_repayment.clearance_date.as_("clearance_date"),
+			loan_repayment.applicant.as_("against"),
+			loan_repayment.amount_paid.as_("amount"),
+		)
+		.where(loan_repayment.docstatus == 1)
+		.where(loan_repayment.posting_date >= filters["from_date"])
+		.where(loan_repayment.posting_date <= filters["to_date"])
+		.where(loan_repayment.payment_account == filters["account"])
+		.orderby(loan_repayment.posting_date, order=frappe.qb.desc)
+		.orderby(loan_repayment.name, order=frappe.qb.desc)
+	)
+
+	if filters.get("from_date"):
+		query = query.where(loan_repayment.posting_date >= filters["from_date"])
+	if filters.get("to_date"):
+		query = query.where(loan_repayment.posting_date <= filters["to_date"])
+
+	loan_repayments = query.run(as_list=1)
+
+	return sorted(
+		journal_entries + payment_entries + loan_disbursements + loan_repayments,
+		key=lambda k: k[2] or getdate(nowdate()),
+	)
diff --git a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
index ddee9fc..33da6ff 100644
--- a/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
+++ b/erpnext/accounts/report/consolidated_financial_statement/consolidated_financial_statement.py
@@ -118,7 +118,6 @@
 		liability,
 		equity,
 		provisional_profit_loss,
-		total_credit,
 		company_currency,
 		filters,
 		True,
@@ -138,7 +137,8 @@
 		for data in [asset_data, liability_data, equity_data]:
 			if data:
 				account_name = get_root_account_name(data[0].root_type, company)
-				opening_value += get_opening_balance(account_name, data, company) or 0.0
+				if account_name:
+					opening_value += get_opening_balance(account_name, data, company) or 0.0
 
 		opening_balance[company] = opening_value
 
@@ -155,7 +155,7 @@
 
 
 def get_root_account_name(root_type, company):
-	return frappe.get_all(
+	root_account = frappe.get_all(
 		"Account",
 		fields=["account_name"],
 		filters={
@@ -165,7 +165,10 @@
 			"parent_account": ("is", "not set"),
 		},
 		as_list=1,
-	)[0][0]
+	)
+
+	if root_account:
+		return root_account[0][0]
 
 
 def get_profit_loss_data(fiscal_year, companies, columns, filters):
diff --git a/erpnext/accounts/report/financial_statements.py b/erpnext/accounts/report/financial_statements.py
index 8c6fe43..debe655 100644
--- a/erpnext/accounts/report/financial_statements.py
+++ b/erpnext/accounts/report/financial_statements.py
@@ -418,46 +418,47 @@
 	ignore_closing_entries=False,
 ):
 	"""Returns a dict like { "account": [gl entries], ... }"""
+	gl_entries = []
 
-	additional_conditions = get_additional_conditions(from_date, ignore_closing_entries, filters)
-
-	accounts = frappe.db.sql_list(
-		"""select name from `tabAccount`
-		where lft >= %s and rgt <= %s and company = %s""",
-		(root_lft, root_rgt, company),
+	accounts_list = frappe.db.get_all(
+		"Account",
+		filters={"company": company, "is_group": 0, "lft": (">=", root_lft), "rgt": ("<=", root_rgt)},
+		pluck="name",
 	)
 
-	if accounts:
-		additional_conditions += " and account in ({})".format(
-			", ".join(frappe.db.escape(d) for d in accounts)
-		)
+	ignore_opening_entries = False
+	if accounts_list:
+		# For balance sheet
+		if not from_date:
+			from_date = filters["period_start_date"]
+			last_period_closing_voucher = frappe.db.get_all(
+				"Period Closing Voucher",
+				filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", from_date)},
+				fields=["posting_date", "name"],
+				order_by="posting_date desc",
+				limit=1,
+			)
+			if last_period_closing_voucher:
+				gl_entries += get_accounting_entries(
+					"Account Closing Balance",
+					from_date,
+					to_date,
+					accounts_list,
+					filters,
+					ignore_closing_entries,
+					last_period_closing_voucher[0].name,
+				)
+				from_date = add_days(last_period_closing_voucher[0].posting_date, 1)
+				ignore_opening_entries = True
 
-		gl_filters = {
-			"company": company,
-			"from_date": from_date,
-			"to_date": to_date,
-			"finance_book": cstr(filters.get("finance_book")),
-		}
-
-		if filters.get("include_default_book_entries"):
-			gl_filters["company_fb"] = frappe.get_cached_value("Company", company, "default_finance_book")
-
-		for key, value in filters.items():
-			if value:
-				gl_filters.update({key: value})
-
-		gl_entries = frappe.db.sql(
-			"""
-			select posting_date, account, debit, credit, is_opening, fiscal_year,
-				debit_in_account_currency, credit_in_account_currency, account_currency from `tabGL Entry`
-			where company=%(company)s
-			{additional_conditions}
-			and posting_date <= %(to_date)s
-			and is_cancelled = 0""".format(
-				additional_conditions=additional_conditions
-			),
-			gl_filters,
-			as_dict=True,
+		gl_entries += get_accounting_entries(
+			"GL Entry",
+			from_date,
+			to_date,
+			accounts_list,
+			filters,
+			ignore_closing_entries,
+			ignore_opening_entries=ignore_opening_entries,
 		)
 
 		if filters and filters.get("presentation_currency"):
@@ -469,34 +470,82 @@
 		return gl_entries_by_account
 
 
-def get_additional_conditions(from_date, ignore_closing_entries, filters):
-	additional_conditions = []
+def get_accounting_entries(
+	doctype,
+	from_date,
+	to_date,
+	accounts,
+	filters,
+	ignore_closing_entries,
+	period_closing_voucher=None,
+	ignore_opening_entries=False,
+):
+	gl_entry = frappe.qb.DocType(doctype)
+	query = (
+		frappe.qb.from_(gl_entry)
+		.select(
+			gl_entry.account,
+			gl_entry.debit,
+			gl_entry.credit,
+			gl_entry.debit_in_account_currency,
+			gl_entry.credit_in_account_currency,
+			gl_entry.account_currency,
+		)
+		.where(gl_entry.company == filters.company)
+	)
 
+	if doctype == "GL Entry":
+		query = query.select(gl_entry.posting_date, gl_entry.is_opening, gl_entry.fiscal_year)
+		query = query.where(gl_entry.is_cancelled == 0)
+		query = query.where(gl_entry.posting_date <= to_date)
+
+		if ignore_opening_entries:
+			query = query.where(gl_entry.is_opening == "No")
+	else:
+		query = query.select(gl_entry.closing_date.as_("posting_date"))
+		query = query.where(gl_entry.period_closing_voucher == period_closing_voucher)
+
+	query = apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters)
+	query = query.where(gl_entry.account.isin(accounts))
+
+	entries = query.run(as_dict=True)
+
+	return entries
+
+
+def apply_additional_conditions(doctype, query, from_date, ignore_closing_entries, filters):
+	gl_entry = frappe.qb.DocType(doctype)
 	accounting_dimensions = get_accounting_dimensions(as_list=False)
 
 	if ignore_closing_entries:
-		additional_conditions.append("ifnull(voucher_type, '')!='Period Closing Voucher'")
+		if doctype == "GL Entry":
+			query = query.where(gl_entry.voucher_type != "Period Closing Voucher")
+		else:
+			query = query.where(gl_entry.is_period_closing_voucher_entry == 0)
 
-	if from_date:
-		additional_conditions.append("posting_date >= %(from_date)s")
+	if from_date and doctype == "GL Entry":
+		query = query.where(gl_entry.posting_date >= from_date)
 
 	if filters:
 		if filters.get("project"):
 			if not isinstance(filters.get("project"), list):
 				filters.project = frappe.parse_json(filters.get("project"))
 
-			additional_conditions.append("project in %(project)s")
+			query = query.where(gl_entry.project.isin(filters.project))
 
 		if filters.get("cost_center"):
 			filters.cost_center = get_cost_centers_with_children(filters.cost_center)
-			additional_conditions.append("cost_center in %(cost_center)s")
+			query = query.where(gl_entry.cost_center.isin(filters.cost_center))
 
 		if filters.get("include_default_book_entries"):
-			additional_conditions.append(
-				"(finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
+			query = query.where(
+				(gl_entry.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
+				| (gl_entry.finance_book.isnull())
 			)
 		else:
-			additional_conditions.append("(finance_book in (%(finance_book)s, '') OR finance_book IS NULL)")
+			query = query.where(
+				(gl_entry.finance_book.isin([cstr(filters.company_fb), ""])) | (gl_entry.finance_book.isnull())
+			)
 
 	if accounting_dimensions:
 		for dimension in accounting_dimensions:
@@ -505,11 +554,10 @@
 					filters[dimension.fieldname] = get_dimension_with_children(
 						dimension.document_type, filters.get(dimension.fieldname)
 					)
-					additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
-				else:
-					additional_conditions.append("{0} in %({0})s".format(dimension.fieldname))
 
-	return " and {}".format(" and ".join(additional_conditions)) if additional_conditions else ""
+				query = query.where(gl_entry[dimension.fieldname].isin(filters[dimension.fieldname]))
+
+	return query
 
 
 def get_cost_centers_with_children(cost_centers):
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.html b/erpnext/accounts/report/general_ledger/general_ledger.html
index 475be92..2d5ca49 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.html
+++ b/erpnext/accounts/report/general_ledger/general_ledger.html
@@ -38,8 +38,11 @@
 			{% if(data[i].posting_date) { %}
 				<td>{%= frappe.datetime.str_to_user(data[i].posting_date) %}</td>
 				<td>{%= data[i].voucher_type %}
-					<br>{%= data[i].voucher_no %}</td>
-				<td>
+					<br>{%= data[i].voucher_no %}
+				</td>
+				{% var longest_word = cstr(data[i].remarks).split(" ").reduce((longest, word) => word.length > longest.length ? word : longest, ""); %}
+				<td {% if longest_word.length > 45 %} class="overflow-wrap-anywhere" {% endif %}>
+					<span>
 					{% if(!(filters.party || filters.account)) { %}
 						{%= data[i].party || data[i].account %}
 						<br>
@@ -49,11 +52,14 @@
 					{% if(data[i].bill_no) { %}
 						<br>{%= __("Supplier Invoice No") %}: {%= data[i].bill_no %}
 					{% } %}
-					</td>
-					<td style="text-align: right">
-						{%= format_currency(data[i].debit, filters.presentation_currency || data[i].account_currency) %}</td>
-					<td style="text-align: right">
-						{%= format_currency(data[i].credit, filters.presentation_currency || data[i].account_currency) %}</td>
+					</span>
+				</td>
+				<td style="text-align: right">
+					{%= format_currency(data[i].debit, filters.presentation_currency) %}
+				</td>
+				<td style="text-align: right">
+					{%= format_currency(data[i].credit, filters.presentation_currency) %}
+				</td>
 			{% } else { %}
 				<td></td>
 				<td></td>
diff --git a/erpnext/accounts/report/general_ledger/general_ledger.js b/erpnext/accounts/report/general_ledger/general_ledger.js
index 010284c..2100f26 100644
--- a/erpnext/accounts/report/general_ledger/general_ledger.js
+++ b/erpnext/accounts/report/general_ledger/general_ledger.js
@@ -58,9 +58,8 @@
 		{
 			"fieldname":"party_type",
 			"label": __("Party Type"),
-			"fieldtype": "Link",
-			"options": "Party Type",
-			"default": "",
+			"fieldtype": "Autocomplete",
+			options: Object.keys(frappe.boot.party_account_types),
 			on_change: function() {
 				frappe.query_report.set_filter_value('party', "");
 			}
diff --git a/erpnext/accounts/report/general_ledger/test_general_ledger.py b/erpnext/accounts/report/general_ledger/test_general_ledger.py
index c563785..a8c362e 100644
--- a/erpnext/accounts/report/general_ledger/test_general_ledger.py
+++ b/erpnext/accounts/report/general_ledger/test_general_ledger.py
@@ -24,7 +24,6 @@
 				"root_type": "Asset",
 				"report_type": "Balance Sheet",
 				"account_currency": "USD",
-				"inter_company_account": 0,
 				"parent_account": "Bank Accounts - _TC",
 				"account_type": "Bank",
 				"doctype": "Account",
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index fde4de8..01fee28 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -501,7 +501,14 @@
 						):
 							returned_item_rows = self.returned_invoices[row.parent][row.item_code]
 							for returned_item_row in returned_item_rows:
-								row.qty += flt(returned_item_row.qty)
+								# returned_items 'qty' should be stateful
+								if returned_item_row.qty != 0:
+									if row.qty >= abs(returned_item_row.qty):
+										row.qty += returned_item_row.qty
+										returned_item_row.qty = 0
+									else:
+										row.qty = 0
+										returned_item_row.qty += row.qty
 								row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
 							row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
 						if flt(row.qty) or row.base_amount:
@@ -734,6 +741,8 @@
 		if self.filters.to_date:
 			conditions += " and posting_date <= %(to_date)s"
 
+		conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
+
 		if self.filters.item_group:
 			conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
 
diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py
index 21681be..82fe1a0 100644
--- a/erpnext/accounts/report/gross_profit/test_gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py
@@ -381,3 +381,82 @@
 		}
 		gp_entry = [x for x in data if x.parent_invoice == sinv.name]
 		self.assertDictContainsSubset(expected_entry, gp_entry[0])
+
+	def test_crnote_against_invoice_with_multiple_instances_of_same_item(self):
+		"""
+		Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items.
+		"""
+		from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
+
+		# Invoice with an item added twice
+		sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True)
+		sinv.append("items", frappe.copy_doc(sinv.items[0], ignore_no_copy=False))
+		sinv = sinv.save().submit()
+
+		# Create Credit Note for Invoice
+		cr_note = make_sales_return(sinv.name)
+		cr_note = cr_note.save().submit()
+
+		filters = frappe._dict(
+			company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
+		)
+
+		columns, data = execute(filters=filters)
+		expected_entry = {
+			"parent_invoice": sinv.name,
+			"currency": "INR",
+			"sales_invoice": self.item,
+			"customer": self.customer,
+			"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
+			"item_code": self.item,
+			"item_name": self.item,
+			"warehouse": "Stores - _GP",
+			"qty": 0.0,
+			"avg._selling_rate": 0.0,
+			"valuation_rate": 0.0,
+			"selling_amount": -100.0,
+			"buying_amount": 0.0,
+			"gross_profit": -100.0,
+			"gross_profit_%": 100.0,
+		}
+		gp_entry = [x for x in data if x.parent_invoice == sinv.name]
+		# Both items of Invoice should have '0' qty
+		self.assertEqual(len(gp_entry), 2)
+		self.assertDictContainsSubset(expected_entry, gp_entry[0])
+		self.assertDictContainsSubset(expected_entry, gp_entry[1])
+
+	def test_standalone_cr_notes(self):
+		"""
+		Standalone cr notes will be reported as usual
+		"""
+		# Make Cr Note
+		sinv = self.create_sales_invoice(
+			qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
+		)
+		sinv.is_return = 1
+		sinv = sinv.save().submit()
+
+		filters = frappe._dict(
+			company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
+		)
+
+		columns, data = execute(filters=filters)
+		expected_entry = {
+			"parent_invoice": sinv.name,
+			"currency": "INR",
+			"sales_invoice": self.item,
+			"customer": self.customer,
+			"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
+			"item_code": self.item,
+			"item_name": self.item,
+			"warehouse": "Stores - _GP",
+			"qty": -1.0,
+			"avg._selling_rate": 100.0,
+			"valuation_rate": 0.0,
+			"selling_amount": -100.0,
+			"buying_amount": 0.0,
+			"gross_profit": -100.0,
+			"gross_profit_%": 100.0,
+		}
+		gp_entry = [x for x in data if x.parent_invoice == sinv.name]
+		self.assertDictContainsSubset(expected_entry, gp_entry[0])
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.js b/erpnext/accounts/report/payment_ledger/payment_ledger.js
index 9779844..a5a4108 100644
--- a/erpnext/accounts/report/payment_ledger/payment_ledger.js
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.js
@@ -38,6 +38,29 @@
 			}
 		},
 		{
+			"fieldname":"party_type",
+			"label": __("Party Type"),
+			"fieldtype": "Link",
+			"options": "Party Type",
+			"default": "",
+			on_change: function() {
+				frappe.query_report.set_filter_value('party', "");
+			}
+		},
+		{
+			"fieldname":"party",
+			"label": __("Party"),
+			"fieldtype": "MultiSelectList",
+			get_data: function(txt) {
+				if (!frappe.query_report.filters) return;
+
+				let party_type = frappe.query_report.get_filter_value('party_type');
+				if (!party_type) return;
+
+				return frappe.db.get_link_options(party_type, txt);
+			},
+		},
+		{
 			"fieldname":"voucher_no",
 			"label": __("Voucher No"),
 			"fieldtype": "Data",
@@ -49,6 +72,20 @@
 			"fieldtype": "Data",
 			"width": 100,
 		},
+		{
+			"fieldname":"include_account_currency",
+			"label": __("Include Account Currency"),
+			"fieldtype": "Check",
+			"width": 100,
+		},
+		{
+			"fieldname":"group_party",
+			"label": __("Group by Party"),
+			"fieldtype": "Check",
+			"width": 100,
+		},
+
+
 
 	]
 	return filters;
diff --git a/erpnext/accounts/report/payment_ledger/payment_ledger.py b/erpnext/accounts/report/payment_ledger/payment_ledger.py
index e470c27..8875d27 100644
--- a/erpnext/accounts/report/payment_ledger/payment_ledger.py
+++ b/erpnext/accounts/report/payment_ledger/payment_ledger.py
@@ -17,34 +17,26 @@
 		self.ple = qb.DocType("Payment Ledger Entry")
 
 	def init_voucher_dict(self):
-
 		if self.voucher_amount:
-			s = set()
-			# build  a set of unique vouchers
+			# for each ple, using group_by_key to create a key and assign it to +/- list
 			for ple in self.voucher_amount:
-				key = (ple.voucher_type, ple.voucher_no, ple.party)
-				s.add(key)
+				group_by_key = None
+				if not self.filters.group_party:
+					group_by_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
+				else:
+					group_by_key = (ple.party_type, ple.party)
 
-			# for each unique vouchers, initialize +/- list
-			for key in s:
-				self.voucher_dict[key] = frappe._dict(increase=list(), decrease=list())
-
-			# for each ple, using against voucher and amount, assign it to +/- list
-			# group by against voucher
-			for ple in self.voucher_amount:
-				against_key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
 				target = None
-				if self.voucher_dict.get(against_key):
-					if ple.amount > 0:
-						target = self.voucher_dict.get(against_key).increase
-					else:
-						target = self.voucher_dict.get(against_key).decrease
+				if ple.amount > 0:
+					target = self.voucher_dict.setdefault(group_by_key, {}).setdefault("increase", [])
+				else:
+					target = self.voucher_dict.setdefault(group_by_key, {}).setdefault("decrease", [])
 
 				# this if condition will lose unassigned ple entries(against_voucher doc doesn't have ple)
 				# need to somehow include the stray entries as well.
 				if target is not None:
 					entry = frappe._dict(
-						company=ple.company,
+						posting_date=ple.posting_date,
 						account=ple.account,
 						party_type=ple.party_type,
 						party=ple.party,
@@ -66,10 +58,10 @@
 
 		for value in self.voucher_dict.values():
 			voucher_data = []
-			if value.increase != []:
-				voucher_data.extend(value.increase)
-			if value.decrease != []:
-				voucher_data.extend(value.decrease)
+			if value.get("increase"):
+				voucher_data.extend(value.get("increase"))
+			if value.get("decrease"):
+				voucher_data.extend(value.get("decrease"))
 
 			if voucher_data:
 				# balance row
@@ -117,6 +109,12 @@
 		if self.filters.against_voucher_no:
 			self.conditions.append(self.ple.against_voucher_no == self.filters.against_voucher_no)
 
+		if self.filters.party_type:
+			self.conditions.append(self.ple.party_type == self.filters.party_type)
+
+		if self.filters.party:
+			self.conditions.append(self.ple.party.isin(self.filters.party))
+
 	def get_data(self):
 		ple = self.ple
 
@@ -134,7 +132,13 @@
 	def get_columns(self):
 		options = None
 		self.columns.append(
-			dict(label=_("Company"), fieldname="company", fieldtype="data", options=options, width="100")
+			dict(
+				label=_("Posting Date"),
+				fieldname="posting_date",
+				fieldtype="Date",
+				options=options,
+				width="100",
+			)
 		)
 
 		self.columns.append(
@@ -160,7 +164,11 @@
 		)
 		self.columns.append(
 			dict(
-				label=_("Voucher No"), fieldname="voucher_no", fieldtype="data", options=options, width="100"
+				label=_("Voucher No"),
+				fieldname="voucher_no",
+				fieldtype="Dynamic Link",
+				options="voucher_type",
+				width="100",
 			)
 		)
 		self.columns.append(
@@ -176,8 +184,8 @@
 			dict(
 				label=_("Against Voucher No"),
 				fieldname="against_voucher_no",
-				fieldtype="data",
-				options=options,
+				fieldtype="Dynamic Link",
+				options="against_voucher_type",
 				width="100",
 			)
 		)
@@ -209,7 +217,7 @@
 		self.get_columns()
 		self.get_data()
 
-		# initialize dictionary and group using against voucher
+		# initialize dictionary and group using key
 		self.init_voucher_dict()
 
 		# convert dictionary to list and add balance rows
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.js b/erpnext/accounts/report/tax_detail/tax_detail.js
deleted file mode 100644
index ed6fac4..0000000
--- a/erpnext/accounts/report/tax_detail/tax_detail.js
+++ /dev/null
@@ -1,451 +0,0 @@
-// Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-// Contributed by Case Solved and sponsored by Nulight Studios
-/* eslint-disable */
-
-frappe.provide('frappe.query_reports');
-
-frappe.query_reports["Tax Detail"] = {
-	filters: [
-		{
-			fieldname: "company",
-			label: __("Company"),
-			fieldtype: "Link",
-			options: "Company",
-			default: frappe.defaults.get_user_default("company"),
-			reqd: 1
-		},
-		{
-			fieldname: "from_date",
-			label: __("From Date"),
-			fieldtype: "Date",
-			default: frappe.datetime.month_start(frappe.datetime.get_today()),
-			reqd: 1,
-			width: "60px"
-		},
-		{
-			fieldname: "to_date",
-			label: __("To Date"),
-			fieldtype: "Date",
-			default: frappe.datetime.month_end(frappe.datetime.get_today()),
-			reqd: 1,
-			width: "60px"
-		},
-		{
-			fieldname: "report_name",
-			label: __("Report Name"),
-			fieldtype: "Read Only",
-			default: frappe.query_report.report_name,
-			hidden: 1,
-			reqd: 1
-		},
-		{
-			fieldname: "mode",
-			label: __("Mode"),
-			fieldtype: "Read Only",
-			default: "edit",
-			hidden: 1,
-			reqd: 1
-		}
-	],
-	onload: function onload(report) {
-		// Remove Add Column and Save from menu
-		report.page.add_inner_button(__("New Report"), () => new_report(), __("Custom Report"));
-		report.page.add_inner_button(__("Load Report"), () => load_report(), __("Custom Report"));
-		hide_filters(report);
-	}
-};
-
-function hide_filters(report) {
-	report.page.page_form[0].querySelectorAll('.form-group.frappe-control').forEach(function setHidden(field) {
-		if (field.dataset.fieldtype == "Read Only") {
-			field.classList.add("hidden");
-		}
-	});
-}
-
-erpnext.TaxDetail = class TaxDetail {
-	constructor() {
-		this.patch();
-		this.load_report();
-	}
-	// Monkey patch the QueryReport class
-	patch() {
-		this.qr = frappe.query_report;
-		this.super = {
-			refresh_report: this.qr.refresh_report,
-			show_footer_message: this.qr.show_footer_message
-		}
-		this.qr.refresh_report = () => this.refresh_report();
-		this.qr.show_footer_message = () => this.show_footer_message();
-	}
-	show_footer_message() {
-		// The last thing to run after datatable_render in refresh()
-		this.super.show_footer_message.apply(this.qr);
-		if (this.qr.report_name !== 'Tax Detail') {
-			this.show_help();
-			if (this.loading) {
-				this.set_section('');
-			} else {
-				this.reload_component('');
-			}
-		}
-		this.loading = false;
-	}
-	refresh_report() {
-		// Infrequent report build (onload), load filters & data
-		// super function runs a refresh() serially
-		// already run within frappe.run_serially
-		this.loading = true;
-		this.super.refresh_report.apply(this.qr);
-		if (this.qr.report_name !== 'Tax Detail') {
-			frappe.call({
-				method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
-				args: {name: this.qr.report_name}
-			}).then((r) => {
-				const data = JSON.parse(r.message[this.qr.report_name]['json']);
-				this.create_controls();
-				this.sections = data.sections || {};
-				this.controls['show_detail'].set_input(data.show_detail);
-			});
-		}
-	}
-	load_report() {
-		// One-off report build like titles, menu, etc
-		// Run when this object is created which happens in qr.load_report
-		this.qr.menu_items = this.get_menu_items();
-	}
-	get_menu_items() {
-		// Replace Save action
-		let new_items = [];
-		const save = __('Save');
-
-		for (let item of this.qr.menu_items) {
-			if (item.label === save) {
-				new_items.push({
-					label: save,
-					action: () => this.save_report(),
-					standard: false
-				});
-			} else {
-				new_items.push(item);
-			}
-		}
-		return new_items;
-	}
-	save_report() {
-		this.check_datatable();
-		if (this.qr.report_name !== 'Tax Detail') {
-			frappe.call({
-				method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
-				args: {
-					reference_report: 'Tax Detail',
-					report_name: this.qr.report_name,
-					data: {
-						columns: this.qr.get_visible_columns(),
-						sections: this.sections,
-						show_detail: this.controls['show_detail'].get_input_value()
-					}
-				},
-				freeze: true
-			}).then((r) => {
-				this.set_section('');
-			});
-		}
-	}
-	check_datatable() {
-		if (!this.qr.datatable) {
-			frappe.throw(__('Please change the date range to load data first'));
-		}
-	}
-	set_section(name) {
-		// Sets the given section name and then reloads the data
-		if (name && !this.sections[name]) {
-			this.sections[name] = {};
-		}
-		let options = Object.keys(this.sections);
-		options.unshift('');
-		this.controls['section_name'].$wrapper.find("select").empty().add_options(options);
-		const org_mode = this.qr.get_filter_value('mode');
-		let refresh = false;
-		if (name) {
-			this.controls['section_name'].set_input(name);
-			this.qr.set_filter_value('mode', 'edit');
-			if (org_mode === 'run') {
-				refresh = true;
-			}
-		} else {
-			this.controls['section_name'].set_input('');
-			this.qr.set_filter_value('mode', 'run');
-			if (org_mode === 'edit') {
-				refresh = true;
-			}
-		}
-		if (refresh) {
-			this.qr.refresh();
-		}
-		this.reload_component('');
-	}
-	reload_component(component_name) {
-		const section_name = this.controls['section_name'].get_input_value();
-		if (section_name) {
-			const section = this.sections[section_name];
-			const component_names = Object.keys(section);
-			component_names.unshift('');
-			this.controls['component'].$wrapper.find("select").empty().add_options(component_names);
-			this.controls['component'].set_input(component_name);
-			if (component_name) {
-				this.controls['component_type'].set_input(section[component_name].type);
-			}
-		} else {
-			this.controls['component'].$wrapper.find("select").empty();
-			this.controls['component'].set_input('');
-		}
-		this.set_table_filters();
-	}
-	set_table_filters() {
-		let filters = {};
-		const section_name = this.controls['section_name'].get_input_value();
-		const component_name = this.controls['component'].get_input_value();
-		if (section_name && component_name) {
-			const component_type = this.sections[section_name][component_name].type;
-			if (component_type === 'filter') {
-				filters = this.sections[section_name][component_name]['filters'];
-			}
-		}
-		this.setAppliedFilters(filters);
-	}
-	setAppliedFilters(filters) {
-		if (this.qr.datatable) {
-			Array.from(this.qr.datatable.header.querySelectorAll('.dt-filter')).map(function setFilters(input) {
-				let idx = input.dataset.colIndex;
-				if (filters[idx]) {
-					input.value = filters[idx];
-				} else {
-					input.value = null;
-				}
-			});
-			this.qr.datatable.columnmanager.applyFilter(filters);
-		}
-	}
-	delete(name, type) {
-		if (type === 'section') {
-			delete this.sections[name];
-			const new_section = Object.keys(this.sections)[0] || '';
-			this.set_section(new_section);
-		}
-		if (type === 'component') {
-			const cur_section = this.controls['section_name'].get_input_value();
-			delete this.sections[cur_section][name];
-			this.reload_component('');
-		}
-	}
-	create_controls() {
-		let controls = {};
-		// SELECT in data.js
-		controls['section_name'] = this.qr.page.add_field({
-			label: __('Section'),
-			fieldtype: 'Select',
-			fieldname: 'section_name',
-			change: (e) => {
-				this.set_section(this.controls['section_name'].get_input_value());
-			}
-		});
-		// BUTTON in button.js
-		controls['new_section'] = this.qr.page.add_field({
-			label: __('New Section'),
-			fieldtype: 'Button',
-			fieldname: 'new_section',
-			click: () => {
-				frappe.prompt({
-					label: __('Section Name'),
-					fieldname: 'name',
-					fieldtype: 'Data'
-				}, (values) => {
-					this.set_section(values.name);
-				});
-			}
-		});
-		controls['delete_section'] = this.qr.page.add_field({
-			label: __('Delete Section'),
-			fieldtype: 'Button',
-			fieldname: 'delete_section',
-			click: () => {
-				let cur_section = this.controls['section_name'].get_input_value();
-				if (cur_section) {
-					frappe.confirm(__('Are you sure you want to delete section') + ' ' + cur_section + '?',
-					() => {this.delete(cur_section, 'section')});
-				}
-			}
-		});
-		controls['component'] = this.qr.page.add_field({
-			label: __('Component'),
-			fieldtype: 'Select',
-			fieldname: 'component',
-			change: (e) => {
-				this.reload_component(this.controls['component'].get_input_value());
-			}
-		});
-		controls['component_type'] = this.qr.page.add_field({
-			label: __('Component Type'),
-			fieldtype: 'Select',
-			fieldname: 'component_type',
-			default: 'filter',
-			options: [
-				{label: __('Filtered Row Subtotal'), value: 'filter'},
-				{label: __('Section Subtotal'), value: 'section'}
-			]
-		});
-		controls['add_component'] = this.qr.page.add_field({
-			label: __('Add Component'),
-			fieldtype: 'Button',
-			fieldname: 'add_component',
-			click: () => {
-				this.check_datatable();
-				let section_name = this.controls['section_name'].get_input_value();
-				if (section_name) {
-					const component_type = this.controls['component_type'].get_input_value();
-					let idx = 0;
-					const names = Object.keys(this.sections[section_name]);
-					if (names.length > 0) {
-						const idxs = names.map((key) => parseInt(key.match(/\d+$/)) || 0);
-						idx = Math.max(...idxs) + 1;
-					}
-					const filters = this.qr.datatable.columnmanager.getAppliedFilters();
-					if (component_type === 'filter') {
-						const name = 'Filter' + idx.toString();
-						let data = {
-							type: component_type,
-							filters: filters
-						}
-						this.sections[section_name][name] = data;
-						this.reload_component(name);
-					} else if (component_type === 'section') {
-						if (filters && Object.keys(filters).length !== 0) {
-							frappe.show_alert({
-								message: __('Column filters ignored'),
-								indicator: 'yellow'
-							});
-						}
-						let data = {
-							type: component_type
-						}
-						frappe.prompt({
-							label: __('Section'),
-							fieldname: 'section',
-							fieldtype: 'Select',
-							options: Object.keys(this.sections)
-						}, (values) => {
-							this.sections[section_name][values.section] = data;
-							this.reload_component(values.section);
-						});
-					} else {
-						frappe.throw(__('Please select the Component Type first'));
-					}
-				} else {
-					frappe.throw(__('Please select the Section first'));
-				}
-			}
-		});
-		controls['delete_component'] = this.qr.page.add_field({
-			label: __('Delete Component'),
-			fieldtype: 'Button',
-			fieldname: 'delete_component',
-			click: () => {
-				const component = this.controls['component'].get_input_value();
-				if (component) {
-					frappe.confirm(__('Are you sure you want to delete component') + ' ' + component + '?',
-					() => {this.delete(component, 'component')});
-				}
-			}
-		});
-		controls['save'] = this.qr.page.add_field({
-			label: __('Save & Run'),
-			fieldtype: 'Button',
-			fieldname: 'save',
-			click: () => {
-				this.save_report();
-			}
-		});
-		controls['show_detail'] = this.qr.page.add_field({
-			label: __('Show Detail'),
-			fieldtype: 'Check',
-			fieldname: 'show_detail',
-			default: 1
-		});
-		this.controls = controls;
-	}
-	show_help() {
-		const help = __('Your custom report is built from General Ledger Entries within the date range. You can add multiple sections to the report using the New Section button. Each component added to a section adds a subset of the data into the specified section. Beware of duplicated data rows. The Filtered Row component type saves the datatable column filters to specify the added data. The Section component type refers to the data in a previously defined section, but it cannot refer to its parent section. The Amount column is summed to give the section subtotal. Use the Show Detail box to see the data rows included in each section in the final report. Once finished, hit Save & Run. Report contributed by');
-		this.qr.$report_footer.append('<div class="col-md-12"><strong>' + __('Help') + `: </strong>${help}<a href="https://www.casesolved.co.uk"> Case Solved</a></div>`);
-	}
-}
-
-if (!window.taxdetail) {
-	window.taxdetail = new erpnext.TaxDetail();
-}
-
-function get_reports(cb) {
-	frappe.call({
-		method: 'erpnext.accounts.report.tax_detail.tax_detail.get_custom_reports',
-		freeze: true
-	}).then((r) => {
-		cb(r.message);
-	})
-}
-
-function new_report() {
-	const dialog = new frappe.ui.Dialog({
-		title: __('New Report'),
-		fields: [
-			{
-				fieldname: 'report_name',
-				label: __('Report Name'),
-				fieldtype: 'Data',
-				default: 'VAT Return'
-			}
-		],
-		primary_action_label: __('Create'),
-		primary_action: function new_report_pa(values) {
-			frappe.call({
-				method:'erpnext.accounts.report.tax_detail.tax_detail.save_custom_report',
-				args: {
-					reference_report: 'Tax Detail',
-					report_name: values.report_name,
-					data: {
-						columns: [],
-						sections: {},
-						show_detail: 1
-					}
-				},
-				freeze: true
-			}).then((r) => {
-				frappe.set_route('query-report', values.report_name);
-			});
-			dialog.hide();
-		}
-	});
-	dialog.show();
-}
-
-function load_report() {
-	get_reports(function load_report_cb(reports) {
-		const dialog = new frappe.ui.Dialog({
-			title: __('Load Report'),
-			fields: [
-				{
-					fieldname: 'report_name',
-					label: __('Report Name'),
-					fieldtype: 'Select',
-					options: Object.keys(reports)
-				}
-			],
-			primary_action_label: __('Load'),
-			primary_action: function load_report_pa(values) {
-				dialog.hide();
-				frappe.set_route('query-report', values.report_name);
-			}
-		});
-		dialog.show();
-	});
-}
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.json b/erpnext/accounts/report/tax_detail/tax_detail.json
deleted file mode 100644
index d52ffd0..0000000
--- a/erpnext/accounts/report/tax_detail/tax_detail.json
+++ /dev/null
@@ -1,32 +0,0 @@
-{
- "add_total_row": 0,
- "columns": [],
- "creation": "2021-02-19 16:44:21.175113",
- "disable_prepared_report": 0,
- "disabled": 0,
- "docstatus": 0,
- "doctype": "Report",
- "filters": [],
- "idx": 0,
- "is_standard": "Yes",
- "modified": "2021-02-19 16:44:21.175113",
- "modified_by": "Administrator",
- "module": "Accounts",
- "name": "Tax Detail",
- "owner": "Administrator",
- "prepared_report": 0,
- "ref_doctype": "GL Entry",
- "report_name": "Tax Detail",
- "report_type": "Script Report",
- "roles": [
-  {
-   "role": "Accounts User"
-  },
-  {
-   "role": "Accounts Manager"
-  },
-  {
-   "role": "Auditor"
-  }
- ]
-}
\ No newline at end of file
diff --git a/erpnext/accounts/report/tax_detail/tax_detail.py b/erpnext/accounts/report/tax_detail/tax_detail.py
deleted file mode 100644
index ba733c2..0000000
--- a/erpnext/accounts/report/tax_detail/tax_detail.py
+++ /dev/null
@@ -1,325 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
-# For license information, please see license.txt
-# Contributed by Case Solved and sponsored by Nulight Studios
-
-
-import json
-
-import frappe
-from frappe import _
-
-# NOTE: Payroll is implemented using Journal Entries which are included as GL Entries
-
-# field lists in multiple doctypes will be coalesced
-required_sql_fields = {
-	("GL Entry", 1): ["posting_date"],
-	("Account",): ["root_type", "account_type"],
-	("GL Entry", 2): ["account", "voucher_type", "voucher_no", "debit", "credit"],
-	("Purchase Invoice Item", "Sales Invoice Item"): [
-		"base_net_amount",
-		"item_tax_rate",
-		"item_tax_template",
-		"item_group",
-		"item_name",
-	],
-	("Purchase Invoice", "Sales Invoice"): ["taxes_and_charges", "tax_category"],
-}
-
-
-def execute(filters=None):
-	if not filters:
-		return [], []
-
-	fieldlist = required_sql_fields
-	fieldstr = get_fieldstr(fieldlist)
-
-	gl_entries = frappe.db.sql(
-		"""
-		select {fieldstr}
-		from `tabGL Entry` ge
-		inner join `tabAccount` a on
-			ge.account=a.name and ge.company=a.company
-		left join `tabSales Invoice` si on
-			ge.company=si.company and ge.voucher_type='Sales Invoice' and ge.voucher_no=si.name
-		left join `tabSales Invoice Item` sii on
-			a.root_type='Income' and si.name=sii.parent
-		left join `tabPurchase Invoice` pi on
-			ge.company=pi.company and ge.voucher_type='Purchase Invoice' and ge.voucher_no=pi.name
-		left join `tabPurchase Invoice Item` pii on
-			a.root_type='Expense' and pi.name=pii.parent
-		where
-			ge.company=%(company)s and
-			ge.posting_date>=%(from_date)s and
-			ge.posting_date<=%(to_date)s
-		order by ge.posting_date, ge.voucher_no
-		""".format(
-			fieldstr=fieldstr
-		),
-		filters,
-		as_dict=1,
-	)
-
-	report_data = modify_report_data(gl_entries)
-	summary = None
-	if filters["mode"] == "run" and filters["report_name"] != "Tax Detail":
-		report_data, summary = run_report(filters["report_name"], report_data)
-
-	# return columns, data, message, chart, report_summary
-	return get_columns(fieldlist), report_data, None, None, summary
-
-
-def run_report(report_name, data):
-	"Applies the sections and filters saved in the custom report"
-	report_config = json.loads(frappe.get_doc("Report", report_name).json)
-	# Columns indexed from 1 wrt colno
-	columns = report_config.get("columns")
-	sections = report_config.get("sections", {})
-	show_detail = report_config.get("show_detail", 1)
-	report = {}
-	new_data = []
-	summary = []
-	for section_name, section in sections.items():
-		report[section_name] = {"rows": [], "subtotal": 0.0}
-		for component_name, component in section.items():
-			if component["type"] == "filter":
-				for row in data:
-					matched = True
-					for colno, filter_string in component["filters"].items():
-						filter_field = columns[int(colno) - 1]["fieldname"]
-						if not filter_match(row[filter_field], filter_string):
-							matched = False
-							break
-					if matched:
-						report[section_name]["rows"] += [row]
-						report[section_name]["subtotal"] += row["amount"]
-			if component["type"] == "section":
-				if component_name == section_name:
-					frappe.throw(_("A report component cannot refer to its parent section") + ": " + section_name)
-				try:
-					report[section_name]["rows"] += report[component_name]["rows"]
-					report[section_name]["subtotal"] += report[component_name]["subtotal"]
-				except KeyError:
-					frappe.throw(
-						_("A report component can only refer to an earlier section") + ": " + section_name
-					)
-
-		if show_detail:
-			new_data += report[section_name]["rows"]
-		new_data += [{"voucher_no": section_name, "amount": report[section_name]["subtotal"]}]
-		summary += [
-			{"label": section_name, "datatype": "Currency", "value": report[section_name]["subtotal"]}
-		]
-		if show_detail:
-			new_data += [{}]
-	return new_data or data, summary or None
-
-
-def filter_match(value, string):
-	"Approximation to datatable filters"
-	import datetime
-
-	if string == "":
-		return True
-	if value is None:
-		value = -999999999999999
-	elif isinstance(value, datetime.date):
-		return True
-
-	if isinstance(value, str):
-		value = value.lower()
-		string = string.lower()
-		if string[0] == "<":
-			return True if string[1:].strip() else False
-		elif string[0] == ">":
-			return False if string[1:].strip() else True
-		elif string[0] == "=":
-			return string[1:] in value if string[1:] else False
-		elif string[0:2] == "!=":
-			return string[2:] not in value
-		elif len(string.split(":")) == 2:
-			pre, post = string.split(":")
-			return True if not pre.strip() and post.strip() in value else False
-		else:
-			return string in value
-	else:
-		if string[0] in ["<", ">", "="]:
-			operator = string[0]
-			if operator == "=":
-				operator = "=="
-			string = string[1:].strip()
-		elif string[0:2] == "!=":
-			operator = "!="
-			string = string[2:].strip()
-		elif len(string.split(":")) == 2:
-			pre, post = string.split(":")
-			try:
-				return True if float(pre) <= value and float(post) >= value else False
-			except ValueError:
-				return False if pre.strip() else True
-		else:
-			return string in str(value)
-
-	try:
-		num = float(string) if string.strip() else 0
-		return frappe.safe_eval(f"{value} {operator} {num}")
-	except ValueError:
-		if operator == "<":
-			return True
-		return False
-
-
-def abbrev(dt):
-	return "".join(l[0].lower() for l in dt.split(" ")) + "."
-
-
-def doclist(dt, dfs):
-	return [abbrev(dt) + f for f in dfs]
-
-
-def as_split(fields):
-	for field in fields:
-		split = field.split(" as ")
-		yield (split[0], split[1] if len(split) > 1 else split[0])
-
-
-def coalesce(doctypes, fields):
-	coalesce = []
-	for name, new_name in as_split(fields):
-		sharedfields = ", ".join(abbrev(dt) + name for dt in doctypes)
-		coalesce += [f"coalesce({sharedfields}) as {new_name}"]
-	return coalesce
-
-
-def get_fieldstr(fieldlist):
-	fields = []
-	for doctypes, docfields in fieldlist.items():
-		if len(doctypes) == 1 or isinstance(doctypes[1], int):
-			fields += doclist(doctypes[0], docfields)
-		else:
-			fields += coalesce(doctypes, docfields)
-	return ", ".join(fields)
-
-
-def get_columns(fieldlist):
-	columns = {}
-	for doctypes, docfields in fieldlist.items():
-		fieldmap = {name: new_name for name, new_name in as_split(docfields)}
-		for doctype in doctypes:
-			if isinstance(doctype, int):
-				break
-			meta = frappe.get_meta(doctype)
-			# get column field metadata from the db
-			fieldmeta = {}
-			for field in meta.get("fields"):
-				if field.fieldname in fieldmap.keys():
-					new_name = fieldmap[field.fieldname]
-					fieldmeta[new_name] = {
-						"label": _(field.label),
-						"fieldname": new_name,
-						"fieldtype": field.fieldtype,
-						"options": field.options,
-					}
-			# edit the columns to match the modified data
-			for field in fieldmap.values():
-				col = modify_report_columns(doctype, field, fieldmeta[field])
-				if col:
-					columns[col["fieldname"]] = col
-	# use of a dict ensures duplicate columns are removed
-	return list(columns.values())
-
-
-def modify_report_columns(doctype, field, column):
-	"Because data is rearranged into other columns"
-	if doctype in ["Sales Invoice Item", "Purchase Invoice Item"]:
-		if field in ["item_tax_rate", "base_net_amount"]:
-			return None
-
-	if doctype == "GL Entry":
-		if field in ["debit", "credit"]:
-			column.update({"label": _("Amount"), "fieldname": "amount"})
-		elif field == "voucher_type":
-			column.update({"fieldtype": "Data", "options": ""})
-
-	if field == "taxes_and_charges":
-		column.update({"label": _("Taxes and Charges Template")})
-	return column
-
-
-def modify_report_data(data):
-	import json
-
-	new_data = []
-	for line in data:
-		if line.debit:
-			line.amount = -line.debit
-		else:
-			line.amount = line.credit
-		# Remove Invoice GL Tax Entries and generate Tax entries from the invoice lines
-		if "Invoice" in line.voucher_type:
-			if line.account_type not in ("Tax", "Round Off"):
-				new_data += [line]
-				if line.item_tax_rate:
-					tax_rates = json.loads(line.item_tax_rate)
-					for account, rate in tax_rates.items():
-						tax_line = line.copy()
-						tax_line.account_type = "Tax"
-						tax_line.account = account
-						if line.voucher_type == "Sales Invoice":
-							line.amount = line.base_net_amount
-							tax_line.amount = line.base_net_amount * (rate / 100)
-						if line.voucher_type == "Purchase Invoice":
-							line.amount = -line.base_net_amount
-							tax_line.amount = -line.base_net_amount * (rate / 100)
-						new_data += [tax_line]
-		else:
-			new_data += [line]
-	return new_data
-
-
-# JS client utilities
-
-custom_report_dict = {
-	"ref_doctype": "GL Entry",
-	"report_type": "Custom Report",
-	"reference_report": "Tax Detail",
-}
-
-
-@frappe.whitelist()
-def get_custom_reports(name=None):
-	filters = custom_report_dict.copy()
-	if name:
-		filters["name"] = name
-	reports = frappe.get_list("Report", filters=filters, fields=["name", "json"], as_list=False)
-	reports_dict = {rep.pop("name"): rep for rep in reports}
-	# Prevent custom reports with the same name
-	reports_dict["Tax Detail"] = {"json": None}
-	return reports_dict
-
-
-@frappe.whitelist()
-def save_custom_report(reference_report, report_name, data):
-	if reference_report != "Tax Detail":
-		frappe.throw(_("The wrong report is referenced."))
-	if report_name == "Tax Detail":
-		frappe.throw(_("The parent report cannot be overwritten."))
-
-	doc = {
-		"doctype": "Report",
-		"report_name": report_name,
-		"is_standard": "No",
-		"module": "Accounts",
-		"json": data,
-	}
-	doc.update(custom_report_dict)
-
-	try:
-		newdoc = frappe.get_doc(doc)
-		newdoc.insert()
-		frappe.msgprint(_("Report created successfully"))
-	except frappe.exceptions.DuplicateEntryError:
-		dbdoc = frappe.get_doc("Report", report_name)
-		dbdoc.update(doc)
-		dbdoc.save()
-		frappe.msgprint(_("Report updated successfully"))
-	return report_name
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.json b/erpnext/accounts/report/tax_detail/test_tax_detail.json
deleted file mode 100644
index e490316..0000000
--- a/erpnext/accounts/report/tax_detail/test_tax_detail.json
+++ /dev/null
@@ -1,840 +0,0 @@
-[
- {
-  "account_manager": null,
-  "accounts": [],
-  "companies": [],
-  "credit_limits": [],
-  "customer_details": null,
-  "customer_group": "All Customer Groups",
-  "customer_name": "_Test Customer",
-  "customer_pos_id": null,
-  "customer_primary_address": null,
-  "customer_primary_contact": null,
-  "customer_type": "Company",
-  "default_bank_account": null,
-  "default_commission_rate": 0.0,
-  "default_currency": null,
-  "default_price_list": null,
-  "default_sales_partner": null,
-  "disabled": 0,
-  "dn_required": 0,
-  "docstatus": 0,
-  "doctype": "Customer",
-  "email_id": null,
-  "gender": null,
-  "image": null,
-  "industry": null,
-  "is_frozen": 0,
-  "is_internal_customer": 0,
-  "language": "en",
-  "lead_name": null,
-  "loyalty_program": null,
-  "loyalty_program_tier": null,
-  "market_segment": null,
-  "mobile_no": null,
-  "modified": "2021-02-15 05:18:03.624724",
-  "name": "_Test Customer",
-  "naming_series": "CUST-.YYYY.-",
-  "pan": null,
-  "parent": null,
-  "parentfield": null,
-  "parenttype": null,
-  "payment_terms": null,
-  "primary_address": null,
-  "represents_company": "",
-  "sales_team": [],
-  "salutation": null,
-  "so_required": 0,
-  "tax_category": null,
-  "tax_id": null,
-  "tax_withholding_category": null,
-  "territory": "All Territories",
-  "website": null
- },{
-  "accounts": [],
-  "allow_purchase_invoice_creation_without_purchase_order": 0,
-  "allow_purchase_invoice_creation_without_purchase_receipt": 0,
-  "companies": [],
-  "country": "United Kingdom",
-  "default_bank_account": null,
-  "default_currency": null,
-  "default_price_list": null,
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Supplier",
-  "hold_type": "",
-  "image": null,
-  "is_frozen": 0,
-  "is_internal_supplier": 0,
-  "is_transporter": 0,
-  "language": "en",
-  "modified": "2021-03-31 16:47:10.109316",
-  "name": "_Test Supplier",
-  "naming_series": "SUP-.YYYY.-",
-  "on_hold": 0,
-  "pan": null,
-  "parent": null,
-  "parentfield": null,
-  "parenttype": null,
-  "payment_terms": null,
-  "prevent_pos": 0,
-  "prevent_rfqs": 0,
-  "release_date": null,
-  "represents_company": null,
-  "supplier_details": null,
-  "supplier_group": "Raw Material",
-  "supplier_name": "_Test Supplier",
-  "supplier_type": "Company",
-  "tax_category": null,
-  "tax_id": null,
-  "tax_withholding_category": null,
-  "warn_pos": 0,
-  "warn_rfqs": 0,
-  "website": null
- },{
-  "account_currency": "GBP",
-  "account_name": "Debtors",
-  "account_number": "",
-  "account_type": "Receivable",
-  "balance_must_be": "",
-  "company": "_T",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Account",
-  "freeze_account": "No",
-  "include_in_gross": 0,
-  "inter_company_account": 0,
-  "is_group": 0,
-  "lft": 58,
-  "modified": "2021-03-26 04:44:19.955468",
-  "name": "Debtors - _T",
-  "old_parent": null,
-  "parent": null,
-  "parent_account": "Application of Funds (Assets) - _T",
-  "parentfield": null,
-  "parenttype": null,
-  "report_type": "Balance Sheet",
-  "rgt": 59,
-  "root_type": "Asset",
-  "tax_rate": 0.0
- },{
-  "account_currency": "GBP",
-  "account_name": "Sales",
-  "account_number": "",
-  "account_type": "Income Account",
-  "balance_must_be": "",
-  "company": "_T",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Account",
-  "freeze_account": "No",
-  "include_in_gross": 0,
-  "inter_company_account": 0,
-  "is_group": 0,
-  "lft": 291,
-  "modified": "2021-03-26 04:50:21.697703",
-  "name": "Sales - _T",
-  "old_parent": null,
-  "parent": null,
-  "parent_account": "Income - _T",
-  "parentfield": null,
-  "parenttype": null,
-  "report_type": "Profit and Loss",
-  "rgt": 292,
-  "root_type": "Income",
-  "tax_rate": 0.0
- },{
-  "account_currency": "GBP",
-  "account_name": "VAT on Sales",
-  "account_number": "",
-  "account_type": "Tax",
-  "balance_must_be": "",
-  "company": "_T",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Account",
-  "freeze_account": "No",
-  "include_in_gross": 0,
-  "inter_company_account": 0,
-  "is_group": 0,
-  "lft": 317,
-  "modified": "2021-03-26 04:50:21.697703",
-  "name": "VAT on Sales - _T",
-  "old_parent": null,
-  "parent": null,
-  "parent_account": "Source of Funds (Liabilities) - _T",
-  "parentfield": null,
-  "parenttype": null,
-  "report_type": "Balance Sheet",
-  "rgt": 318,
-  "root_type": "Liability",
-  "tax_rate": 0.0
- },{
-  "account_currency": "GBP",
-  "account_name": "Cost of Goods Sold",
-  "account_number": "",
-  "account_type": "Cost of Goods Sold",
-  "balance_must_be": "",
-  "company": "_T",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Account",
-  "freeze_account": "No",
-  "include_in_gross": 0,
-  "inter_company_account": 0,
-  "is_group": 0,
-  "lft": 171,
-  "modified": "2021-03-26 04:44:19.994857",
-  "name": "Cost of Goods Sold - _T",
-  "old_parent": null,
-  "parent": null,
-  "parent_account": "Expenses - _T",
-  "parentfield": null,
-  "parenttype": null,
-  "report_type": "Profit and Loss",
-  "rgt": 172,
-  "root_type": "Expense",
-  "tax_rate": 0.0
- },{
-  "account_currency": "GBP",
-  "account_name": "VAT on Purchases",
-  "account_number": "",
-  "account_type": "Tax",
-  "balance_must_be": "",
-  "company": "_T",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Account",
-  "freeze_account": "No",
-  "include_in_gross": 0,
-  "inter_company_account": 0,
-  "is_group": 0,
-  "lft": 80,
-  "modified": "2021-03-26 04:44:19.961983",
-  "name": "VAT on Purchases - _T",
-  "old_parent": null,
-  "parent": null,
-  "parent_account": "Application of Funds (Assets) - _T",
-  "parentfield": null,
-  "parenttype": null,
-  "report_type": "Balance Sheet",
-  "rgt": 81,
-  "root_type": "Asset",
-  "tax_rate": 0.0
- },{
-  "account_currency": "GBP",
-  "account_name": "Creditors",
-  "account_number": "",
-  "account_type": "Payable",
-  "balance_must_be": "",
-  "company": "_T",
-  "disabled": 0,
-  "docstatus": 0,
-  "doctype": "Account",
-  "freeze_account": "No",
-  "include_in_gross": 0,
-  "inter_company_account": 0,
-  "is_group": 0,
-  "lft": 302,
-  "modified": "2021-03-26 04:50:21.697703",
-  "name": "Creditors - _T",
-  "old_parent": null,
-  "parent": null,
-  "parent_account": "Source of Funds (Liabilities) - _T",
-  "parentfield": null,
-  "parenttype": null,
-  "report_type": "Balance Sheet",
-  "rgt": 303,
-  "root_type": "Liability",
-  "tax_rate": 0.0
- },{
-  "additional_discount_percentage": 0.0,
-  "address_display": null,
-  "adjust_advance_taxes": 0,
-  "advances": [],
-  "against_expense_account": "Cost of Goods Sold - _T",
-  "allocate_advances_automatically": 0,
-  "amended_from": null,
-  "apply_discount_on": "Grand Total",
-  "apply_tds": 0,
-  "auto_repeat": null,
-  "base_discount_amount": 0.0,
-  "base_grand_total": 511.68,
-  "base_in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
-  "base_net_total": 426.4,
-  "base_paid_amount": 0.0,
-  "base_rounded_total": 511.68,
-  "base_rounding_adjustment": 0.0,
-  "base_taxes_and_charges_added": 85.28,
-  "base_taxes_and_charges_deducted": 0.0,
-  "base_total": 426.4,
-  "base_total_taxes_and_charges": 85.28,
-  "base_write_off_amount": 0.0,
-  "bill_date": null,
-  "bill_no": null,
-  "billing_address": null,
-  "billing_address_display": null,
-  "buying_price_list": "Standard Buying",
-  "cash_bank_account": null,
-  "clearance_date": null,
-  "company": "_T",
-  "contact_display": null,
-  "contact_email": null,
-  "contact_mobile": null,
-  "contact_person": null,
-  "conversion_rate": 1.0,
-  "cost_center": null,
-  "credit_to": "Creditors - _T",
-  "currency": "GBP",
-  "disable_rounded_total": 0,
-  "discount_amount": 0.0,
-  "docstatus": 0,
-  "doctype": "Purchase Invoice",
-  "due_date": null,
-  "from_date": null,
-  "grand_total": 511.68,
-  "group_same_items": 0,
-  "hold_comment": null,
-  "ignore_pricing_rule": 0,
-  "in_words": "GBP Five Hundred And Eleven and Sixty Eight Pence only.",
-  "inter_company_invoice_reference": null,
-  "is_internal_supplier": 0,
-  "is_opening": "No",
-  "is_paid": 0,
-  "is_return": 0,
-  "is_subcontracted": 0,
-  "items": [
-   {
-    "allow_zero_valuation_rate": 0,
-    "amount": 426.4,
-    "asset_category": null,
-    "asset_location": null,
-    "base_amount": 426.4,
-    "base_net_amount": 426.4,
-    "base_net_rate": 5.33,
-    "base_price_list_rate": 5.33,
-    "base_rate": 5.33,
-    "base_rate_with_margin": 0.0,
-    "batch_no": null,
-    "bom": null,
-    "brand": null,
-    "conversion_factor": 0.0,
-    "cost_center": "Main - _T",
-    "deferred_expense_account": null,
-    "description": "<div class=\"ql-editor read-mode\"><p>Fluid to make widgets</p></div>",
-    "discount_amount": 0.0,
-    "discount_percentage": 0.0,
-    "enable_deferred_expense": 0,
-    "expense_account": "Cost of Goods Sold - _T",
-    "from_warehouse": null,
-    "image": null,
-    "include_exploded_items": 0,
-    "is_fixed_asset": 0,
-    "is_free_item": 0,
-    "item_code": null,
-    "item_group": null,
-    "item_name": "Widget Fluid 1Litre",
-    "item_tax_amount": 0.0,
-    "item_tax_rate": "{\"VAT on Purchases - _T\": 20.0}",
-    "item_tax_template": null,
-    "landed_cost_voucher_amount": 0.0,
-    "manufacturer": null,
-    "manufacturer_part_no": null,
-    "margin_rate_or_amount": 0.0,
-    "margin_type": "",
-    "net_amount": 426.4,
-    "net_rate": 5.33,
-    "page_break": 0,
-    "parent": null,
-    "parentfield": "items",
-    "parenttype": "Purchase Invoice",
-    "po_detail": null,
-    "pr_detail": null,
-    "price_list_rate": 5.33,
-    "pricing_rules": null,
-    "project": null,
-    "purchase_invoice_item": null,
-    "purchase_order": null,
-    "purchase_receipt": null,
-    "qty": 80.0,
-    "quality_inspection": null,
-    "rate": 5.33,
-    "rate_with_margin": 0.0,
-    "received_qty": 0.0,
-    "rejected_qty": 0.0,
-    "rejected_serial_no": null,
-    "rejected_warehouse": null,
-    "rm_supp_cost": 0.0,
-    "sales_invoice_item": null,
-    "serial_no": null,
-    "service_end_date": null,
-    "service_start_date": null,
-    "service_stop_date": null,
-    "stock_qty": 0.0,
-    "stock_uom": "Nos",
-    "stock_uom_rate": 0.0,
-    "total_weight": 0.0,
-    "uom": "Nos",
-    "valuation_rate": 0.0,
-    "warehouse": null,
-    "weight_per_unit": 0.0,
-    "weight_uom": null
-   }
-  ],
-  "language": "en",
-  "letter_head": null,
-  "mode_of_payment": null,
-  "modified": "2021-04-03 03:33:09.180453",
-  "name": null,
-  "naming_series": "ACC-PINV-.YYYY.-",
-  "net_total": 426.4,
-  "on_hold": 0,
-  "other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Purchases</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Widget Fluid 1Litre</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 426.40\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 85.28\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
-  "outstanding_amount": 511.68,
-  "paid_amount": 0.0,
-  "parent": null,
-  "parentfield": null,
-  "parenttype": null,
-  "party_account_currency": "GBP",
-  "payment_schedule": [],
-  "payment_terms_template": null,
-  "plc_conversion_rate": 1.0,
-  "posting_date": null,
-  "posting_time": "16:59:56.789522",
-  "price_list_currency": "GBP",
-  "pricing_rules": [],
-  "project": null,
-  "rejected_warehouse": null,
-  "release_date": null,
-  "remarks": "No Remarks",
-  "represents_company": null,
-  "return_against": null,
-  "rounded_total": 511.68,
-  "rounding_adjustment": 0.0,
-  "scan_barcode": null,
-  "select_print_heading": null,
-  "set_from_warehouse": null,
-  "set_posting_time": 0,
-  "set_warehouse": null,
-  "shipping_address": null,
-  "shipping_address_display": "",
-  "shipping_rule": null,
-  "status": "Unpaid",
-  "supplied_items": [],
-  "supplier": "_Test Supplier",
-  "supplier_address": null,
-  "supplier_name": "_Test Supplier",
-  "supplier_warehouse": "Stores - _T",
-  "tax_category": null,
-  "tax_id": null,
-  "tax_withholding_category": null,
-  "taxes": [
-   {
-    "account_head": "VAT on Purchases - _T",
-    "add_deduct_tax": "Add",
-    "base_tax_amount": 85.28,
-    "base_tax_amount_after_discount_amount": 85.28,
-    "base_total": 511.68,
-    "category": "Total",
-    "charge_type": "On Net Total",
-    "cost_center": "Main - _T",
-    "description": "VAT on Purchases",
-    "included_in_print_rate": 0,
-    "item_wise_tax_detail": "{\"Widget Fluid 1Litre\":[20.0,85.28]}",
-    "parent": null,
-    "parentfield": "taxes",
-    "parenttype": "Purchase Invoice",
-    "rate": 0.0,
-    "row_id": null,
-    "tax_amount": 85.28,
-    "tax_amount_after_discount_amount": 85.28,
-    "total": 511.68
-   }
-  ],
-  "taxes_and_charges": null,
-  "taxes_and_charges_added": 85.28,
-  "taxes_and_charges_deducted": 0.0,
-  "tc_name": null,
-  "terms": null,
-  "title": "_Purchase Invoice",
-  "to_date": null,
-  "total": 426.4,
-  "total_advance": 0.0,
-  "total_net_weight": 0.0,
-  "total_qty": 80.0,
-  "total_taxes_and_charges": 85.28,
-  "unrealized_profit_loss_account": null,
-  "update_stock": 0,
-  "write_off_account": null,
-  "write_off_amount": 0.0,
-  "write_off_cost_center": null
- },{
-  "account_for_change_amount": null,
-  "additional_discount_percentage": 0.0,
-  "address_display": null,
-  "advances": [],
-  "against_income_account": "Sales - _T",
-  "allocate_advances_automatically": 0,
-  "amended_from": null,
-  "apply_discount_on": "Grand Total",
-  "auto_repeat": null,
-  "base_change_amount": 0.0,
-  "base_discount_amount": 0.0,
-  "base_grand_total": 868.25,
-  "base_in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
-  "base_net_total": 825.0,
-  "base_paid_amount": 0.0,
-  "base_rounded_total": 868.25,
-  "base_rounding_adjustment": 0.0,
-  "base_total": 825.0,
-  "base_total_taxes_and_charges": 43.25,
-  "base_write_off_amount": 0.0,
-  "c_form_applicable": "No",
-  "c_form_no": null,
-  "campaign": null,
-  "cash_bank_account": null,
-  "change_amount": 0.0,
-  "commission_rate": 0.0,
-  "company": "_T",
-  "company_address": null,
-  "company_address_display": null,
-  "company_tax_id": null,
-  "contact_display": null,
-  "contact_email": null,
-  "contact_mobile": null,
-  "contact_person": null,
-  "conversion_rate": 1.0,
-  "cost_center": null,
-  "currency": "GBP",
-  "customer": "_Test Customer",
-  "customer_address": null,
-  "customer_group": "All Customer Groups",
-  "customer_name": "_Test Customer",
-  "debit_to": "Debtors - _T",
-  "discount_amount": 0.0,
-  "docstatus": 0,
-  "doctype": "Sales Invoice",
-  "due_date": null,
-  "from_date": null,
-  "grand_total": 868.25,
-  "group_same_items": 0,
-  "ignore_pricing_rule": 0,
-  "in_words": "GBP Eight Hundred And Sixty Eight and Twenty Five Pence only.",
-  "inter_company_invoice_reference": null,
-  "is_consolidated": 0,
-  "is_discounted": 0,
-  "is_internal_customer": 0,
-  "is_opening": "No",
-  "is_pos": 0,
-  "is_return": 0,
-  "items": [
-   {
-    "actual_batch_qty": 0.0,
-    "actual_qty": 0.0,
-    "allow_zero_valuation_rate": 0,
-    "amount": 200.0,
-    "asset": null,
-    "barcode": null,
-    "base_amount": 200.0,
-    "base_net_amount": 200.0,
-    "base_net_rate": 50.0,
-    "base_price_list_rate": 0.0,
-    "base_rate": 50.0,
-    "base_rate_with_margin": 0.0,
-    "batch_no": null,
-    "brand": null,
-    "conversion_factor": 1.0,
-    "cost_center": "Main - _T",
-    "customer_item_code": null,
-    "deferred_revenue_account": null,
-    "delivered_by_supplier": 0,
-    "delivered_qty": 0.0,
-    "delivery_note": null,
-    "description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
-    "discount_amount": 0.0,
-    "discount_percentage": 0.0,
-    "dn_detail": null,
-    "enable_deferred_revenue": 0,
-    "expense_account": null,
-    "finance_book": null,
-    "image": null,
-    "income_account": "Sales - _T",
-    "incoming_rate": 0.0,
-    "is_fixed_asset": 0,
-    "is_free_item": 0,
-    "item_code": null,
-    "item_group": null,
-    "item_name": "Dunlop tyres",
-    "item_tax_rate": "{\"VAT on Sales - _T\": 20.0}",
-    "item_tax_template": null,
-    "margin_rate_or_amount": 0.0,
-    "margin_type": "",
-    "net_amount": 200.0,
-    "net_rate": 50.0,
-    "page_break": 0,
-    "parent": null,
-    "parentfield": "items",
-    "parenttype": "Sales Invoice",
-    "price_list_rate": 0.0,
-    "pricing_rules": null,
-    "project": null,
-    "qty": 4.0,
-    "quality_inspection": null,
-    "rate": 50.0,
-    "rate_with_margin": 0.0,
-    "sales_invoice_item": null,
-    "sales_order": null,
-    "serial_no": null,
-    "service_end_date": null,
-    "service_start_date": null,
-    "service_stop_date": null,
-    "so_detail": null,
-    "stock_qty": 4.0,
-    "stock_uom": "Nos",
-    "stock_uom_rate": 50.0,
-    "target_warehouse": null,
-    "total_weight": 0.0,
-    "uom": "Nos",
-    "warehouse": null,
-    "weight_per_unit": 0.0,
-    "weight_uom": null
-   },
-   {
-    "actual_batch_qty": 0.0,
-    "actual_qty": 0.0,
-    "allow_zero_valuation_rate": 0,
-    "amount": 65.0,
-    "asset": null,
-    "barcode": null,
-    "base_amount": 65.0,
-    "base_net_amount": 65.0,
-    "base_net_rate": 65.0,
-    "base_price_list_rate": 0.0,
-    "base_rate": 65.0,
-    "base_rate_with_margin": 0.0,
-    "batch_no": null,
-    "brand": null,
-    "conversion_factor": 1.0,
-    "cost_center": "Main - _T",
-    "customer_item_code": null,
-    "deferred_revenue_account": null,
-    "delivered_by_supplier": 0,
-    "delivered_qty": 0.0,
-    "delivery_note": null,
-    "description": "<div class=\"ql-editor read-mode\"><p>Used</p></div>",
-    "discount_amount": 0.0,
-    "discount_percentage": 0.0,
-    "dn_detail": null,
-    "enable_deferred_revenue": 0,
-    "expense_account": null,
-    "finance_book": null,
-    "image": null,
-    "income_account": "Sales - _T",
-    "incoming_rate": 0.0,
-    "is_fixed_asset": 0,
-    "is_free_item": 0,
-    "item_code": "",
-    "item_group": null,
-    "item_name": "Continental tyres",
-    "item_tax_rate": "{\"VAT on Sales - _T\": 5.0}",
-    "item_tax_template": null,
-    "margin_rate_or_amount": 0.0,
-    "margin_type": "",
-    "net_amount": 65.0,
-    "net_rate": 65.0,
-    "page_break": 0,
-    "parent": null,
-    "parentfield": "items",
-    "parenttype": "Sales Invoice",
-    "price_list_rate": 0.0,
-    "pricing_rules": null,
-    "project": null,
-    "qty": 1.0,
-    "quality_inspection": null,
-    "rate": 65.0,
-    "rate_with_margin": 0.0,
-    "sales_invoice_item": null,
-    "sales_order": null,
-    "serial_no": null,
-    "service_end_date": null,
-    "service_start_date": null,
-    "service_stop_date": null,
-    "so_detail": null,
-    "stock_qty": 1.0,
-    "stock_uom": null,
-    "stock_uom_rate": 65.0,
-    "target_warehouse": null,
-    "total_weight": 0.0,
-    "uom": "Nos",
-    "warehouse": null,
-    "weight_per_unit": 0.0,
-    "weight_uom": null
-   },
-   {
-    "actual_batch_qty": 0.0,
-    "actual_qty": 0.0,
-    "allow_zero_valuation_rate": 0,
-    "amount": 560.0,
-    "asset": null,
-    "barcode": null,
-    "base_amount": 560.0,
-    "base_net_amount": 560.0,
-    "base_net_rate": 70.0,
-    "base_price_list_rate": 0.0,
-    "base_rate": 70.0,
-    "base_rate_with_margin": 0.0,
-    "batch_no": null,
-    "brand": null,
-    "conversion_factor": 1.0,
-    "cost_center": "Main - _T",
-    "customer_item_code": null,
-    "deferred_revenue_account": null,
-    "delivered_by_supplier": 0,
-    "delivered_qty": 0.0,
-    "delivery_note": null,
-    "description": "<div class=\"ql-editor read-mode\"><p>New</p></div>",
-    "discount_amount": 0.0,
-    "discount_percentage": 0.0,
-    "dn_detail": null,
-    "enable_deferred_revenue": 0,
-    "expense_account": null,
-    "finance_book": null,
-    "image": null,
-    "income_account": "Sales - _T",
-    "incoming_rate": 0.0,
-    "is_fixed_asset": 0,
-    "is_free_item": 0,
-    "item_code": null,
-    "item_group": null,
-    "item_name": "Toyo tyres",
-    "item_tax_rate": "{\"VAT on Sales - _T\": 0.0}",
-    "item_tax_template": null,
-    "margin_rate_or_amount": 0.0,
-    "margin_type": "",
-    "net_amount": 560.0,
-    "net_rate": 70.0,
-    "page_break": 0,
-    "parent": null,
-    "parentfield": "items",
-    "parenttype": "Sales Invoice",
-    "price_list_rate": 0.0,
-    "pricing_rules": null,
-    "project": null,
-    "qty": 8.0,
-    "quality_inspection": null,
-    "rate": 70.0,
-    "rate_with_margin": 0.0,
-    "sales_invoice_item": null,
-    "sales_order": null,
-    "serial_no": null,
-    "service_end_date": null,
-    "service_start_date": null,
-    "service_stop_date": null,
-    "so_detail": null,
-    "stock_qty": 8.0,
-    "stock_uom": null,
-    "stock_uom_rate": 70.0,
-    "target_warehouse": null,
-    "total_weight": 0.0,
-    "uom": "Nos",
-    "warehouse": null,
-    "weight_per_unit": 0.0,
-    "weight_uom": null
-   }
-  ],
-  "language": "en",
-  "letter_head": null,
-  "loyalty_amount": 0.0,
-  "loyalty_points": 0,
-  "loyalty_program": null,
-  "loyalty_redemption_account": null,
-  "loyalty_redemption_cost_center": null,
-  "modified": "2021-02-16 05:18:59.755144",
-  "name": null,
-  "naming_series": "ACC-SINV-.YYYY.-",
-  "net_total": 825.0,
-  "other_charges_calculation": "<div class=\"tax-break-up\" style=\"overflow-x: auto;\">\n\t<table class=\"table table-bordered table-hover\">\n\t\t<thead>\n\t\t\t<tr>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-left\">Item</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">Taxable Amount</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t<th class=\"text-right\">VAT on Sales</th>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t</tr>\n\t\t</thead>\n\t\t<tbody>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Dunlop tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 200.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(20.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 40.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Continental tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 65.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(5.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 3.25\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t\t\t<tr>\n\t\t\t\t\t<td>Toyo tyres</td>\n\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t\u00a3 560.00\n\t\t\t\t\t\t\n\t\t\t\t\t</td>\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t<td class='text-right'>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t(0.0%)\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\u00a3 0.00\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t</tr>\n\t\t\t\n\t\t</tbody>\n\t</table>\n</div>",
-  "outstanding_amount": 868.25,
-  "packed_items": [],
-  "paid_amount": 0.0,
-  "parent": null,
-  "parentfield": null,
-  "parenttype": null,
-  "party_account_currency": "GBP",
-  "payment_schedule": [],
-  "payment_terms_template": null,
-  "payments": [],
-  "plc_conversion_rate": 1.0,
-  "po_date": null,
-  "po_no": "",
-  "pos_profile": null,
-  "posting_date": null,
-  "posting_time": "5:19:02.994077",
-  "price_list_currency": "GBP",
-  "pricing_rules": [],
-  "project": null,
-  "redeem_loyalty_points": 0,
-  "remarks": "No Remarks",
-  "represents_company": "",
-  "return_against": null,
-  "rounded_total": 868.25,
-  "rounding_adjustment": 0.0,
-  "sales_partner": null,
-  "sales_team": [],
-  "scan_barcode": null,
-  "select_print_heading": null,
-  "selling_price_list": "Standard Selling",
-  "set_posting_time": 0,
-  "set_target_warehouse": null,
-  "set_warehouse": null,
-  "shipping_address": null,
-  "shipping_address_name": "",
-  "shipping_rule": null,
-  "source": null,
-  "status": "Overdue",
-  "tax_category": "",
-  "tax_id": null,
-  "taxes": [
-   {
-    "account_head": "VAT on Sales - _T",
-    "base_tax_amount": 43.25,
-    "base_tax_amount_after_discount_amount": 43.25,
-    "base_total": 868.25,
-    "charge_type": "On Net Total",
-    "cost_center": "Main - _T",
-    "description": "VAT on Sales",
-    "included_in_print_rate": 0,
-    "item_wise_tax_detail": "{\"Dunlop tyres\":[20.0,40.0],\"Continental tyres\":[5.0,3.25],\"Toyo tyres\":[0.0,0.0]}",
-    "parent": null,
-    "parentfield": "taxes",
-    "parenttype": "Sales Invoice",
-    "rate": 0.0,
-    "row_id": null,
-    "tax_amount": 43.25,
-    "tax_amount_after_discount_amount": 43.25,
-    "total": 868.25
-   }
-  ],
-  "taxes_and_charges": null,
-  "tc_name": null,
-  "terms": null,
-  "territory": "All Territories",
-  "timesheets": [],
-  "title": "_Sales Invoice",
-  "to_date": null,
-  "total": 825.0,
-  "total_advance": 0.0,
-  "total_billing_amount": 0.0,
-  "total_commission": 0.0,
-  "total_net_weight": 0.0,
-  "total_qty": 13.0,
-  "total_taxes_and_charges": 43.25,
-  "unrealized_profit_loss_account": null,
-  "update_billed_amount_in_sales_order": 0,
-  "update_stock": 0,
-  "write_off_account": null,
-  "write_off_amount": 0.0,
-  "write_off_cost_center": null,
-  "write_off_outstanding_amount_automatically": 0
- }
-]
diff --git a/erpnext/accounts/report/tax_detail/test_tax_detail.py b/erpnext/accounts/report/tax_detail/test_tax_detail.py
deleted file mode 100644
index 869f245..0000000
--- a/erpnext/accounts/report/tax_detail/test_tax_detail.py
+++ /dev/null
@@ -1,213 +0,0 @@
-import datetime
-import json
-import os
-import unittest
-
-import frappe
-from frappe.utils import (
-	add_to_date,
-	get_first_day,
-	get_last_day,
-	get_year_ending,
-	get_year_start,
-	getdate,
-)
-
-from .tax_detail import filter_match, save_custom_report
-
-
-class TestTaxDetail(unittest.TestCase):
-	def load_testdocs(self):
-		from erpnext.accounts.utils import FiscalYearError, get_fiscal_year
-
-		datapath, _ = os.path.splitext(os.path.realpath(__file__))
-		with open(datapath + ".json", "r") as fp:
-			docs = json.load(fp)
-
-		now = getdate()
-		self.from_date = get_first_day(now)
-		self.to_date = get_last_day(now)
-
-		try:
-			get_fiscal_year(now, company="_T")
-		except FiscalYearError:
-			docs = [
-				{
-					"companies": [
-						{
-							"company": "_T",
-							"parent": "_Test Fiscal",
-							"parentfield": "companies",
-							"parenttype": "Fiscal Year",
-						}
-					],
-					"doctype": "Fiscal Year",
-					"year": "_Test Fiscal",
-					"year_end_date": get_year_ending(now),
-					"year_start_date": get_year_start(now),
-				}
-			] + docs
-
-		docs = [
-			{
-				"abbr": "_T",
-				"company_name": "_T",
-				"country": "United Kingdom",
-				"default_currency": "GBP",
-				"doctype": "Company",
-				"name": "_T",
-			}
-		] + docs
-
-		for doc in docs:
-			try:
-				db_doc = frappe.get_doc(doc)
-				if "Invoice" in db_doc.doctype:
-					db_doc.due_date = add_to_date(now, days=1)
-					db_doc.insert()
-					# Create GL Entries:
-					db_doc.submit()
-				else:
-					db_doc.insert(ignore_if_duplicate=True)
-			except frappe.exceptions.DuplicateEntryError:
-				pass
-
-	def load_defcols(self):
-		self.company = frappe.get_doc("Company", "_T")
-		custom_report = frappe.get_doc("Report", "Tax Detail")
-		self.default_columns, _ = custom_report.run_query_report(
-			filters={
-				"from_date": "2021-03-01",
-				"to_date": "2021-03-31",
-				"company": self.company.name,
-				"mode": "run",
-				"report_name": "Tax Detail",
-			},
-			user=frappe.session.user,
-		)
-
-	def rm_testdocs(self):
-		"Remove the Company and all data"
-		from erpnext.setup.doctype.company.company import create_transaction_deletion_request
-
-		create_transaction_deletion_request(self.company.name)
-
-	def test_report(self):
-		self.load_testdocs()
-		self.load_defcols()
-		report_name = save_custom_report(
-			"Tax Detail",
-			"_Test Tax Detail",
-			json.dumps(
-				{
-					"columns": self.default_columns,
-					"sections": {
-						"Box1": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Sales"}}},
-						"Box2": {"Filter0": {"type": "filter", "filters": {"4": "Acquisition"}}},
-						"Box3": {"Box1": {"type": "section"}, "Box2": {"type": "section"}},
-						"Box4": {"Filter0": {"type": "filter", "filters": {"4": "VAT on Purchases"}}},
-						"Box5": {"Box3": {"type": "section"}, "Box4": {"type": "section"}},
-						"Box6": {"Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales"}}},
-						"Box7": {"Filter0": {"type": "filter", "filters": {"2": "Expense", "3": "!=Tax"}}},
-						"Box8": {"Filter0": {"type": "filter", "filters": {"3": "!=Tax", "4": "Sales", "12": "EU"}}},
-						"Box9": {
-							"Filter0": {"type": "filter", "filters": {"2": "Expense", "3": "!=Tax", "12": "EU"}}
-						},
-					},
-					"show_detail": 1,
-				}
-			),
-		)
-		data = frappe.desk.query_report.run(
-			report_name,
-			filters={
-				"from_date": self.from_date,
-				"to_date": self.to_date,
-				"company": self.company.name,
-				"mode": "run",
-				"report_name": report_name,
-			},
-			user=frappe.session.user,
-		)
-
-		self.assertListEqual(data.get("columns"), self.default_columns)
-		expected = (
-			("Box1", 43.25),
-			("Box2", 0.0),
-			("Box3", 43.25),
-			("Box4", -85.28),
-			("Box5", -42.03),
-			("Box6", 825.0),
-			("Box7", -426.40),
-			("Box8", 0.0),
-			("Box9", 0.0),
-		)
-		exrow = iter(expected)
-		for row in data.get("result"):
-			if row.get("voucher_no") and not row.get("posting_date"):
-				label, value = next(exrow)
-				self.assertDictEqual(row, {"voucher_no": label, "amount": value})
-		self.assertListEqual(
-			data.get("report_summary"),
-			[{"label": label, "datatype": "Currency", "value": value} for label, value in expected],
-		)
-
-		self.rm_testdocs()
-
-	def test_filter_match(self):
-		# None - treated as -inf number except range
-		self.assertTrue(filter_match(None, "!="))
-		self.assertTrue(filter_match(None, "<"))
-		self.assertTrue(filter_match(None, "<jjj"))
-		self.assertTrue(filter_match(None, "  :  "))
-		self.assertTrue(filter_match(None, ":56"))
-		self.assertTrue(filter_match(None, ":de"))
-		self.assertFalse(filter_match(None, "3.4"))
-		self.assertFalse(filter_match(None, "="))
-		self.assertFalse(filter_match(None, "=3.4"))
-		self.assertFalse(filter_match(None, ">3.4"))
-		self.assertFalse(filter_match(None, "   <"))
-		self.assertFalse(filter_match(None, "ew"))
-		self.assertFalse(filter_match(None, " "))
-		self.assertFalse(filter_match(None, " f :"))
-
-		# Numbers
-		self.assertTrue(filter_match(3.4, "3.4"))
-		self.assertTrue(filter_match(3.4, ".4"))
-		self.assertTrue(filter_match(3.4, "3"))
-		self.assertTrue(filter_match(-3.4, "< -3"))
-		self.assertTrue(filter_match(-3.4, "> -4"))
-		self.assertTrue(filter_match(3.4, "= 3.4 "))
-		self.assertTrue(filter_match(3.4, "!=4.5"))
-		self.assertTrue(filter_match(3.4, " 3 : 4 "))
-		self.assertTrue(filter_match(0.0, "  :  "))
-		self.assertFalse(filter_match(3.4, "=4.5"))
-		self.assertFalse(filter_match(3.4, " = 3.4 "))
-		self.assertFalse(filter_match(3.4, "!=3.4"))
-		self.assertFalse(filter_match(3.4, ">6"))
-		self.assertFalse(filter_match(3.4, "<-4.5"))
-		self.assertFalse(filter_match(3.4, "4.5"))
-		self.assertFalse(filter_match(3.4, "5:9"))
-
-		# Strings
-		self.assertTrue(filter_match("ACC-SINV-2021-00001", "SINV"))
-		self.assertTrue(filter_match("ACC-SINV-2021-00001", "sinv"))
-		self.assertTrue(filter_match("ACC-SINV-2021-00001", "-2021"))
-		self.assertTrue(filter_match(" ACC-SINV-2021-00001", " acc"))
-		self.assertTrue(filter_match("ACC-SINV-2021-00001", "=2021"))
-		self.assertTrue(filter_match("ACC-SINV-2021-00001", "!=zz"))
-		self.assertTrue(filter_match("ACC-SINV-2021-00001", "<   zzz  "))
-		self.assertTrue(filter_match("ACC-SINV-2021-00001", "  :  sinv  "))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", "  sinv  :"))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", " acc"))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", "= 2021 "))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", "!=sinv"))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", " >"))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", ">aa"))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", " <"))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", "<   "))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", " ="))
-		self.assertFalse(filter_match("ACC-SINV-2021-00001", "="))
-
-		# Date - always match
-		self.assertTrue(filter_match(datetime.date(2021, 3, 19), " kdsjkldfs "))
diff --git a/erpnext/accounts/report/trial_balance/trial_balance.py b/erpnext/accounts/report/trial_balance/trial_balance.py
index 3af01fd..53611ab 100644
--- a/erpnext/accounts/report/trial_balance/trial_balance.py
+++ b/erpnext/accounts/report/trial_balance/trial_balance.py
@@ -4,7 +4,8 @@
 
 import frappe
 from frappe import _
-from frappe.utils import cstr, flt, formatdate, getdate
+from frappe.query_builder.functions import Sum
+from frappe.utils import add_days, cstr, flt, formatdate, getdate
 
 import erpnext
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -78,7 +79,6 @@
 
 
 def get_data(filters):
-
 	accounts = frappe.db.sql(
 		"""select name, account_number, parent_account, account_name, root_type, report_type, lft, rgt
 
@@ -118,12 +118,10 @@
 		ignore_closing_entries=not flt(filters.with_period_closing_entry),
 	)
 
-	total_row = calculate_values(
-		accounts, gl_entries_by_account, opening_balances, filters, company_currency
-	)
+	calculate_values(accounts, gl_entries_by_account, opening_balances)
 	accumulate_values_into_parents(accounts, accounts_by_name)
 
-	data = prepare_data(accounts, filters, total_row, parent_children_map, company_currency)
+	data = prepare_data(accounts, filters, parent_children_map, company_currency)
 	data = filter_out_zero_value_rows(
 		data, parent_children_map, show_zero_values=filters.get("show_zero_values")
 	)
@@ -140,45 +138,125 @@
 
 
 def get_rootwise_opening_balances(filters, report_type):
-	additional_conditions = ""
-	if not filters.show_unclosed_fy_pl_balances:
-		additional_conditions = (
-			" and posting_date >= %(year_start_date)s" if report_type == "Profit and Loss" else ""
-		)
+	gle = []
 
-	if not flt(filters.with_period_closing_entry):
-		additional_conditions += " and ifnull(voucher_type, '')!='Period Closing Voucher'"
-
-	if filters.cost_center:
-		lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
-		additional_conditions += """ and cost_center in (select name from `tabCost Center`
-			where lft >= %s and rgt <= %s)""" % (
-			lft,
-			rgt,
-		)
-
-	if filters.project:
-		additional_conditions += " and project = %(project)s"
-
-	if filters.get("include_default_book_entries"):
-		additional_conditions += (
-			" AND (finance_book in (%(finance_book)s, %(company_fb)s, '') OR finance_book IS NULL)"
-		)
-	else:
-		additional_conditions += " AND (finance_book in (%(finance_book)s, '') OR finance_book IS NULL)"
+	last_period_closing_voucher = frappe.db.get_all(
+		"Period Closing Voucher",
+		filters={"docstatus": 1, "company": filters.company, "posting_date": ("<", filters.from_date)},
+		fields=["posting_date", "name"],
+		order_by="posting_date desc",
+		limit=1,
+	)
 
 	accounting_dimensions = get_accounting_dimensions(as_list=False)
 
-	query_filters = {
-		"company": filters.company,
-		"from_date": filters.from_date,
-		"to_date": filters.to_date,
-		"report_type": report_type,
-		"year_start_date": filters.year_start_date,
-		"project": filters.project,
-		"finance_book": filters.finance_book,
-		"company_fb": frappe.get_cached_value("Company", filters.company, "default_finance_book"),
-	}
+	if last_period_closing_voucher:
+		gle = get_opening_balance(
+			"Account Closing Balance",
+			filters,
+			report_type,
+			accounting_dimensions,
+			period_closing_voucher=last_period_closing_voucher[0].name,
+		)
+		if getdate(last_period_closing_voucher[0].posting_date) < getdate(
+			add_days(filters.from_date, -1)
+		):
+			start_date = add_days(last_period_closing_voucher[0].posting_date, 1)
+			gle += get_opening_balance(
+				"GL Entry", filters, report_type, accounting_dimensions, start_date=start_date
+			)
+	else:
+		gle = get_opening_balance("GL Entry", filters, report_type, accounting_dimensions)
+
+	opening = frappe._dict()
+	for d in gle:
+		opening.setdefault(
+			d.account,
+			{
+				"account": d.account,
+				"opening_debit": 0.0,
+				"opening_credit": 0.0,
+			},
+		)
+		opening[d.account]["opening_debit"] += flt(d.opening_debit)
+		opening[d.account]["opening_credit"] += flt(d.opening_credit)
+
+	return opening
+
+
+def get_opening_balance(
+	doctype, filters, report_type, accounting_dimensions, period_closing_voucher=None, start_date=None
+):
+	closing_balance = frappe.qb.DocType(doctype)
+	account = frappe.qb.DocType("Account")
+
+	opening_balance = (
+		frappe.qb.from_(closing_balance)
+		.select(
+			closing_balance.account,
+			Sum(closing_balance.debit).as_("opening_debit"),
+			Sum(closing_balance.credit).as_("opening_credit"),
+		)
+		.where(
+			(closing_balance.company == filters.company)
+			& (
+				closing_balance.account.isin(
+					frappe.qb.from_(account).select("name").where(account.report_type == report_type)
+				)
+			)
+		)
+		.groupby(closing_balance.account)
+	)
+
+	if period_closing_voucher:
+		opening_balance = opening_balance.where(
+			closing_balance.period_closing_voucher == period_closing_voucher
+		)
+	else:
+		if start_date:
+			opening_balance = opening_balance.where(closing_balance.posting_date >= start_date)
+			opening_balance = opening_balance.where(closing_balance.is_opening == "No")
+		opening_balance = opening_balance.where(closing_balance.posting_date < filters.from_date)
+
+	if (
+		not filters.show_unclosed_fy_pl_balances
+		and report_type == "Profit and Loss"
+		and doctype == "GL Entry"
+	):
+		opening_balance = opening_balance.where(closing_balance.posting_date >= filters.year_start_date)
+
+	if not flt(filters.with_period_closing_entry):
+		if doctype == "Account Closing Balance":
+			opening_balance = opening_balance.where(closing_balance.is_period_closing_voucher_entry == 0)
+		else:
+			opening_balance = opening_balance.where(
+				closing_balance.voucher_type != "Period Closing Voucher"
+			)
+
+	if filters.cost_center:
+		lft, rgt = frappe.db.get_value("Cost Center", filters.cost_center, ["lft", "rgt"])
+		cost_center = frappe.qb.DocType("Cost Center")
+		opening_balance = opening_balance.where(
+			closing_balance.cost_center.in_(
+				frappe.qb.from_(cost_center)
+				.select("name")
+				.where((cost_center.lft >= lft) & (cost_center.rgt <= rgt))
+			)
+		)
+
+	if filters.project:
+		opening_balance = opening_balance.where(closing_balance.project == filters.project)
+
+	if filters.get("include_default_book_entries"):
+		opening_balance = opening_balance.where(
+			(closing_balance.finance_book.isin([cstr(filters.finance_book), cstr(filters.company_fb), ""]))
+			| (closing_balance.finance_book.isnull())
+		)
+	else:
+		opening_balance = opening_balance.where(
+			(closing_balance.finance_book.isin([cstr(filters.finance_book), ""]))
+			| (closing_balance.finance_book.isnull())
+		)
 
 	if accounting_dimensions:
 		for dimension in accounting_dimensions:
@@ -187,38 +265,20 @@
 					filters[dimension.fieldname] = get_dimension_with_children(
 						dimension.document_type, filters.get(dimension.fieldname)
 					)
-					additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
+					opening_balance = opening_balance.where(
+						closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
+					)
 				else:
-					additional_conditions += " and {0} in %({0})s".format(dimension.fieldname)
+					opening_balance = opening_balance.where(
+						closing_balance[dimension.fieldname].isin(filters[dimension.fieldname])
+					)
 
-				query_filters.update({dimension.fieldname: filters.get(dimension.fieldname)})
+	gle = opening_balance.run(as_dict=1)
 
-	gle = frappe.db.sql(
-		"""
-		select
-			account, sum(debit) as opening_debit, sum(credit) as opening_credit
-		from `tabGL Entry`
-		where
-			company=%(company)s
-			{additional_conditions}
-			and (posting_date < %(from_date)s or (ifnull(is_opening, 'No') = 'Yes' and posting_date <= %(to_date)s))
-			and account in (select name from `tabAccount` where report_type=%(report_type)s)
-			and is_cancelled = 0
-		group by account""".format(
-			additional_conditions=additional_conditions
-		),
-		query_filters,
-		as_dict=True,
-	)
-
-	opening = frappe._dict()
-	for d in gle:
-		opening.setdefault(d.account, d)
-
-	return opening
+	return gle
 
 
-def calculate_values(accounts, gl_entries_by_account, opening_balances, filters, company_currency):
+def calculate_values(accounts, gl_entries_by_account, opening_balances):
 	init = {
 		"opening_debit": 0.0,
 		"opening_credit": 0.0,
@@ -228,22 +288,6 @@
 		"closing_credit": 0.0,
 	}
 
-	total_row = {
-		"account": "'" + _("Total") + "'",
-		"account_name": "'" + _("Total") + "'",
-		"warn_if_negative": True,
-		"opening_debit": 0.0,
-		"opening_credit": 0.0,
-		"debit": 0.0,
-		"credit": 0.0,
-		"closing_debit": 0.0,
-		"closing_credit": 0.0,
-		"parent_account": None,
-		"indent": 0,
-		"has_value": True,
-		"currency": company_currency,
-	}
-
 	for d in accounts:
 		d.update(init.copy())
 
@@ -261,8 +305,28 @@
 
 		prepare_opening_closing(d)
 
-		for field in value_fields:
-			total_row[field] += d[field]
+
+def calculate_total_row(accounts, company_currency):
+	total_row = {
+		"account": "'" + _("Total") + "'",
+		"account_name": "'" + _("Total") + "'",
+		"warn_if_negative": True,
+		"opening_debit": 0.0,
+		"opening_credit": 0.0,
+		"debit": 0.0,
+		"credit": 0.0,
+		"closing_debit": 0.0,
+		"closing_credit": 0.0,
+		"parent_account": None,
+		"indent": 0,
+		"has_value": True,
+		"currency": company_currency,
+	}
+
+	for d in accounts:
+		if not d.parent_account:
+			for field in value_fields:
+				total_row[field] += d[field]
 
 	return total_row
 
@@ -274,7 +338,7 @@
 				accounts_by_name[d.parent_account][key] += d[key]
 
 
-def prepare_data(accounts, filters, total_row, parent_children_map, company_currency):
+def prepare_data(accounts, filters, parent_children_map, company_currency):
 	data = []
 
 	for d in accounts:
@@ -305,6 +369,7 @@
 		row["has_value"] = has_value
 		data.append(row)
 
+	total_row = calculate_total_row(accounts, company_currency)
 	data.extend([{}, total_row])
 
 	return data
diff --git a/erpnext/accounts/test/test_reports.py b/erpnext/accounts/test/test_reports.py
index 3f06c30..609f74e 100644
--- a/erpnext/accounts/test/test_reports.py
+++ b/erpnext/accounts/test/test_reports.py
@@ -30,10 +30,6 @@
 	("Sales Register", {}),
 	("Sales Register", {"item_group": "All Item Groups"}),
 	("Purchase Register", {}),
-	(
-		"Tax Detail",
-		{"mode": "run", "report_name": "Tax Detail"},
-	),
 ]
 
 OPTIONAL_FILTERS = {}
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 2608c03..2ab9ef6 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -51,13 +51,25 @@
 
 @frappe.whitelist()
 def get_fiscal_year(
-	date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
+	date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False, boolean=False
 ):
-	return get_fiscal_years(date, fiscal_year, label, verbose, company, as_dict=as_dict)[0]
+	fiscal_years = get_fiscal_years(
+		date, fiscal_year, label, verbose, company, as_dict=as_dict, boolean=boolean
+	)
+	if boolean:
+		return fiscal_years
+	else:
+		return fiscal_years[0]
 
 
 def get_fiscal_years(
-	transaction_date=None, fiscal_year=None, label="Date", verbose=1, company=None, as_dict=False
+	transaction_date=None,
+	fiscal_year=None,
+	label="Date",
+	verbose=1,
+	company=None,
+	as_dict=False,
+	boolean=False,
 ):
 	fiscal_years = frappe.cache().hget("fiscal_years", company) or []
 
@@ -121,8 +133,12 @@
 	if company:
 		error_msg = _("""{0} for {1}""").format(error_msg, frappe.bold(company))
 
+	if boolean:
+		return False
+
 	if verbose == 1:
 		frappe.msgprint(error_msg)
+
 	raise FiscalYearError(error_msg)
 
 
@@ -451,12 +467,6 @@
 			else:
 				update_reference_in_payment_entry(entry, doc, do_not_save=True)
 
-		if doc.doctype == "Journal Entry":
-			try:
-				doc.validate_total_debit_and_credit()
-			except Exception as validation_exception:
-				raise frappe.ValidationError(_(f"Validation Error for {doc.name}")) from validation_exception
-
 		doc.save(ignore_permissions=True)
 		# re-submit advance entry
 		doc = frappe.get_doc(entry.voucher_type, entry.voucher_no)
diff --git a/erpnext/accounts/workspace/accounting/accounting.json b/erpnext/accounts/workspace/accounting/accounting.json
index b0c1124..595efcd 100644
--- a/erpnext/accounts/workspace/accounting/accounting.json
+++ b/erpnext/accounts/workspace/accounting/accounting.json
@@ -13,6 +13,7 @@
  "hide_custom": 0,
  "icon": "accounting",
  "idx": 0,
+ "is_hidden": 0,
  "label": "Accounting",
  "links": [
   {
@@ -497,17 +498,6 @@
    "dependencies": "GL Entry",
    "hidden": 0,
    "is_query_report": 1,
-   "label": "Tax Detail",
-   "link_count": 0,
-   "link_to": "Tax Detail",
-   "link_type": "Report",
-   "onboard": 0,
-   "type": "Link"
-  },
-  {
-   "dependencies": "GL Entry",
-   "hidden": 0,
-   "is_query_report": 1,
    "label": "UAE VAT 201",
    "link_count": 0,
    "link_to": "UAE VAT 201",
@@ -517,18 +507,6 @@
    "type": "Link"
   },
   {
-   "dependencies": "GL Entry",
-   "hidden": 0,
-   "is_query_report": 1,
-   "label": "KSA VAT Report",
-   "link_count": 0,
-   "link_to": "KSA VAT",
-   "link_type": "Report",
-   "onboard": 0,
-   "only_for": "Saudi Arabia",
-   "type": "Link"
-  },
-  {
    "hidden": 0,
    "is_query_report": 0,
    "label": "Financial Statements",
@@ -1032,17 +1010,6 @@
   {
    "hidden": 0,
    "is_query_report": 0,
-   "label": "KSA VAT Setting",
-   "link_count": 0,
-   "link_to": "KSA VAT Setting",
-   "link_type": "DocType",
-   "onboard": 0,
-   "only_for": "Saudi Arabia",
-   "type": "Link"
-  },
-  {
-   "hidden": 0,
-   "is_query_report": 0,
    "label": "Profitability",
    "link_count": 0,
    "onboard": 0,
@@ -1093,7 +1060,7 @@
    "type": "Link"
   }
  ],
- "modified": "2022-06-24 05:41:09.236458",
+ "modified": "2023-02-23 15:32:12.135355",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Accounting",
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 4951385..b9f16a7 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -466,6 +466,9 @@
 		} else {
 			frm.set_value('purchase_date', purchase_doc.posting_date);
 		}
+		if (!frm.doc.is_existing_asset && !frm.doc.available_for_use_date) {
+			frm.set_value('available_for_use_date', frm.doc.purchase_date);
+		}
 		const item = purchase_doc.items.find(item => item.item_code === frm.doc.item_code);
 		if (!item) {
 			doctype_field = frappe.scrub(doctype)
diff --git a/erpnext/assets/doctype/asset/asset.json b/erpnext/assets/doctype/asset/asset.json
index ea575fd..a1e8f33 100644
--- a/erpnext/assets/doctype/asset/asset.json
+++ b/erpnext/assets/doctype/asset/asset.json
@@ -79,6 +79,9 @@
    "options": "ACC-ASS-.YYYY.-"
   },
   {
+   "depends_on": "item_code",
+   "fetch_from": "item_code.item_name",
+   "fetch_if_empty": 1,
    "fieldname": "asset_name",
    "fieldtype": "Data",
    "in_list_view": 1,
@@ -517,7 +520,7 @@
    "table_fieldname": "accounts"
   }
  ],
- "modified": "2023-02-02 00:03:11.706427",
+ "modified": "2023-03-30 15:07:41.542374",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset",
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index e1d58a0..6001254 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -8,16 +8,12 @@
 import frappe
 from frappe import _
 from frappe.utils import (
-	add_months,
 	cint,
-	date_diff,
 	flt,
 	get_datetime,
 	get_last_day,
 	get_link_to_form,
 	getdate,
-	is_last_day_of_the_month,
-	month_diff,
 	nowdate,
 	today,
 )
@@ -239,30 +235,6 @@
 				self.get_depreciation_rate(d, on_validate=True), d.precision("rate_of_depreciation")
 			)
 
-	# if it returns True, depreciation_amount will not be equal for the first and last rows
-	def check_is_pro_rata(self, row):
-		has_pro_rata = False
-
-		# if not existing asset, from_date = available_for_use_date
-		# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
-		# from_date = 01/01/2022
-		from_date = self.get_modified_available_for_use_date(row)
-		days = date_diff(row.depreciation_start_date, from_date) + 1
-
-		# if frequency_of_depreciation is 12 months, total_days = 365
-		total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
-
-		if days < total_days:
-			has_pro_rata = True
-
-		return has_pro_rata
-
-	def get_modified_available_for_use_date(self, row):
-		return add_months(
-			self.available_for_use_date,
-			(self.number_of_depreciations_booked * row.frequency_of_depreciation),
-		)
-
 	def validate_asset_finance_books(self, row):
 		if flt(row.expected_value_after_useful_life) >= flt(self.gross_purchase_amount):
 			frappe.throw(
@@ -471,25 +443,6 @@
 
 		return records
 
-	@erpnext.allow_regional
-	def get_depreciation_amount(self, depreciable_value, fb_row):
-		if fb_row.depreciation_method in ("Straight Line", "Manual"):
-			# if the Depreciation Schedule is being prepared for the first time
-			if not self.flags.increase_in_asset_life:
-				depreciation_amount = (
-					flt(self.gross_purchase_amount) - flt(fb_row.expected_value_after_useful_life)
-				) / flt(fb_row.total_number_of_depreciations)
-
-			# if the Depreciation Schedule is being modified after Asset Repair
-			else:
-				depreciation_amount = (
-					flt(fb_row.value_after_depreciation) - flt(fb_row.expected_value_after_useful_life)
-				) / (date_diff(self.to_date, self.available_for_use_date) / 365)
-		else:
-			depreciation_amount = flt(depreciable_value * (flt(fb_row.rate_of_depreciation) / 100))
-
-		return depreciation_amount
-
 	def validate_make_gl_entry(self):
 		purchase_document = self.get_purchase_document()
 		if not purchase_document:
@@ -614,23 +567,42 @@
 		float_precision = cint(frappe.db.get_default("float_precision")) or 2
 
 		if args.get("depreciation_method") == "Double Declining Balance":
-			return 200.0 / args.get("total_number_of_depreciations")
+			return 200.0 / (
+				(
+					flt(args.get("total_number_of_depreciations"), 2) * flt(args.get("frequency_of_depreciation"))
+				)
+				/ 12
+			)
 
 		if args.get("depreciation_method") == "Written Down Value":
-			if args.get("rate_of_depreciation") and on_validate:
+			if (
+				args.get("rate_of_depreciation")
+				and on_validate
+				and not self.flags.increase_in_asset_value_due_to_repair
+			):
 				return args.get("rate_of_depreciation")
 
-			value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
-			depreciation_rate = math.pow(value, 1.0 / flt(args.get("total_number_of_depreciations"), 2))
+			if self.flags.increase_in_asset_value_due_to_repair:
+				value = flt(args.get("expected_value_after_useful_life")) / flt(
+					args.get("value_after_depreciation")
+				)
+			else:
+				value = flt(args.get("expected_value_after_useful_life")) / flt(self.gross_purchase_amount)
+
+			depreciation_rate = math.pow(
+				value,
+				1.0
+				/ (
+					(
+						flt(args.get("total_number_of_depreciations"), 2)
+						* flt(args.get("frequency_of_depreciation"))
+					)
+					/ 12
+				),
+			)
+
 			return flt((100 * (1 - depreciation_rate)), float_precision)
 
-	def get_pro_rata_amt(self, row, depreciation_amount, from_date, to_date):
-		days = date_diff(to_date, from_date)
-		months = month_diff(to_date, from_date)
-		total_days = get_total_days(to_date, row.frequency_of_depreciation)
-
-		return (depreciation_amount * flt(days)) / flt(total_days), days, months
-
 
 def update_maintenance_status():
 	assets = frappe.get_all(
@@ -874,15 +846,6 @@
 	return asset.get_value_after_depreciation(finance_book)
 
 
-def get_total_days(date, frequency):
-	period_start_date = add_months(date, cint(frequency) * -1)
-
-	if is_last_day_of_the_month(date):
-		period_start_date = get_last_day(period_start_date)
-
-	return date_diff(date, period_start_date)
-
-
 @frappe.whitelist()
 def split_asset(asset_name, split_qty):
 	asset = frappe.get_doc("Asset", asset_name)
diff --git a/erpnext/assets/doctype/asset/asset_list.js b/erpnext/assets/doctype/asset/asset_list.js
index 3d00eb7..5f53b00 100644
--- a/erpnext/assets/doctype/asset/asset_list.js
+++ b/erpnext/assets/doctype/asset/asset_list.js
@@ -36,7 +36,7 @@
 		}
 	},
 	onload: function(me) {
-		me.page.add_action_item('Make Asset Movement', function() {
+		me.page.add_action_item(__("Make Asset Movement"), function() {
 			const assets = me.get_checked_items();
 			frappe.call({
 				method: "erpnext.assets.doctype.asset.asset.make_asset_movement",
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index fb6e174..028e3d6 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -249,10 +249,16 @@
 	asset_links = get_comma_separated_asset_links(failed_asset_names)
 
 	message = (
-		_("Hi,")
-		+ "<br>"
-		+ _("The following assets have failed to post depreciation entries: {0}").format(asset_links)
+		_("Hello,")
+		+ "<br><br>"
+		+ _("The following assets have failed to automatically post depreciation entries: {0}").format(
+			asset_links
+		)
 		+ "."
+		+ "<br><br>"
+		+ _(
+			"Please raise a support ticket and share this email, or forward this email to your development team so that they can find the issue in the developer console by manually creating the depreciation entry via the asset's depreciation schedule table."
+		)
 	)
 
 	frappe.sendmail(recipients=recipients, subject=subject, message=message)
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index 9a15263..cde0280 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -29,8 +29,11 @@
 	scrap_asset,
 )
 from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
+	_check_is_pro_rata,
+	_get_pro_rata_amt,
 	get_asset_depr_schedule_doc,
 	get_depr_schedule,
+	get_depreciation_amount,
 )
 from erpnext.stock.doctype.purchase_receipt.purchase_receipt import (
 	make_purchase_invoice as make_invoice,
@@ -234,7 +237,7 @@
 			asset.gross_purchase_amount - asset.finance_books[0].value_after_depreciation,
 			asset.precision("gross_purchase_amount"),
 		)
-		pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+		pro_rata_amount, _, _ = _get_pro_rata_amt(
 			asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
 		)
 		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
@@ -321,7 +324,7 @@
 		self.assertEquals(second_asset_depr_schedule.status, "Active")
 		self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
 
-		pro_rata_amount, _, _ = asset.get_pro_rata_amt(
+		pro_rata_amount, _, _ = _get_pro_rata_amt(
 			asset.finance_books[0], 9000, get_last_day(add_months(purchase_date, 1)), date
 		)
 		pro_rata_amount = flt(pro_rata_amount, asset.precision("gross_purchase_amount"))
@@ -828,8 +831,8 @@
 		expected_schedules = [
 			["2030-12-31", 28630.14, 28630.14],
 			["2031-12-31", 35684.93, 64315.07],
-			["2032-12-31", 17842.47, 82157.54],
-			["2033-06-06", 5342.46, 87500.0],
+			["2032-12-31", 17842.46, 82157.53],
+			["2033-06-06", 5342.47, 87500.0],
 		]
 
 		schedules = [
@@ -857,12 +860,12 @@
 		)
 
 		expected_schedules = [
-			["2022-02-28", 647.25, 647.25],
-			["2022-03-31", 1210.71, 1857.96],
-			["2022-04-30", 1053.99, 2911.95],
-			["2022-05-31", 917.55, 3829.5],
-			["2022-06-30", 798.77, 4628.27],
-			["2022-07-15", 371.73, 5000.0],
+			["2022-02-28", 310.89, 310.89],
+			["2022-03-31", 654.45, 965.34],
+			["2022-04-30", 654.45, 1619.79],
+			["2022-05-31", 654.45, 2274.24],
+			["2022-06-30", 654.45, 2928.69],
+			["2022-07-15", 2071.31, 5000.0],
 		]
 
 		schedules = [
@@ -938,7 +941,7 @@
 			},
 		)
 
-		depreciation_amount = asset.get_depreciation_amount(100000, asset.finance_books[0])
+		depreciation_amount = get_depreciation_amount(asset, 100000, asset.finance_books[0])
 		self.assertEqual(depreciation_amount, 30000)
 
 	def test_make_depr_schedule(self):
@@ -997,7 +1000,7 @@
 			},
 		)
 
-		has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
+		has_pro_rata = _check_is_pro_rata(asset, asset.finance_books[0])
 		self.assertFalse(has_pro_rata)
 
 		asset.finance_books = []
@@ -1012,7 +1015,7 @@
 			},
 		)
 
-		has_pro_rata = asset.check_is_pro_rata(asset.finance_books[0])
+		has_pro_rata = _check_is_pro_rata(asset, asset.finance_books[0])
 		self.assertTrue(has_pro_rata)
 
 	def test_expected_value_after_useful_life_greater_than_purchase_amount(self):
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
index c28b2b3..3d2dff1 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.js
@@ -43,9 +43,9 @@
 	if(frm.doc.depreciation_method != "Manual") return;
 
 	var accumulated_depreciation = flt(frm.doc.opening_accumulated_depreciation);
-	$.each(frm.doc.schedules || [], function(i, row) {
+
+	$.each(frm.doc.depreciation_schedule || [], function(i, row) {
 		accumulated_depreciation  += flt(row.depreciation_amount);
-		frappe.model.set_value(row.doctype, row.name,
-			"accumulated_depreciation_amount", accumulated_depreciation);
+		frappe.model.set_value(row.doctype, row.name, "accumulated_depreciation_amount", accumulated_depreciation);
 	})
 };
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
index 898c482..d38508d 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.json
@@ -10,7 +10,9 @@
   "asset",
   "naming_series",
   "column_break_2",
+  "gross_purchase_amount",
   "opening_accumulated_depreciation",
+  "number_of_depreciations_booked",
   "finance_book",
   "finance_book_id",
   "depreciation_details_section",
@@ -148,18 +150,36 @@
    "read_only": 1
   },
   {
-   "depends_on": "opening_accumulated_depreciation",
    "fieldname": "opening_accumulated_depreciation",
    "fieldtype": "Currency",
+   "hidden": 1,
    "label": "Opening Accumulated Depreciation",
    "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "gross_purchase_amount",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Gross Purchase Amount",
+   "options": "Company:company:default_currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "number_of_depreciations_booked",
+   "fieldtype": "Int",
+   "hidden": 1,
+   "label": "Number of Depreciations Booked",
+   "print_hide": 1,
    "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-16 21:08:21.421260",
+ "modified": "2023-02-26 16:37:23.734806",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Asset Depreciation Schedule",
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 6f02662..116593a 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -4,7 +4,19 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import add_days, add_months, cint, flt, get_last_day, is_last_day_of_the_month
+from frappe.utils import (
+	add_days,
+	add_months,
+	cint,
+	date_diff,
+	flt,
+	get_last_day,
+	getdate,
+	is_last_day_of_the_month,
+	month_diff,
+)
+
+import erpnext
 
 
 class AssetDepreciationSchedule(Document):
@@ -83,15 +95,58 @@
 		date_of_return=None,
 		update_asset_finance_book_row=True,
 	):
+		have_asset_details_been_modified = self.have_asset_details_been_modified(asset_doc)
+		not_manual_depr_or_have_manual_depr_details_been_modified = (
+			self.not_manual_depr_or_have_manual_depr_details_been_modified(row)
+		)
+
 		self.set_draft_asset_depr_schedule_details(asset_doc, row)
-		self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
-		self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+
+		if self.should_prepare_depreciation_schedule(
+			have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
+		):
+			self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
+			self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+
+	def have_asset_details_been_modified(self, asset_doc):
+		return (
+			asset_doc.gross_purchase_amount != self.gross_purchase_amount
+			or asset_doc.opening_accumulated_depreciation != self.opening_accumulated_depreciation
+			or asset_doc.number_of_depreciations_booked != self.number_of_depreciations_booked
+		)
+
+	def not_manual_depr_or_have_manual_depr_details_been_modified(self, row):
+		return (
+			self.depreciation_method != "Manual"
+			or row.total_number_of_depreciations != self.total_number_of_depreciations
+			or row.frequency_of_depreciation != self.frequency_of_depreciation
+			or getdate(row.depreciation_start_date) != self.get("depreciation_schedule")[0].schedule_date
+			or row.expected_value_after_useful_life != self.expected_value_after_useful_life
+		)
+
+	def should_prepare_depreciation_schedule(
+		self, have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
+	):
+		if not self.get("depreciation_schedule"):
+			return True
+
+		old_asset_depr_schedule_doc = self.get_doc_before_save()
+
+		if self.docstatus != 0 and not old_asset_depr_schedule_doc:
+			return True
+
+		if have_asset_details_been_modified or not_manual_depr_or_have_manual_depr_details_been_modified:
+			return True
+
+		return False
 
 	def set_draft_asset_depr_schedule_details(self, asset_doc, row):
 		self.asset = asset_doc.name
 		self.finance_book = row.finance_book
 		self.finance_book_id = row.idx
-		self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation
+		self.opening_accumulated_depreciation = asset_doc.opening_accumulated_depreciation or 0
+		self.number_of_depreciations_booked = asset_doc.number_of_depreciations_booked or 0
+		self.gross_purchase_amount = asset_doc.gross_purchase_amount
 		self.depreciation_method = row.depreciation_method
 		self.total_number_of_depreciations = row.total_number_of_depreciations
 		self.frequency_of_depreciation = row.frequency_of_depreciation
@@ -102,7 +157,7 @@
 	def make_depr_schedule(
 		self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
 	):
-		if row.depreciation_method != "Manual" and not self.get("depreciation_schedule"):
+		if not self.get("depreciation_schedule"):
 			self.depreciation_schedule = []
 
 		if not asset_doc.available_for_use_date:
@@ -141,24 +196,49 @@
 			row.db_update()
 
 		number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
-			asset_doc.number_of_depreciations_booked
+			self.number_of_depreciations_booked
 		)
 
-		has_pro_rata = asset_doc.check_is_pro_rata(row)
+		has_pro_rata = _check_is_pro_rata(asset_doc, row)
 		if has_pro_rata:
 			number_of_pending_depreciations += 1
 
+		has_wdv_or_dd_non_yearly_pro_rata = False
+		if (
+			row.depreciation_method in ("Written Down Value", "Double Declining Balance")
+			and cint(row.frequency_of_depreciation) != 12
+		):
+			has_wdv_or_dd_non_yearly_pro_rata = _check_is_pro_rata(
+				asset_doc, row, wdv_or_dd_non_yearly=True
+			)
+
 		skip_row = False
 		should_get_last_day = is_last_day_of_the_month(row.depreciation_start_date)
 
+		depreciation_amount = 0
+
 		for n in range(start, number_of_pending_depreciations):
 			# If depreciation is already completed (for double declining balance)
 			if skip_row:
 				continue
 
-			depreciation_amount = asset_doc.get_depreciation_amount(value_after_depreciation, row)
+			if n > 0 and len(self.get("depreciation_schedule")) > n - 1:
+				prev_depreciation_amount = self.get("depreciation_schedule")[n - 1].depreciation_amount
+			else:
+				prev_depreciation_amount = 0
 
-			if not has_pro_rata or n < cint(number_of_pending_depreciations) - 1:
+			depreciation_amount = get_depreciation_amount(
+				asset_doc,
+				value_after_depreciation,
+				row,
+				n,
+				prev_depreciation_amount,
+				has_wdv_or_dd_non_yearly_pro_rata,
+			)
+
+			if not has_pro_rata or (
+				n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2
+			):
 				schedule_date = add_months(
 					row.depreciation_start_date, n * cint(row.frequency_of_depreciation)
 				)
@@ -176,26 +256,36 @@
 				if self.depreciation_schedule:
 					from_date = self.depreciation_schedule[-1].schedule_date
 
-				depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
-					row, depreciation_amount, from_date, date_of_disposal
+				depreciation_amount, days, months = _get_pro_rata_amt(
+					row,
+					depreciation_amount,
+					from_date,
+					date_of_disposal,
 				)
 
 				if depreciation_amount > 0:
 					self.add_depr_schedule_row(
 						date_of_disposal,
 						depreciation_amount,
-						row.depreciation_method,
 					)
 
 				break
 
 			# For first row
-			if has_pro_rata and not asset_doc.opening_accumulated_depreciation and n == 0:
+			if (
+				(has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
+				and not self.opening_accumulated_depreciation
+				and n == 0
+			):
 				from_date = add_days(
 					asset_doc.available_for_use_date, -1
 				)  # needed to calc depr amount for available_for_use_date too
-				depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
-					row, depreciation_amount, from_date, row.depreciation_start_date
+				depreciation_amount, days, months = _get_pro_rata_amt(
+					row,
+					depreciation_amount,
+					from_date,
+					row.depreciation_start_date,
+					has_wdv_or_dd_non_yearly_pro_rata,
 				)
 
 				# For first depr schedule date will be the start date
@@ -209,13 +299,17 @@
 					# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
 					asset_doc.to_date = add_months(
 						asset_doc.available_for_use_date,
-						(n + asset_doc.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
+						(n + self.number_of_depreciations_booked) * cint(row.frequency_of_depreciation),
 					)
 
 				depreciation_amount_without_pro_rata = depreciation_amount
 
-				depreciation_amount, days, months = asset_doc.get_pro_rata_amt(
-					row, depreciation_amount, schedule_date, asset_doc.to_date
+				depreciation_amount, days, months = _get_pro_rata_amt(
+					row,
+					depreciation_amount,
+					schedule_date,
+					asset_doc.to_date,
+					has_wdv_or_dd_non_yearly_pro_rata,
 				)
 
 				depreciation_amount = self.get_adjusted_depreciation_amount(
@@ -247,7 +341,6 @@
 				self.add_depr_schedule_row(
 					schedule_date,
 					depreciation_amount,
-					row.depreciation_method,
 				)
 
 	# to ensure that final accumulated depreciation amount is accurate
@@ -274,14 +367,12 @@
 		self,
 		schedule_date,
 		depreciation_amount,
-		depreciation_method,
 	):
 		self.append(
 			"depreciation_schedule",
 			{
 				"schedule_date": schedule_date,
 				"depreciation_amount": depreciation_amount,
-				"depreciation_method": depreciation_method,
 			},
 		)
 
@@ -293,7 +384,9 @@
 		ignore_booked_entry=False,
 	):
 		straight_line_idx = [
-			d.idx for d in self.get("depreciation_schedule") if d.depreciation_method == "Straight Line"
+			d.idx
+			for d in self.get("depreciation_schedule")
+			if self.depreciation_method == "Straight Line" or self.depreciation_method == "Manual"
 		]
 
 		accumulated_depreciation = flt(self.opening_accumulated_depreciation)
@@ -336,6 +429,132 @@
 	return value_after_depreciation
 
 
+# if it returns True, depreciation_amount will not be equal for the first and last rows
+def _check_is_pro_rata(asset_doc, row, wdv_or_dd_non_yearly=False):
+	has_pro_rata = False
+
+	# if not existing asset, from_date = available_for_use_date
+	# otherwise, if number_of_depreciations_booked = 2, available_for_use_date = 01/01/2020 and frequency_of_depreciation = 12
+	# from_date = 01/01/2022
+	from_date = _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly)
+	days = date_diff(row.depreciation_start_date, from_date) + 1
+
+	if wdv_or_dd_non_yearly:
+		total_days = get_total_days(row.depreciation_start_date, 12)
+	else:
+		# if frequency_of_depreciation is 12 months, total_days = 365
+		total_days = get_total_days(row.depreciation_start_date, row.frequency_of_depreciation)
+
+	if days < total_days:
+		has_pro_rata = True
+
+	return has_pro_rata
+
+
+def _get_modified_available_for_use_date(asset_doc, row, wdv_or_dd_non_yearly=False):
+	if wdv_or_dd_non_yearly:
+		return add_months(
+			asset_doc.available_for_use_date,
+			(asset_doc.number_of_depreciations_booked * 12),
+		)
+	else:
+		return add_months(
+			asset_doc.available_for_use_date,
+			(asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation),
+		)
+
+
+def _get_pro_rata_amt(
+	row, depreciation_amount, from_date, to_date, has_wdv_or_dd_non_yearly_pro_rata=False
+):
+	days = date_diff(to_date, from_date)
+	months = month_diff(to_date, from_date)
+	if has_wdv_or_dd_non_yearly_pro_rata:
+		total_days = get_total_days(to_date, 12)
+	else:
+		total_days = get_total_days(to_date, row.frequency_of_depreciation)
+
+	return (depreciation_amount * flt(days)) / flt(total_days), days, months
+
+
+def get_total_days(date, frequency):
+	period_start_date = add_months(date, cint(frequency) * -1)
+
+	if is_last_day_of_the_month(date):
+		period_start_date = get_last_day(period_start_date)
+
+	return date_diff(date, period_start_date)
+
+
+@erpnext.allow_regional
+def get_depreciation_amount(
+	asset,
+	depreciable_value,
+	row,
+	schedule_idx=0,
+	prev_depreciation_amount=0,
+	has_wdv_or_dd_non_yearly_pro_rata=False,
+):
+	if row.depreciation_method in ("Straight Line", "Manual"):
+		return get_straight_line_or_manual_depr_amount(asset, row)
+	else:
+		return get_wdv_or_dd_depr_amount(
+			depreciable_value,
+			row.rate_of_depreciation,
+			row.frequency_of_depreciation,
+			schedule_idx,
+			prev_depreciation_amount,
+			has_wdv_or_dd_non_yearly_pro_rata,
+		)
+
+
+def get_straight_line_or_manual_depr_amount(asset, row):
+	# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
+	if asset.flags.increase_in_asset_life:
+		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
+			date_diff(asset.to_date, asset.available_for_use_date) / 365
+		)
+	# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset value
+	elif asset.flags.increase_in_asset_value_due_to_repair:
+		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
+			row.total_number_of_depreciations
+		)
+	# if the Depreciation Schedule is being prepared for the first time
+	else:
+		return (flt(asset.gross_purchase_amount) - flt(row.expected_value_after_useful_life)) / flt(
+			row.total_number_of_depreciations
+		)
+
+
+def get_wdv_or_dd_depr_amount(
+	depreciable_value,
+	rate_of_depreciation,
+	frequency_of_depreciation,
+	schedule_idx,
+	prev_depreciation_amount,
+	has_wdv_or_dd_non_yearly_pro_rata,
+):
+	if cint(frequency_of_depreciation) == 12:
+		return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
+	else:
+		if has_wdv_or_dd_non_yearly_pro_rata:
+			if schedule_idx == 0:
+				return flt(depreciable_value) * (flt(rate_of_depreciation) / 100)
+			elif schedule_idx % (12 / cint(frequency_of_depreciation)) == 1:
+				return (
+					flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
+				)
+			else:
+				return prev_depreciation_amount
+		else:
+			if schedule_idx % (12 / cint(frequency_of_depreciation)) == 0:
+				return (
+					flt(depreciable_value) * flt(frequency_of_depreciation) * (flt(rate_of_depreciation) / 1200)
+				)
+			else:
+				return prev_depreciation_amount
+
+
 def make_draft_asset_depr_schedules_if_not_present(asset_doc):
 	for row in asset_doc.get("finance_books"):
 		draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
@@ -412,6 +631,16 @@
 
 		new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
 
+		if asset_doc.flags.increase_in_asset_value_due_to_repair and row.depreciation_method in (
+			"Written Down Value",
+			"Double Declining Balance",
+		):
+			new_rate_of_depreciation = flt(
+				asset_doc.get_depreciation_rate(row), row.precision("rate_of_depreciation")
+			)
+			row.rate_of_depreciation = new_rate_of_depreciation
+			new_asset_depr_schedule_doc.rate_of_depreciation = new_rate_of_depreciation
+
 		new_asset_depr_schedule_doc.make_depr_schedule(asset_doc, row, date_of_disposal)
 		new_asset_depr_schedule_doc.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
 
diff --git a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
index 0028d84..8303141 100644
--- a/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
+++ b/erpnext/assets/doctype/asset_maintenance/asset_maintenance.py
@@ -84,6 +84,8 @@
 		next_due_date = add_years(start_date, 1)
 	if periodicity == "2 Yearly":
 		next_due_date = add_years(start_date, 2)
+	if periodicity == "3 Yearly":
+		next_due_date = add_years(start_date, 3)
 	if periodicity == "Quarterly":
 		next_due_date = add_months(start_date, 3)
 	if end_date and (
diff --git a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json
index 20963e3..b7cb23e 100644
--- a/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json
+++ b/erpnext/assets/doctype/asset_maintenance_task/asset_maintenance_task.json
@@ -1,664 +1,156 @@
 {
- "allow_copy": 0, 
- "allow_guest_to_view": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "autoname": "", 
- "beta": 0, 
- "creation": "2017-10-20 07:10:55.903571", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "Document", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2017-10-20 07:10:55.903571",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "maintenance_task",
+  "maintenance_type",
+  "column_break_2",
+  "maintenance_status",
+  "section_break_2",
+  "start_date",
+  "periodicity",
+  "column_break_4",
+  "end_date",
+  "certificate_required",
+  "section_break_9",
+  "assign_to",
+  "column_break_10",
+  "assign_to_name",
+  "section_break_10",
+  "next_due_date",
+  "column_break_14",
+  "last_completion_date",
+  "section_break_7",
+  "description"
+ ],
  "fields": [
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "maintenance_task", 
-   "fieldtype": "Data", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 1, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 1, 
-   "label": "Maintenance Task", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "maintenance_task",
+   "fieldtype": "Data",
+   "in_filter": 1,
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Maintenance Task",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "maintenance_type", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Maintenance Type", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Preventive Maintenance\nCalibration", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "maintenance_type",
+   "fieldtype": "Select",
+   "label": "Maintenance Type",
+   "options": "Preventive Maintenance\nCalibration"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_2", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_2",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "", 
-   "fieldname": "maintenance_status", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Maintenance Status", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Planned\nOverdue\nCancelled", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "maintenance_status",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Maintenance Status",
+   "options": "Planned\nOverdue\nCancelled",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_2", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_2",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "default": "Today", 
-   "fieldname": "start_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Start Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "default": "Today",
+   "fieldname": "start_date",
+   "fieldtype": "Date",
+   "label": "Start Date",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "periodicity", 
-   "fieldtype": "Select", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Periodicity", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 1, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "periodicity",
+   "fieldtype": "Select",
+   "in_list_view": 1,
+   "label": "Periodicity",
+   "options": "\nDaily\nWeekly\nMonthly\nQuarterly\nYearly\n2 Yearly\n3 Yearly",
+   "reqd": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_4", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_4",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "end_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "End Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "end_date",
+   "fieldtype": "Date",
+   "label": "End Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "certificate_required", 
-   "fieldtype": "Check", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Certificate Required", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 1, 
-   "set_only_once": 1, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "default": "0",
+   "fieldname": "certificate_required",
+   "fieldtype": "Check",
+   "label": "Certificate Required",
+   "search_index": 1,
+   "set_only_once": 1
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_9", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_9",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "assign_to", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Assign To", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "User", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "assign_to",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Assign To",
+   "options": "User"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_10", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_10",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
    "fetch_from": "assign_to.full_name",
-   "fieldname": "assign_to_name", 
-   "fieldtype": "Read Only", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Assign to Name", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "assign_to_name",
+   "fieldtype": "Read Only",
+   "label": "Assign to Name"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_10", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_10",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "next_due_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Next Due Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "next_due_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Next Due Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "column_break_14", 
-   "fieldtype": "Column Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "column_break_14",
+   "fieldtype": "Column Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "last_completion_date", 
-   "fieldtype": "Date", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 1, 
-   "in_standard_filter": 0, 
-   "label": "Last Completion Date", 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "last_completion_date",
+   "fieldtype": "Date",
+   "in_list_view": 1,
+   "label": "Last Completion Date"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "section_break_7", 
-   "fieldtype": "Section Break", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "length": 0, 
-   "no_copy": 0, 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
-  }, 
+   "fieldname": "section_break_7",
+   "fieldtype": "Section Break"
+  },
   {
-   "allow_bulk_edit": 0, 
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "description", 
-   "fieldtype": "Text Editor", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_global_search": 0, 
-   "in_list_view": 0, 
-   "in_standard_filter": 0, 
-   "label": "Description", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "", 
-   "permlevel": 0, 
-   "precision": "", 
-   "print_hide": 0, 
-   "print_hide_if_no_value": 0, 
-   "read_only": 0, 
-   "remember_last_selected_value": 0, 
-   "report_hide": 0, 
-   "reqd": 0, 
-   "search_index": 0, 
-   "set_only_once": 0, 
-   "translatable": 0,
-   "unique": 0
+   "fieldname": "description",
+   "fieldtype": "Text Editor",
+   "label": "Description"
   }
- ], 
- "has_web_view": 0, 
- "hide_heading": 0, 
- "hide_toolbar": 0, 
- "idx": 0, 
- "image_view": 0, 
- "in_create": 0, 
- "is_submittable": 0, 
- "issingle": 0, 
- "istable": 1, 
- "max_attachments": 0, 
- "modified": "2018-06-18 16:12:04.330021", 
- "modified_by": "Administrator", 
- "module": "Assets", 
- "name": "Asset Maintenance Task", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "show_name_in_global_search": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_changes": 0, 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2023-03-23 07:03:07.113452",
+ "modified_by": "Administrator",
+ "module": "Assets",
+ "name": "Asset Maintenance Task",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/assets/doctype/asset_repair/asset_repair.py b/erpnext/assets/doctype/asset_repair/asset_repair.py
index a7172a7..a913ee4 100644
--- a/erpnext/assets/doctype/asset_repair/asset_repair.py
+++ b/erpnext/assets/doctype/asset_repair/asset_repair.py
@@ -43,53 +43,57 @@
 	def before_submit(self):
 		self.check_repair_status()
 
-		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
-			self.increase_asset_value()
-		if self.get("stock_consumption"):
-			self.check_for_stock_items_and_warehouse()
-			self.decrease_stock_quantity()
-		if self.get("capitalize_repair_cost"):
-			self.make_gl_entries()
-			if (
-				frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
-				and self.increase_in_asset_life
-			):
-				self.modify_depreciation_schedule()
+		self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
 
-		notes = _(
-			"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
-		).format(
-			get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
-			get_link_to_form(self.doctype, self.name),
-		)
-		self.asset_doc.flags.ignore_validate_update_after_submit = True
-		make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
-		self.asset_doc.save()
+		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
+			self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
+
+			self.increase_asset_value()
+
+			if self.get("stock_consumption"):
+				self.check_for_stock_items_and_warehouse()
+				self.decrease_stock_quantity()
+			if self.get("capitalize_repair_cost"):
+				self.make_gl_entries()
+				if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
+					self.modify_depreciation_schedule()
+
+			notes = _(
+				"This schedule was created when Asset {0} was repaired through Asset Repair {1}."
+			).format(
+				get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+				get_link_to_form(self.doctype, self.name),
+			)
+			self.asset_doc.flags.ignore_validate_update_after_submit = True
+			make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
+			self.asset_doc.save()
 
 	def before_cancel(self):
 		self.asset_doc = frappe.get_doc("Asset", self.asset)
 
-		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
-			self.decrease_asset_value()
-		if self.get("stock_consumption"):
-			self.increase_stock_quantity()
-		if self.get("capitalize_repair_cost"):
-			self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
-			self.make_gl_entries(cancel=True)
-			self.db_set("stock_entry", None)
-			if (
-				frappe.db.get_value("Asset", self.asset, "calculate_depreciation")
-				and self.increase_in_asset_life
-			):
-				self.revert_depreciation_schedule_on_cancellation()
+		self.asset_doc.flags.increase_in_asset_value_due_to_repair = False
 
-		notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
-			get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
-			get_link_to_form(self.doctype, self.name),
-		)
-		self.asset_doc.flags.ignore_validate_update_after_submit = True
-		make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
-		self.asset_doc.save()
+		if self.get("stock_consumption") or self.get("capitalize_repair_cost"):
+			self.asset_doc.flags.increase_in_asset_value_due_to_repair = True
+
+			self.decrease_asset_value()
+
+			if self.get("stock_consumption"):
+				self.increase_stock_quantity()
+			if self.get("capitalize_repair_cost"):
+				self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+				self.make_gl_entries(cancel=True)
+				self.db_set("stock_entry", None)
+				if self.asset_doc.calculate_depreciation and self.increase_in_asset_life:
+					self.revert_depreciation_schedule_on_cancellation()
+
+			notes = _("This schedule was created when Asset {0}'s Asset Repair {1} was cancelled.").format(
+				get_link_to_form(self.asset_doc.doctype, self.asset_doc.name),
+				get_link_to_form(self.doctype, self.name),
+			)
+			self.asset_doc.flags.ignore_validate_update_after_submit = True
+			make_new_active_asset_depr_schedules_and_cancel_current_ones(self.asset_doc, notes)
+			self.asset_doc.save()
 
 	def after_delete(self):
 		frappe.get_doc("Asset", self.asset).set_status()
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
index ae0e1bd..d07f40c 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.js
@@ -49,7 +49,7 @@
 			frm.call({
 				method: "erpnext.assets.doctype.asset.asset.get_asset_value_after_depreciation",
 				args: {
-					asset: frm.doc.asset,
+					asset_name: frm.doc.asset,
 					finance_book: frm.doc.finance_book
 				},
 				callback: function(r) {
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 31d6ffa..0213328 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -14,6 +14,7 @@
 from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
 from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
 	get_asset_depr_schedule_doc,
+	get_depreciation_amount,
 )
 
 
@@ -162,7 +163,7 @@
 						depreciation_amount = days * rate_per_day
 						from_date = data.schedule_date
 					else:
-						depreciation_amount = asset.get_depreciation_amount(value_after_depreciation, d)
+						depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
 
 					if depreciation_amount:
 						value_after_depreciation -= flt(depreciation_amount)
diff --git a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json
index 882c4bf..abe295c 100644
--- a/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json
+++ b/erpnext/assets/doctype/depreciation_schedule/depreciation_schedule.json
@@ -12,8 +12,7 @@
   "column_break_3",
   "accumulated_depreciation_amount",
   "journal_entry",
-  "make_depreciation_entry",
-  "depreciation_method"
+  "make_depreciation_entry"
  ],
  "fields": [
   {
@@ -58,20 +57,11 @@
    "fieldname": "make_depreciation_entry",
    "fieldtype": "Button",
    "label": "Make Depreciation Entry"
-  },
-  {
-   "fieldname": "depreciation_method",
-   "fieldtype": "Select",
-   "hidden": 1,
-   "label": "Depreciation Method",
-   "options": "\nStraight Line\nDouble Declining Balance\nWritten Down Value\nManual",
-   "print_hide": 1,
-   "read_only": 1
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2022-12-06 20:35:50.264281",
+ "modified": "2023-03-13 23:17:15.849950",
  "modified_by": "Administrator",
  "module": "Assets",
  "name": "Depreciation Schedule",
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
index 06989a9..65a4226 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.js
@@ -24,7 +24,7 @@
 			"label": __("Period Based On"),
 			"fieldtype": "Select",
 			"options": ["Fiscal Year", "Date Range"],
-			"default": ["Fiscal Year"],
+			"default": "Fiscal Year",
 			"reqd": 1
 		},
 		{
@@ -76,12 +76,6 @@
 			options: "Asset Category"
 		},
 		{
-			fieldname:"finance_book",
-			label: __("Finance Book"),
-			fieldtype: "Link",
-			options: "Finance Book"
-		},
-		{
 			fieldname:"cost_center",
 			label: __("Cost Center"),
 			fieldtype: "Link",
@@ -96,8 +90,20 @@
 			reqd: 1
 		},
 		{
-			fieldname:"is_existing_asset",
-			label: __("Is Existing Asset"),
+			fieldname:"finance_book",
+			label: __("Finance Book"),
+			fieldtype: "Link",
+			options: "Finance Book",
+			depends_on: "eval: doc.only_depreciable_assets == 1",
+		},
+		{
+			fieldname:"only_depreciable_assets",
+			label: __("Only depreciable assets"),
+			fieldtype: "Check"
+		},
+		{
+			fieldname:"only_existing_assets",
+			label: __("Only existing assets"),
 			fieldtype: "Check"
 		},
 	]
diff --git a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
index 59d43b1..5fbcbe2 100644
--- a/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
+++ b/erpnext/assets/report/fixed_asset_register/fixed_asset_register.py
@@ -45,8 +45,10 @@
 		filters.year_end_date = getdate(fiscal_year.year_end_date)
 
 		conditions[date_field] = ["between", [filters.year_start_date, filters.year_end_date]]
-	if filters.get("is_existing_asset"):
-		conditions["is_existing_asset"] = filters.get("is_existing_asset")
+	if filters.get("only_depreciable_assets"):
+		conditions["calculate_depreciation"] = filters.get("only_depreciable_assets")
+	if filters.get("only_existing_assets"):
+		conditions["is_existing_asset"] = filters.get("only_existing_assets")
 	if filters.get("asset_category"):
 		conditions["asset_category"] = filters.get("asset_category")
 	if filters.get("cost_center"):
@@ -102,19 +104,18 @@
 		]
 		assets_record = frappe.db.get_all("Asset", filters=conditions, fields=fields)
 
-	assets_linked_to_fb = frappe.db.get_all(
-		doctype="Asset Finance Book",
-		filters={"finance_book": filters.finance_book or ("is", "not set")},
-		pluck="parent",
-	)
+	assets_linked_to_fb = None
+
+	if filters.only_depreciable_assets:
+		assets_linked_to_fb = frappe.db.get_all(
+			doctype="Asset Finance Book",
+			filters={"finance_book": filters.finance_book or ("is", "not set")},
+			pluck="parent",
+		)
 
 	for asset in assets_record:
-		if filters.finance_book:
-			if asset.asset_id not in assets_linked_to_fb:
-				continue
-		else:
-			if asset.calculate_depreciation and asset.asset_id not in assets_linked_to_fb:
-				continue
+		if assets_linked_to_fb and asset.asset_id not in assets_linked_to_fb:
+			continue
 
 		asset_value = get_asset_value_after_depreciation(asset.asset_id, filters.finance_book)
 		row = {
@@ -172,11 +173,11 @@
 			"datasets": [
 				{
 					"name": _("Asset Value"),
-					"values": [d.get("asset_value") for d in labels_values_map.values()],
+					"values": [flt(d.get("asset_value"), 2) for d in labels_values_map.values()],
 				},
 				{
 					"name": _("Depreciatied Amount"),
-					"values": [d.get("depreciated_amount") for d in labels_values_map.values()],
+					"values": [flt(d.get("depreciated_amount"), 2) for d in labels_values_map.values()],
 				},
 			],
 		},
@@ -312,7 +313,7 @@
 
 	return [
 		{
-			"label": _("Asset Id"),
+			"label": _("Asset ID"),
 			"fieldtype": "Link",
 			"fieldname": "asset_id",
 			"options": "Asset",
diff --git a/erpnext/buying/doctype/buying_settings/buying_settings.json b/erpnext/buying/doctype/buying_settings/buying_settings.json
index 652dcf0..8c73e56 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.json
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.json
@@ -16,8 +16,10 @@
   "transaction_settings_section",
   "po_required",
   "pr_required",
+  "over_order_allowance",
   "column_break_12",
   "maintain_same_rate",
+  "set_landed_cost_based_on_purchase_invoice_rate",
   "allow_multiple_items",
   "bill_for_rejected_quantity_in_purchase_invoice",
   "disable_last_purchase_rate",
@@ -147,6 +149,21 @@
    "fieldname": "show_pay_button",
    "fieldtype": "Check",
    "label": "Show Pay Button in Purchase Order Portal"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: !doc.maintain_same_rate",
+   "description": "Users can enable the checkbox If they want to adjust the incoming rate (set using purchase receipt) based on the purchase invoice rate.",
+   "fieldname": "set_landed_cost_based_on_purchase_invoice_rate",
+   "fieldtype": "Check",
+   "label": "Set Landed Cost Based on Purchase Invoice Rate"
+  },
+  {
+   "default": "0",
+   "description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
+   "fieldname": "over_order_allowance",
+   "fieldtype": "Float",
+   "label": "Over Order Allowance (%)"
   }
  ],
  "icon": "fa fa-cog",
@@ -154,7 +171,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-02-15 14:42:10.200679",
+ "modified": "2023-03-02 17:02:14.404622",
  "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 be1ebde..4680a88 100644
--- a/erpnext/buying/doctype/buying_settings/buying_settings.py
+++ b/erpnext/buying/doctype/buying_settings/buying_settings.py
@@ -21,3 +21,10 @@
 			self.get("supp_master_name") == "Naming Series",
 			hide_name_field=False,
 		)
+
+	def before_save(self):
+		self.check_maintain_same_rate()
+
+	def check_maintain_same_rate(self):
+		if self.maintain_same_rate:
+			self.set_landed_cost_based_on_purchase_invoice_rate = 0
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 47089f7..c6c9f1f 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -236,7 +236,11 @@
 							this.make_purchase_invoice, __('Create'));
 
 					if(flt(doc.per_billed) < 100 && doc.status != "Delivered") {
-						cur_frm.add_custom_button(__('Payment'), cur_frm.cscript.make_payment_entry, __('Create'));
+						this.frm.add_custom_button(
+							__('Payment'),
+							() => this.make_payment_entry(),
+							__('Create')
+						);
 					}
 
 					if(flt(doc.per_billed) < 100) {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 2415aec..06b9d29 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -21,6 +21,9 @@
 from erpnext.accounts.party import get_party_account, get_party_account_currency
 from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
 from erpnext.controllers.buying_controller import BuyingController
+from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
+	validate_against_blanket_order,
+)
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
 from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details
 from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
@@ -69,6 +72,7 @@
 		self.validate_with_previous_doc()
 		self.validate_for_subcontracting()
 		self.validate_minimum_order_qty()
+		validate_against_blanket_order(self)
 
 		if self.is_old_subcontracting_flow:
 			self.validate_bom_for_subcontracting_items()
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 7927beb..4590f8c 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -113,7 +113,10 @@
 
 	def get_link(self):
 		# RFQ link for supplier portal
-		return get_url("/app/request-for-quotation/" + self.name)
+		route = frappe.db.get_value(
+			"Portal Menu Item", {"reference_doctype": "Request for Quotation"}, ["route"]
+		)
+		return get_url("/app/{0}/".format(route) + self.name)
 
 	def update_supplier_part_no(self, supplier):
 		self.vendor = supplier
diff --git a/erpnext/buying/doctype/supplier/supplier.js b/erpnext/buying/doctype/supplier/supplier.js
index f0899b0..1ae6f03 100644
--- a/erpnext/buying/doctype/supplier/supplier.js
+++ b/erpnext/buying/doctype/supplier/supplier.js
@@ -64,7 +64,7 @@
 			// custom buttons
 			frm.add_custom_button(__('Accounting Ledger'), function () {
 				frappe.set_route('query-report', 'General Ledger',
-					{ party_type: 'Supplier', party: frm.doc.name });
+					{ party_type: 'Supplier', party: frm.doc.name, party_name: frm.doc.supplier_name });
 			}, __("View"));
 
 			frm.add_custom_button(__('Accounts Payable'), function () {
diff --git a/erpnext/buying/doctype/supplier/supplier.py b/erpnext/buying/doctype/supplier/supplier.py
index 120b2f8..01b5c8f 100644
--- a/erpnext/buying/doctype/supplier/supplier.py
+++ b/erpnext/buying/doctype/supplier/supplier.py
@@ -125,18 +125,9 @@
 
 	def on_trash(self):
 		if self.supplier_primary_contact:
-			frappe.db.sql(
-				"""
-				UPDATE `tabSupplier`
-				SET
-					supplier_primary_contact=null,
-					supplier_primary_address=null,
-					mobile_no=null,
-					email_id=null,
-					primary_address=null
-				WHERE name=%(name)s""",
-				{"name": self.name},
-			)
+			self.db_set("supplier_primary_contact", None)
+		if self.supplier_primary_address:
+			self.db_set("supplier_primary_address", None)
 
 		delete_contact_and_address("Supplier", self.name)
 
diff --git a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
index 6304a09..9db769d 100644
--- a/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
+++ b/erpnext/buying/report/subcontracted_item_to_be_received/subcontracted_item_to_be_received.js
@@ -22,14 +22,14 @@
 			fieldname:"from_date",
 			label: __("From Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
+			default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
 			reqd: 1
 		},
 		{
 			fieldname:"to_date",
 			label: __("To Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
+			default: frappe.datetime.get_today(),
 			reqd: 1
 		},
 	]
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
index b6739fe..7e5338f 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.js
@@ -22,14 +22,14 @@
 			fieldname:"from_date",
 			label: __("From Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_months(frappe.datetime.month_start(), -1),
+			default: frappe.datetime.add_months(frappe.datetime.get_today(), -1),
 			reqd: 1
 		},
 		{
 			fieldname:"to_date",
 			label: __("To Date"),
 			fieldtype: "Date",
-			default: frappe.datetime.add_days(frappe.datetime.month_start(),-1),
+			default: frappe.datetime.get_today(),
 			reqd: 1
 		},
 	]
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 3705fcf..7fcc28b 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -515,6 +515,7 @@
 				parent_dict.update({"customer": parent_dict.get("party_name")})
 
 			self.pricing_rules = []
+
 			for item in self.get("items"):
 				if item.get("item_code"):
 					args = parent_dict.copy()
@@ -833,7 +834,9 @@
 	def set_advances(self):
 		"""Returns list of advances against Account, Party, Reference"""
 
-		res = self.get_advance_entries()
+		res = self.get_advance_entries(
+			include_unallocated=not cint(self.get("only_include_allocated_payments"))
+		)
 
 		self.set("advances", [])
 		advance_allocated = 0
@@ -1232,7 +1235,7 @@
 				)
 			)
 
-	def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on, parentfield):
+	def validate_multiple_billing(self, ref_dt, item_ref_dn, based_on):
 		from erpnext.controllers.status_updater import get_allowance_for
 
 		item_allowance = {}
@@ -1245,17 +1248,20 @@
 
 		total_overbilled_amt = 0.0
 
+		reference_names = [d.get(item_ref_dn) for d in self.get("items") if d.get(item_ref_dn)]
+		reference_details = self.get_billing_reference_details(
+			reference_names, ref_dt + " Item", based_on
+		)
+
 		for item in self.get("items"):
 			if not item.get(item_ref_dn):
 				continue
 
-			ref_amt = flt(
-				frappe.db.get_value(ref_dt + " Item", item.get(item_ref_dn), based_on),
-				self.precision(based_on, item),
-			)
+			ref_amt = flt(reference_details.get(item.get(item_ref_dn)), self.precision(based_on, item))
+
 			if not ref_amt:
 				frappe.msgprint(
-					_("System will not check overbilling since amount for Item {0} in {1} is zero").format(
+					_("System will not check over billing since amount for Item {0} in {1} is zero").format(
 						item.item_code, ref_dt
 					),
 					title=_("Warning"),
@@ -1302,6 +1308,16 @@
 				alert=True,
 			)
 
+	def get_billing_reference_details(self, reference_names, reference_doctype, based_on):
+		return frappe._dict(
+			frappe.get_all(
+				reference_doctype,
+				filters={"name": ("in", reference_names)},
+				fields=["name", based_on],
+				as_list=1,
+			)
+		)
+
 	def get_billed_amount_for_item(self, item, item_ref_dn, based_on):
 		"""
 		Returns Sum of Amount of
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 4f7d9ad..e15b612 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -265,7 +265,10 @@
 					) / qty_in_stock_uom
 				else:
 					item.valuation_rate = (
-						item.base_net_amount + item.item_tax_amount + flt(item.landed_cost_voucher_amount)
+						item.base_net_amount
+						+ item.item_tax_amount
+						+ flt(item.landed_cost_voucher_amount)
+						+ flt(item.get("rate_difference_with_purchase_invoice"))
 					) / qty_in_stock_uom
 			else:
 				item.valuation_rate = 0.0
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 9fcb769..15c270e 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -131,7 +131,7 @@
 					)
 
 				elif ref.serial_no:
-					if not d.serial_no:
+					if d.qty and not d.serial_no:
 						frappe.throw(_("Row # {0}: Serial No is mandatory").format(d.idx))
 					else:
 						serial_nos = get_serial_nos(d.serial_no)
@@ -400,6 +400,16 @@
 			if serial_nos:
 				target_doc.serial_no = "\n".join(serial_nos)
 
+		if source_doc.get("rejected_serial_no"):
+			returned_serial_nos = get_returned_serial_nos(
+				source_doc, source_parent, serial_no_field="rejected_serial_no"
+			)
+			rejected_serial_nos = list(
+				set(get_serial_nos(source_doc.rejected_serial_no)) - set(returned_serial_nos)
+			)
+			if rejected_serial_nos:
+				target_doc.rejected_serial_no = "\n".join(rejected_serial_nos)
+
 		if doctype in ["Purchase Receipt", "Subcontracting Receipt"]:
 			returned_qty_map = get_returned_qty_map_for_row(
 				source_parent.name, source_parent.supplier, source_doc.name, doctype
@@ -610,7 +620,7 @@
 	return filters
 
 
-def get_returned_serial_nos(child_doc, parent_doc):
+def get_returned_serial_nos(child_doc, parent_doc, serial_no_field="serial_no"):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
 	return_ref_field = frappe.scrub(child_doc.doctype)
@@ -619,7 +629,7 @@
 
 	serial_nos = []
 
-	fields = ["`{0}`.`serial_no`".format("tab" + child_doc.doctype)]
+	fields = [f"`{'tab' + child_doc.doctype}`.`{serial_no_field}`"]
 
 	filters = [
 		[parent_doc.doctype, "return_against", "=", parent_doc.name],
@@ -629,6 +639,6 @@
 	]
 
 	for row in frappe.get_all(parent_doc.doctype, fields=fields, filters=filters):
-		serial_nos.extend(get_serial_nos(row.serial_no))
+		serial_nos.extend(get_serial_nos(row.get(serial_no_field)))
 
 	return serial_nos
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 8b4d28b..fc16a91 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -136,7 +136,7 @@
 			self.in_words = money_in_words(amount, self.currency)
 
 	def calculate_commission(self):
-		if not self.meta.get_field("commission_rate"):
+		if not self.meta.get_field("commission_rate") or self.docstatus.is_submitted():
 			return
 
 		self.round_floats_in(self, ("amount_eligible_for_commission", "commission_rate"))
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index dd2a670..58cab14 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -464,7 +464,7 @@
 					ifnull((select
 						ifnull(sum(case when abs(%(target_ref_field)s) > abs(%(target_field)s) then abs(%(target_field)s) else abs(%(target_ref_field)s) end), 0)
 						/ sum(abs(%(target_ref_field)s)) * 100
-					from `tab%(target_dt)s` where parent='%(name)s' having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
+					from `tab%(target_dt)s` where parent='%(name)s' and parenttype='%(target_parent_dt)s' having sum(abs(%(target_ref_field)s)) > 0), 0), 6)
 					%(update_modified)s
 				where name='%(name)s'"""
 				% args
diff --git a/erpnext/controllers/subcontracting_controller.py b/erpnext/controllers/subcontracting_controller.py
index cc80f6c..0575429 100644
--- a/erpnext/controllers/subcontracting_controller.py
+++ b/erpnext/controllers/subcontracting_controller.py
@@ -455,7 +455,7 @@
 					"allow_zero_valuation": 1,
 				}
 			)
-			rm_obj.rate = get_incoming_rate(args)
+			rm_obj.rate = bom_item.rate if self.backflush_based_on == "BOM" else get_incoming_rate(args)
 
 		if self.doctype == self.subcontract_data.order_doctype:
 			rm_obj.required_qty = qty
diff --git a/erpnext/controllers/taxes_and_totals.py b/erpnext/controllers/taxes_and_totals.py
index 8c403aa..1edd7bf 100644
--- a/erpnext/controllers/taxes_and_totals.py
+++ b/erpnext/controllers/taxes_and_totals.py
@@ -24,11 +24,19 @@
 	def __init__(self, doc: Document):
 		self.doc = doc
 		frappe.flags.round_off_applicable_accounts = []
+
+		self._items = self.filter_rows() if self.doc.doctype == "Quotation" else self.doc.get("items")
+
 		get_round_off_applicable_accounts(self.doc.company, frappe.flags.round_off_applicable_accounts)
 		self.calculate()
 
+	def filter_rows(self):
+		"""Exclude rows, that do not fulfill the filter criteria, from totals computation."""
+		items = list(filter(lambda item: not item.get("is_alternative"), self.doc.get("items")))
+		return items
+
 	def calculate(self):
-		if not len(self.doc.get("items")):
+		if not len(self._items):
 			return
 
 		self.discount_amount_applied = False
@@ -70,7 +78,7 @@
 		if hasattr(self.doc, "tax_withholding_net_total"):
 			sum_net_amount = 0
 			sum_base_net_amount = 0
-			for item in self.doc.get("items"):
+			for item in self._items:
 				if hasattr(item, "apply_tds") and item.apply_tds:
 					sum_net_amount += item.net_amount
 					sum_base_net_amount += item.base_net_amount
@@ -79,7 +87,7 @@
 			self.doc.base_tax_withholding_net_total = sum_base_net_amount
 
 	def validate_item_tax_template(self):
-		for item in self.doc.get("items"):
+		for item in self._items:
 			if item.item_code and item.get("item_tax_template"):
 				item_doc = frappe.get_cached_doc("Item", item.item_code)
 				args = {
@@ -137,7 +145,7 @@
 			return
 
 		if not self.discount_amount_applied:
-			for item in self.doc.get("items"):
+			for item in self._items:
 				self.doc.round_floats_in(item)
 
 				if item.discount_percentage == 100:
@@ -236,7 +244,7 @@
 		if not any(cint(tax.included_in_print_rate) for tax in self.doc.get("taxes")):
 			return
 
-		for item in self.doc.get("items"):
+		for item in self._items:
 			item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
 			cumulated_tax_fraction = 0
 			total_inclusive_tax_amount_per_qty = 0
@@ -317,7 +325,7 @@
 			self.doc.total
 		) = self.doc.base_total = self.doc.net_total = self.doc.base_net_total = 0.0
 
-		for item in self.doc.get("items"):
+		for item in self._items:
 			self.doc.total += item.amount
 			self.doc.total_qty += item.qty
 			self.doc.base_total += item.base_amount
@@ -354,7 +362,7 @@
 			]
 		)
 
-		for n, item in enumerate(self.doc.get("items")):
+		for n, item in enumerate(self._items):
 			item_tax_map = self._load_item_tax_rate(item.item_tax_rate)
 			for i, tax in enumerate(self.doc.get("taxes")):
 				# tax_amount represents the amount of tax for the current step
@@ -363,7 +371,7 @@
 				# Adjust divisional loss to the last item
 				if tax.charge_type == "Actual":
 					actual_tax_dict[tax.idx] -= current_tax_amount
-					if n == len(self.doc.get("items")) - 1:
+					if n == len(self._items) - 1:
 						current_tax_amount += actual_tax_dict[tax.idx]
 
 				# accumulate tax amount into tax.tax_amount
@@ -391,7 +399,7 @@
 					)
 
 				# set precision in the last item iteration
-				if n == len(self.doc.get("items")) - 1:
+				if n == len(self._items) - 1:
 					self.round_off_totals(tax)
 					self._set_in_company_currency(tax, ["tax_amount", "tax_amount_after_discount_amount"])
 
@@ -570,7 +578,7 @@
 	def calculate_total_net_weight(self):
 		if self.doc.meta.get_field("total_net_weight"):
 			self.doc.total_net_weight = 0.0
-			for d in self.doc.items:
+			for d in self._items:
 				if d.total_weight:
 					self.doc.total_net_weight += d.total_weight
 
@@ -630,7 +638,7 @@
 
 			if total_for_discount_amount:
 				# calculate item amount after Discount Amount
-				for i, item in enumerate(self.doc.get("items")):
+				for i, item in enumerate(self._items):
 					distributed_amount = (
 						flt(self.doc.discount_amount) * item.net_amount / total_for_discount_amount
 					)
@@ -643,7 +651,7 @@
 						self.doc.apply_discount_on == "Net Total"
 						or not taxes
 						or total_for_discount_amount == self.doc.net_total
-					) and i == len(self.doc.get("items")) - 1:
+					) and i == len(self._items) - 1:
 						discount_amount_loss = flt(
 							self.doc.net_total - net_total - self.doc.discount_amount, self.doc.precision("net_total")
 						)
diff --git a/erpnext/controllers/website_list_for_contact.py b/erpnext/controllers/website_list_for_contact.py
index 4673230..7c3c387 100644
--- a/erpnext/controllers/website_list_for_contact.py
+++ b/erpnext/controllers/website_list_for_contact.py
@@ -76,12 +76,9 @@
 	ignore_permissions = False
 
 	if not filters:
-		filters = []
+		filters = {}
 
-	if doctype in ["Supplier Quotation", "Purchase Invoice"]:
-		filters.append((doctype, "docstatus", "<", 2))
-	else:
-		filters.append((doctype, "docstatus", "=", 1))
+	filters["docstatus"] = ["<", "2"] if doctype in ["Supplier Quotation", "Purchase Invoice"] else 1
 
 	if (user != "Guest" and is_website_user()) or doctype == "Request for Quotation":
 		parties_doctype = (
@@ -92,12 +89,12 @@
 
 		if customers:
 			if doctype == "Quotation":
-				filters.append(("quotation_to", "=", "Customer"))
-				filters.append(("party_name", "in", customers))
+				filters["quotation_to"] = "Customer"
+				filters["party_name"] = ["in", customers]
 			else:
-				filters.append(("customer", "in", customers))
+				filters["customer"] = ["in", customers]
 		elif suppliers:
-			filters.append(("supplier", "in", suppliers))
+			filters["supplier"] = ["in", suppliers]
 		elif not custom:
 			return []
 
@@ -110,7 +107,7 @@
 
 		if not customers and not suppliers and custom:
 			ignore_permissions = False
-			filters = []
+			filters = {}
 
 	transactions = get_list_for_transactions(
 		doctype,
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 1f76a1a..b261795 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -19,10 +19,6 @@
 				}
 			}
 		});
-
-		if (frm.doc.opportunity_from && frm.doc.party_name){
-			frm.trigger('set_contact_link');
-		}
 	},
 
 	validate: function(frm) {
@@ -130,6 +126,10 @@
 		} else {
 			frappe.contacts.clear_address_and_contact(frm);
 		}
+
+		if (frm.doc.opportunity_from && frm.doc.party_name) {
+			frm.trigger('set_contact_link');
+		}
 	},
 
 	set_contact_link: function(frm) {
@@ -137,6 +137,8 @@
 			frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Customer'}
 		} else if(frm.doc.opportunity_from == "Lead" && frm.doc.party_name) {
 			frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Lead'}
+		} else if (frm.doc.opportunity_from == "Prospect" && frm.doc.party_name) {
+			frappe.dynamic_link = {doc: frm.doc, fieldname: 'party_name', doctype: 'Prospect'}
 		}
 	},
 
diff --git a/erpnext/crm/doctype/opportunity/opportunity.py b/erpnext/crm/doctype/opportunity/opportunity.py
index f4b6e91..6a5fead 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.py
+++ b/erpnext/crm/doctype/opportunity/opportunity.py
@@ -33,7 +33,6 @@
 	def after_insert(self):
 		if self.opportunity_from == "Lead":
 			frappe.get_doc("Lead", self.party_name).set_status(update=True)
-			self.disable_lead()
 
 			link_open_tasks(self.opportunity_from, self.party_name, self)
 			link_open_events(self.opportunity_from, self.party_name, self)
@@ -119,10 +118,6 @@
 				prospect.flags.ignore_mandatory = True
 				prospect.save()
 
-	def disable_lead(self):
-		if self.opportunity_from == "Lead":
-			frappe.db.set_value("Lead", self.party_name, {"disabled": 1, "docstatus": 1})
-
 	def make_new_lead_if_required(self):
 		"""Set lead against new opportunity"""
 		if (not self.get("party_name")) and self.contact_email:
diff --git a/erpnext/crm/report/lead_details/lead_details.py b/erpnext/crm/report/lead_details/lead_details.py
index 8660c73..7b8c43b 100644
--- a/erpnext/crm/report/lead_details/lead_details.py
+++ b/erpnext/crm/report/lead_details/lead_details.py
@@ -98,7 +98,7 @@
 			`tabAddress`.name=`tabDynamic Link`.parent)
 		WHERE
 			company = %(company)s
-			AND `tabLead`.creation BETWEEN %(from_date)s AND %(to_date)s
+			AND DATE(`tabLead`.creation) BETWEEN %(from_date)s AND %(to_date)s
 			{conditions}
 		ORDER BY
 			`tabLead`.creation asc """.format(
diff --git a/erpnext/crm/report/lost_opportunity/lost_opportunity.py b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
index 254511c..b37cfa4 100644
--- a/erpnext/crm/report/lost_opportunity/lost_opportunity.py
+++ b/erpnext/crm/report/lost_opportunity/lost_opportunity.py
@@ -82,7 +82,7 @@
 			{join}
 		WHERE
 			`tabOpportunity`.status = 'Lost' and `tabOpportunity`.company = %(company)s
-			AND `tabOpportunity`.modified BETWEEN %(from_date)s AND %(to_date)s
+			AND DATE(`tabOpportunity`.modified) BETWEEN %(from_date)s AND %(to_date)s
 			{conditions}
 		GROUP BY
 			`tabOpportunity`.name
diff --git a/erpnext/e_commerce/doctype/website_item/test_website_item.py b/erpnext/e_commerce/doctype/website_item/test_website_item.py
index bbe04d5..43b2f67 100644
--- a/erpnext/e_commerce/doctype/website_item/test_website_item.py
+++ b/erpnext/e_commerce/doctype/website_item/test_website_item.py
@@ -199,8 +199,14 @@
 
 		breadcrumbs = get_parent_item_groups(item.item_group)
 
+		settings = frappe.get_cached_doc("E Commerce Settings")
+		if settings.enable_field_filters:
+			base_breadcrumb = "Shop by Category"
+		else:
+			base_breadcrumb = "All Products"
+
 		self.assertEqual(breadcrumbs[0]["name"], "Home")
-		self.assertEqual(breadcrumbs[1]["name"], "All Products")
+		self.assertEqual(breadcrumbs[1]["name"], base_breadcrumb)
 		self.assertEqual(breadcrumbs[2]["name"], "_Test Item Group B")  # parent item group
 		self.assertEqual(breadcrumbs[3]["name"], "_Test Item Group B - 1")
 
@@ -226,11 +232,11 @@
 		self.assertTrue(bool(data.product_info["price"]))
 
 		price_object = data.product_info["price"]
-		self.assertEqual(price_object.get("discount_percent"), 25)
+		self.assertEqual(price_object.get("discount_percent"), 25.0)
 		self.assertEqual(price_object.get("price_list_rate"), 750)
 		self.assertEqual(price_object.get("formatted_mrp"), "₹ 1,000.00")
 		self.assertEqual(price_object.get("formatted_price"), "₹ 750.00")
-		self.assertEqual(price_object.get("formatted_discount_percent"), "25%")
+		self.assertEqual(price_object.get("formatted_discount_percent"), "25.0%")
 
 		# switch to admin and disable show price
 		frappe.set_user("Administrator")
diff --git a/erpnext/e_commerce/variant_selector/utils.py b/erpnext/e_commerce/variant_selector/utils.py
index df62c23..1a3e737 100644
--- a/erpnext/e_commerce/variant_selector/utils.py
+++ b/erpnext/e_commerce/variant_selector/utils.py
@@ -1,5 +1,5 @@
 import frappe
-from frappe.utils import cint
+from frappe.utils import cint, flt
 
 from erpnext.e_commerce.doctype.e_commerce_settings.e_commerce_settings import (
 	get_shopping_cart_settings,
@@ -166,6 +166,27 @@
 	else:
 		product_info = None
 
+	product_id = ""
+	website_warehouse = ""
+	if exact_match or filtered_items:
+		if exact_match and len(exact_match) == 1:
+			product_id = exact_match[0]
+		elif filtered_items_count == 1:
+			product_id = list(filtered_items)[0]
+
+	if product_id:
+		website_warehouse = frappe.get_cached_value(
+			"Website Item", {"item_code": product_id}, "website_warehouse"
+		)
+
+	available_qty = 0.0
+	if website_warehouse:
+		available_qty = flt(
+			frappe.db.get_value(
+				"Bin", {"item_code": product_id, "warehouse": website_warehouse}, "actual_qty"
+			)
+		)
+
 	return {
 		"next_attribute": next_attribute,
 		"valid_options_for_attributes": valid_options_for_attributes,
@@ -173,6 +194,7 @@
 		"filtered_items": filtered_items if filtered_items_count < 10 else [],
 		"exact_match": exact_match,
 		"product_info": product_info,
+		"available_qty": available_qty,
 	}
 
 
diff --git a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
index f3aa6a3..e57a30a 100644
--- a/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
+++ b/erpnext/erpnext_integrations/doctype/plaid_settings/plaid_settings.py
@@ -220,7 +220,7 @@
 		if e.code == "ITEM_LOGIN_REQUIRED":
 			msg = _("There was an error syncing transactions.") + " "
 			msg += _("Please refresh or reset the Plaid linking of the Bank {}.").format(bank) + " "
-			frappe.log_error(msg, title=_("Plaid Link Refresh Required"))
+			frappe.log_error(message=msg, title=_("Plaid Link Refresh Required"))
 
 	return transactions
 
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index fba886c..862a546 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -28,6 +28,10 @@
 
 override_doctype_class = {"Address": "erpnext.accounts.custom.address.ERPNextAddress"}
 
+override_whitelisted_methods = {
+	"frappe.www.contact.send_message": "erpnext.templates.utils.send_message"
+}
+
 welcome_email = "erpnext.setup.utils.welcome_email"
 
 # setup wizard
@@ -275,7 +279,7 @@
 before_tests = "erpnext.setup.utils.before_tests"
 
 standard_queries = {
-	"Customer": "erpnext.selling.doctype.customer.customer.get_customer_list",
+	"Customer": "erpnext.controllers.queries.customer_query",
 }
 
 doc_events = {
@@ -356,7 +360,7 @@
 
 scheduler_events = {
 	"cron": {
-		"0/5 * * * *": [
+		"0/15 * * * *": [
 			"erpnext.manufacturing.doctype.bom_update_log.bom_update_log.resume_bom_cost_update_jobs",
 		],
 		"0/30 * * * *": [
@@ -510,6 +514,7 @@
 	"Subcontracting Order Item",
 	"Subcontracting Receipt",
 	"Subcontracting Receipt Item",
+	"Account Closing Balance",
 ]
 
 # get matching queries for Bank Reconciliation
diff --git a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
index 158f143..ba05355 100644
--- a/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
+++ b/erpnext/maintenance/doctype/maintenance_visit_purpose/maintenance_visit_purpose.json
@@ -64,8 +64,6 @@
    "fieldtype": "Section Break"
   },
   {
-   "fetch_from": "prevdoc_detail_docname.sales_person",
-   "fetch_if_empty": 1,
    "fieldname": "service_person",
    "fieldtype": "Link",
    "in_list_view": 1,
@@ -110,13 +108,15 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-05-27 17:47:21.474282",
+ "modified": "2023-02-27 11:09:33.114458",
  "modified_by": "Administrator",
  "module": "Maintenance",
  "name": "Maintenance Visit Purpose",
+ "naming_rule": "Random",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
index d3bb33e..7b26a14 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.js
@@ -7,6 +7,12 @@
 	},
 
 	setup: function(frm) {
+		frm.custom_make_buttons = {
+			'Purchase Order': 'Purchase Order',
+			'Sales Order': 'Sales Order',
+			'Quotation': 'Quotation',
+		};
+
 		frm.add_fetch("customer", "customer_name", "customer_name");
 		frm.add_fetch("supplier", "supplier_name", "supplier_name");
 	},
diff --git a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
index ff21401..32f1c36 100644
--- a/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/blanket_order.py
@@ -6,6 +6,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
+from frappe.query_builder.functions import Sum
 from frappe.utils import flt, getdate
 
 from erpnext.stock.doctype.item.item import get_item_defaults
@@ -29,21 +30,23 @@
 
 	def update_ordered_qty(self):
 		ref_doctype = "Sales Order" if self.blanket_order_type == "Selling" else "Purchase Order"
+
+		trans = frappe.qb.DocType(ref_doctype)
+		trans_item = frappe.qb.DocType(f"{ref_doctype} Item")
+
 		item_ordered_qty = frappe._dict(
-			frappe.db.sql(
-				"""
-			select trans_item.item_code, sum(trans_item.stock_qty) as qty
-			from `tab{0} Item` trans_item, `tab{0}` trans
-			where trans.name = trans_item.parent
-				and trans_item.blanket_order=%s
-				and trans.docstatus=1
-				and trans.status not in ('Closed', 'Stopped')
-			group by trans_item.item_code
-		""".format(
-					ref_doctype
-				),
-				self.name,
-			)
+			(
+				frappe.qb.from_(trans_item)
+				.from_(trans)
+				.select(trans_item.item_code, Sum(trans_item.stock_qty).as_("qty"))
+				.where(
+					(trans.name == trans_item.parent)
+					& (trans_item.blanket_order == self.name)
+					& (trans.docstatus == 1)
+					& (trans.status.notin(["Stopped", "Closed"]))
+				)
+				.groupby(trans_item.item_code)
+			).run()
 		)
 
 		for d in self.items:
@@ -79,7 +82,43 @@
 				"doctype": doctype + " Item",
 				"field_map": {"rate": "blanket_order_rate", "parent": "blanket_order"},
 				"postprocess": update_item,
+				"condition": lambda item: (flt(item.qty) - flt(item.ordered_qty)) > 0,
 			},
 		},
 	)
 	return target_doc
+
+
+def validate_against_blanket_order(order_doc):
+	if order_doc.doctype in ("Sales Order", "Purchase Order"):
+		order_data = {}
+
+		for item in order_doc.get("items"):
+			if item.against_blanket_order and item.blanket_order:
+				if item.blanket_order in order_data:
+					if item.item_code in order_data[item.blanket_order]:
+						order_data[item.blanket_order][item.item_code] += item.qty
+					else:
+						order_data[item.blanket_order][item.item_code] = item.qty
+				else:
+					order_data[item.blanket_order] = {item.item_code: item.qty}
+
+		if order_data:
+			allowance = flt(
+				frappe.db.get_single_value(
+					"Selling Settings" if order_doc.doctype == "Sales Order" else "Buying Settings",
+					"over_order_allowance",
+				)
+			)
+			for bo_name, item_data in order_data.items():
+				bo_doc = frappe.get_doc("Blanket Order", bo_name)
+				for item in bo_doc.get("items"):
+					if item.item_code in item_data:
+						remaining_qty = item.qty - item.ordered_qty
+						allowed_qty = remaining_qty + (remaining_qty * (allowance / 100))
+						if allowed_qty < item_data[item.item_code]:
+							frappe.throw(
+								_("Item {0} cannot be ordered more than {1} against Blanket Order {2}.").format(
+									item.item_code, allowed_qty, bo_name
+								)
+							)
diff --git a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
index 2f1f3ae..58f3c95 100644
--- a/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
+++ b/erpnext/manufacturing/doctype/blanket_order/test_blanket_order.py
@@ -63,6 +63,33 @@
 		po1.currency = get_company_currency(po1.company)
 		self.assertEqual(po1.items[0].qty, (bo.items[0].qty - bo.items[0].ordered_qty))
 
+	def test_over_order_allowance(self):
+		# Sales Order
+		bo = make_blanket_order(blanket_order_type="Selling", quantity=100)
+
+		frappe.flags.args.doctype = "Sales Order"
+		so = make_order(bo.name)
+		so.currency = get_company_currency(so.company)
+		so.delivery_date = today()
+		so.items[0].qty = 110
+		self.assertRaises(frappe.ValidationError, so.submit)
+
+		frappe.db.set_single_value("Selling Settings", "over_order_allowance", 10)
+		so.submit()
+
+		# Purchase Order
+		bo = make_blanket_order(blanket_order_type="Purchasing", quantity=100)
+
+		frappe.flags.args.doctype = "Purchase Order"
+		po = make_order(bo.name)
+		po.currency = get_company_currency(po.company)
+		po.schedule_date = today()
+		po.items[0].qty = 110
+		self.assertRaises(frappe.ValidationError, po.submit)
+
+		frappe.db.set_single_value("Buying Settings", "over_order_allowance", 10)
+		po.submit()
+
 
 def make_blanket_order(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index db699b9..d024022 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -9,15 +9,14 @@
   "production_item_tab",
   "item",
   "company",
-  "item_name",
   "uom",
+  "quantity",
   "cb0",
   "is_active",
   "is_default",
   "allow_alternative_item",
   "set_rate_of_sub_assembly_item_based_on_bom",
   "project",
-  "quantity",
   "image",
   "currency_detail",
   "rm_cost_as_per",
@@ -27,6 +26,8 @@
   "column_break_ivyw",
   "currency",
   "conversion_rate",
+  "materials_section",
+  "items",
   "section_break_21",
   "operations_section_section",
   "with_operations",
@@ -38,8 +39,6 @@
   "operating_cost_per_bom_quantity",
   "operations_section",
   "operations",
-  "materials_section",
-  "items",
   "scrap_section",
   "scrap_items_section",
   "scrap_items",
@@ -59,6 +58,7 @@
   "total_cost",
   "base_total_cost",
   "more_info_tab",
+  "item_name",
   "description",
   "column_break_27",
   "has_variants",
@@ -192,6 +192,7 @@
    "options": "Quality Inspection Template"
   },
   {
+   "collapsible": 1,
    "fieldname": "currency_detail",
    "fieldtype": "Section Break",
    "label": "Cost Configuration"
@@ -417,7 +418,7 @@
   {
    "collapsible": 1,
    "fieldname": "website_section",
-   "fieldtype": "Section Break",
+   "fieldtype": "Tab Break",
    "label": "Website"
   },
   {
@@ -482,7 +483,7 @@
   {
    "fieldname": "section_break_21",
    "fieldtype": "Tab Break",
-   "label": "Operations & Materials"
+   "label": "Operations"
   },
   {
    "fieldname": "column_break_23",
@@ -605,7 +606,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-02-13 17:31:37.504565",
+ "modified": "2023-04-06 12:47:58.514795",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 8ab79e6..a085af8 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -31,7 +31,7 @@
 
 	# specifying the attributes to save resources
 	# ref: https://docs.python.org/3/reference/datamodel.html#slots
-	__slots__ = ["name", "child_items", "is_bom", "item_code", "exploded_qty", "qty"]
+	__slots__ = ["name", "child_items", "is_bom", "item_code", "qty", "exploded_qty", "bom_qty"]
 
 	def __init__(
 		self, name: str, is_bom: bool = True, exploded_qty: float = 1.0, qty: float = 1
@@ -50,9 +50,10 @@
 	def __create_tree(self):
 		bom = frappe.get_cached_doc("BOM", self.name)
 		self.item_code = bom.item
+		self.bom_qty = bom.quantity
 
 		for item in bom.get("items", []):
-			qty = item.qty / bom.quantity  # quantity per unit
+			qty = item.stock_qty / bom.quantity  # quantity per unit
 			exploded_qty = self.exploded_qty * qty
 			if item.bom_no:
 				child = BOMTree(item.bom_no, exploded_qty=exploded_qty, qty=qty)
@@ -942,7 +943,8 @@
 	2) If no value, get last valuation rate from SLE
 	3) If no value, get valuation rate from Item
 	"""
-	from frappe.query_builder.functions import Sum
+	from frappe.query_builder.functions import Count, IfNull, Sum
+	from pypika import Case
 
 	item_code, company = data.get("item_code"), data.get("company")
 	valuation_rate = 0.0
@@ -953,7 +955,14 @@
 		frappe.qb.from_(bin_table)
 		.join(wh_table)
 		.on(bin_table.warehouse == wh_table.name)
-		.select((Sum(bin_table.stock_value) / Sum(bin_table.actual_qty)).as_("valuation_rate"))
+		.select(
+			Case()
+			.when(
+				Count(bin_table.name) > 0, IfNull(Sum(bin_table.stock_value) / Sum(bin_table.actual_qty), 0.0)
+			)
+			.else_(None)
+			.as_("valuation_rate")
+		)
 		.where((bin_table.item_code == item_code) & (wh_table.company == company))
 	).run(as_dict=True)[0]
 
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index d60feb2..01bf2e4 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -6,7 +6,7 @@
 from functools import partial
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, timeout
 from frappe.utils import cstr, flt
 
 from erpnext.controllers.tests.test_subcontracting_controller import (
@@ -27,6 +27,7 @@
 
 
 class TestBOM(FrappeTestCase):
+	@timeout
 	def test_get_items(self):
 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
 
@@ -37,6 +38,7 @@
 		self.assertTrue(test_records[2]["items"][1]["item_code"] in items_dict)
 		self.assertEqual(len(items_dict.values()), 2)
 
+	@timeout
 	def test_get_items_exploded(self):
 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
 
@@ -49,11 +51,13 @@
 		self.assertTrue(test_records[0]["items"][1]["item_code"] in items_dict)
 		self.assertEqual(len(items_dict.values()), 3)
 
+	@timeout
 	def test_get_items_list(self):
 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items
 
 		self.assertEqual(len(get_bom_items(bom=get_default_bom(), company="_Test Company")), 3)
 
+	@timeout
 	def test_default_bom(self):
 		def _get_default_bom_in_item():
 			return cstr(frappe.db.get_value("Item", "_Test FG Item 2", "default_bom"))
@@ -71,6 +75,7 @@
 
 		self.assertTrue(_get_default_bom_in_item(), bom.name)
 
+	@timeout
 	def test_update_bom_cost_in_all_boms(self):
 		# get current rate for '_Test Item 2'
 		bom_rates = frappe.db.get_values(
@@ -99,6 +104,7 @@
 		):
 			self.assertEqual(d.base_rate, rm_base_rate + 10)
 
+	@timeout
 	def test_bom_cost(self):
 		bom = frappe.copy_doc(test_records[2])
 		bom.insert()
@@ -127,6 +133,7 @@
 		self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
 		self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
 
+	@timeout
 	def test_bom_cost_with_batch_size(self):
 		bom = frappe.copy_doc(test_records[2])
 		bom.docstatus = 0
@@ -145,6 +152,7 @@
 		self.assertAlmostEqual(bom.operating_cost, op_cost / 2)
 		bom.delete()
 
+	@timeout
 	def test_bom_cost_multi_uom_multi_currency_based_on_price_list(self):
 		frappe.db.set_value("Price List", "_Test Price List", "price_not_uom_dependent", 1)
 		for item_code, rate in (("_Test Item", 3600), ("_Test Item Home Desktop Manufactured", 3000)):
@@ -181,6 +189,7 @@
 		self.assertEqual(bom.base_raw_material_cost, 27000)
 		self.assertEqual(bom.base_total_cost, 33000)
 
+	@timeout
 	def test_bom_cost_multi_uom_based_on_valuation_rate(self):
 		bom = frappe.copy_doc(test_records[2])
 		bom.set_rate_of_sub_assembly_item_based_on_bom = 0
@@ -202,6 +211,7 @@
 
 		self.assertEqual(bom.items[0].rate, 20)
 
+	@timeout
 	def test_bom_cost_with_fg_based_operating_cost(self):
 		bom = frappe.copy_doc(test_records[4])
 		bom.insert()
@@ -229,6 +239,7 @@
 		self.assertAlmostEqual(bom.base_raw_material_cost, base_raw_material_cost)
 		self.assertAlmostEqual(bom.base_total_cost, base_raw_material_cost + base_op_cost)
 
+	@timeout
 	def test_subcontractor_sourced_item(self):
 		item_code = "_Test Subcontracted FG Item 1"
 		set_backflush_based_on("Material Transferred for Subcontract")
@@ -310,6 +321,7 @@
 		supplied_items = sorted([d.rm_item_code for d in sco.supplied_items])
 		self.assertEqual(bom_items, supplied_items)
 
+	@timeout
 	def test_bom_tree_representation(self):
 		bom_tree = {
 			"Assembly": {
@@ -335,6 +347,7 @@
 		for reqd_item, created_item in zip(reqd_order, created_order):
 			self.assertEqual(reqd_item, created_item.item_code)
 
+	@timeout
 	def test_generated_variant_bom(self):
 		from erpnext.controllers.item_variant import create_variant
 
@@ -375,6 +388,7 @@
 			self.assertEqual(reqd_item.qty, created_item.qty)
 			self.assertEqual(reqd_item.exploded_qty, created_item.exploded_qty)
 
+	@timeout
 	def test_bom_recursion_1st_level(self):
 		"""BOM should not allow BOM item again in child"""
 		item_code = make_item(properties={"is_stock_item": 1}).name
@@ -387,6 +401,7 @@
 			bom.items[0].bom_no = bom.name
 			bom.save()
 
+	@timeout
 	def test_bom_recursion_transitive(self):
 		item1 = make_item(properties={"is_stock_item": 1}).name
 		item2 = make_item(properties={"is_stock_item": 1}).name
@@ -408,6 +423,7 @@
 			bom1.save()
 			bom2.save()
 
+	@timeout
 	def test_bom_with_process_loss_item(self):
 		fg_item_non_whole, fg_item_whole, bom_item = create_process_loss_bom_items()
 
@@ -421,6 +437,7 @@
 		#  Items with whole UOMs can't be PL Items
 		self.assertRaises(frappe.ValidationError, bom_doc.submit)
 
+	@timeout
 	def test_bom_item_query(self):
 		query = partial(
 			item_query,
@@ -440,6 +457,7 @@
 		)
 		self.assertTrue(0 < len(filtered) <= 3, msg="Item filtering showing excessive results")
 
+	@timeout
 	def test_exclude_exploded_items_from_bom(self):
 		bom_no = get_default_bom()
 		new_bom = frappe.copy_doc(frappe.get_doc("BOM", bom_no))
@@ -458,6 +476,7 @@
 
 		new_bom.delete()
 
+	@timeout
 	def test_valid_transfer_defaults(self):
 		bom_with_op = frappe.db.get_value(
 			"BOM", {"item": "_Test FG Item 2", "with_operations": 1, "is_active": 1}
@@ -489,11 +508,13 @@
 		self.assertEqual(bom.transfer_material_against, "Work Order")
 		bom.delete()
 
+	@timeout
 	def test_bom_name_length(self):
 		"""test >140 char names"""
 		bom_tree = {"x" * 140: {" ".join(["abc"] * 35): {}}}
 		create_nested_bom(bom_tree, prefix="")
 
+	@timeout
 	def test_version_index(self):
 
 		bom = frappe.new_doc("BOM")
@@ -515,6 +536,7 @@
 					msg=f"Incorrect index for {existing_boms}",
 				)
 
+	@timeout
 	def test_bom_versioning(self):
 		bom_tree = {frappe.generate_hash(length=10): {frappe.generate_hash(length=10): {}}}
 		bom = create_nested_bom(bom_tree, prefix="")
@@ -547,6 +569,7 @@
 		self.assertNotEqual(amendment.name, version.name)
 		self.assertEqual(int(version.name.split("-")[-1]), 2)
 
+	@timeout
 	def test_clear_inpection_quality(self):
 
 		bom = frappe.copy_doc(test_records[2], ignore_no_copy=True)
@@ -565,6 +588,7 @@
 
 		self.assertEqual(bom.quality_inspection_template, None)
 
+	@timeout
 	def test_bom_pricing_based_on_lpp(self):
 		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
 
@@ -585,6 +609,7 @@
 		bom.submit()
 		self.assertEqual(bom.items[0].rate, 42)
 
+	@timeout
 	def test_set_default_bom_for_item_having_single_bom(self):
 		from erpnext.stock.doctype.item.test_item import make_item
 
@@ -621,6 +646,7 @@
 		bom.reload()
 		self.assertEqual(frappe.get_value("Item", fg_item.item_code, "default_bom"), bom.name)
 
+	@timeout
 	def test_exploded_items_rate(self):
 		rm_item = make_item(
 			properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
@@ -649,6 +675,7 @@
 		bom.submit()
 		self.assertEqual(bom.exploded_items[0].rate, bom.items[0].base_rate)
 
+	@timeout
 	def test_bom_cost_update_flag(self):
 		rm_item = make_item(
 			properties={"is_stock_item": 1, "valuation_rate": 99, "last_purchase_rate": 89}
diff --git a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
index c3f52d4..7477f95 100644
--- a/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
+++ b/erpnext/manufacturing/doctype/bom_update_log/bom_update_log.py
@@ -164,7 +164,7 @@
 
 	while current_boms_list:
 		batch_no += 1
-		batch_size = 20_000
+		batch_size = 7_000
 		boms_to_process = current_boms_list[:batch_size]  # slice out batch of 20k BOMs
 
 		# update list to exclude 20K (queued) BOMs
diff --git a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
index 5dd557f..2026f62 100644
--- a/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
+++ b/erpnext/manufacturing/doctype/bom_update_tool/test_bom_update_tool.py
@@ -2,7 +2,7 @@
 # License: GNU General Public License v3. See license.txt
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, timeout
 
 from erpnext.manufacturing.doctype.bom_update_log.test_bom_update_log import (
 	update_cost_in_all_boms_in_test,
@@ -20,6 +20,7 @@
 	def tearDown(self):
 		frappe.db.rollback()
 
+	@timeout
 	def test_replace_bom(self):
 		current_bom = "BOM-_Test Item Home Desktop Manufactured-001"
 
@@ -33,6 +34,7 @@
 		self.assertFalse(frappe.db.exists("BOM Item", {"bom_no": current_bom, "docstatus": 1}))
 		self.assertTrue(frappe.db.exists("BOM Item", {"bom_no": bom_doc.name, "docstatus": 1}))
 
+	@timeout
 	def test_bom_cost(self):
 		for item in ["BOM Cost Test Item 1", "BOM Cost Test Item 2", "BOM Cost Test Item 3"]:
 			item_doc = create_item(item, valuation_rate=100)
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.js b/erpnext/manufacturing/doctype/job_card/job_card.js
index 619e6bd..5305db3 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.js
+++ b/erpnext/manufacturing/doctype/job_card/job_card.js
@@ -83,7 +83,7 @@
 			// and if stock mvt for WIP is required
 			if (frm.doc.work_order) {
 				frappe.db.get_value('Work Order', frm.doc.work_order, ['skip_transfer', 'status'], (result) => {
-					if (result.skip_transfer === 1 || result.status == 'In Process') {
+					if (result.skip_transfer === 1 || result.status == 'In Process' || frm.doc.transferred_qty > 0) {
 						frm.trigger("prepare_timer_buttons");
 					}
 				});
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 3133628..e82f379 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -561,7 +561,34 @@
 		)
 
 	def set_transferred_qty_in_job_card_item(self, ste_doc):
-		from frappe.query_builder.functions import Sum
+		def _get_job_card_items_transferred_qty(ste_doc):
+			from frappe.query_builder.functions import Sum
+
+			job_card_items_transferred_qty = {}
+			job_card_items = [
+				x.get("job_card_item") for x in ste_doc.get("items") if x.get("job_card_item")
+			]
+
+			if job_card_items:
+				se = frappe.qb.DocType("Stock Entry")
+				sed = frappe.qb.DocType("Stock Entry Detail")
+
+				query = (
+					frappe.qb.from_(sed)
+					.join(se)
+					.on(sed.parent == se.name)
+					.select(sed.job_card_item, Sum(sed.qty))
+					.where(
+						(sed.job_card_item.isin(job_card_items))
+						& (se.docstatus == 1)
+						& (se.purpose == "Material Transfer for Manufacture")
+					)
+					.groupby(sed.job_card_item)
+				)
+
+				job_card_items_transferred_qty = frappe._dict(query.run(as_list=True))
+
+			return job_card_items_transferred_qty
 
 		def _validate_over_transfer(row, transferred_qty):
 			"Block over transfer of items if not allowed in settings."
@@ -578,29 +605,23 @@
 					exc=JobCardOverTransferError,
 				)
 
-		for row in ste_doc.items:
-			if not row.job_card_item:
-				continue
+		job_card_items_transferred_qty = _get_job_card_items_transferred_qty(ste_doc)
 
-			sed = frappe.qb.DocType("Stock Entry Detail")
-			se = frappe.qb.DocType("Stock Entry")
-			transferred_qty = (
-				frappe.qb.from_(sed)
-				.join(se)
-				.on(sed.parent == se.name)
-				.select(Sum(sed.qty))
-				.where(
-					(sed.job_card_item == row.job_card_item)
-					& (se.docstatus == 1)
-					& (se.purpose == "Material Transfer for Manufacture")
-				)
-			).run()[0][0]
-
+		if job_card_items_transferred_qty:
 			allow_excess = frappe.db.get_single_value("Manufacturing Settings", "job_card_excess_transfer")
-			if not allow_excess:
-				_validate_over_transfer(row, transferred_qty)
 
-			frappe.db.set_value("Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty))
+			for row in ste_doc.items:
+				if not row.job_card_item:
+					continue
+
+				transferred_qty = flt(job_card_items_transferred_qty.get(row.job_card_item))
+
+				if not allow_excess:
+					_validate_over_transfer(row, transferred_qty)
+
+				frappe.db.set_value(
+					"Job Card Item", row.job_card_item, "transferred_qty", flt(transferred_qty)
+				)
 
 	def set_transferred_qty(self, update_status=False):
 		"Set total FG Qty in Job Card for which RM was transferred."
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.json b/erpnext/manufacturing/doctype/production_plan/production_plan.json
index 2624daa..fdaa4a2 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.json
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.json
@@ -344,6 +344,7 @@
   {
    "fieldname": "prod_plan_references",
    "fieldtype": "Table",
+   "hidden": 1,
    "label": "Production Plan Item Reference",
    "options": "Production Plan Item Reference"
   },
@@ -397,7 +398,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-11-26 14:51:08.774372",
+ "modified": "2023-03-31 10:30:48.118932",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan",
diff --git a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
index 84dee4a..15ef207 100644
--- a/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
+++ b/erpnext/manufacturing/doctype/production_plan_item_reference/production_plan_item_reference.json
@@ -28,7 +28,7 @@
    "fieldname": "qty",
    "fieldtype": "Data",
    "in_list_view": 1,
-   "label": "qty"
+   "label": "Qty"
   },
   {
    "fieldname": "item_reference",
@@ -40,7 +40,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-05-07 17:03:49.707487",
+ "modified": "2023-03-31 10:30:14.604051",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Production Plan Item Reference",
@@ -48,5 +48,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 4aff42c..97480b2 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -506,7 +506,7 @@
 				callback: function(r) {
 					if (r.message) {
 						frappe.model.set_value(cdt, cdn, {
-							"required_qty": 1,
+							"required_qty": row.required_qty || 1,
 							"item_name": r.message.item_name,
 							"description": r.message.description,
 							"source_warehouse": r.message.default_warehouse,
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.json b/erpnext/manufacturing/doctype/work_order/work_order.json
index 25e16d6..aa90498 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.json
+++ b/erpnext/manufacturing/doctype/work_order/work_order.json
@@ -22,17 +22,13 @@
   "produced_qty",
   "process_loss_qty",
   "project",
-  "serial_no_and_batch_for_finished_good_section",
-  "has_serial_no",
-  "has_batch_no",
-  "column_break_17",
-  "serial_no",
-  "batch_size",
+  "section_break_ndpq",
+  "required_items",
   "work_order_configuration",
   "settings_section",
   "allow_alternative_item",
   "use_multi_level_bom",
-  "column_break_18",
+  "column_break_17",
   "skip_transfer",
   "from_wip_warehouse",
   "update_consumed_material_cost_in_project",
@@ -42,9 +38,14 @@
   "column_break_12",
   "fg_warehouse",
   "scrap_warehouse",
+  "serial_no_and_batch_for_finished_good_section",
+  "has_serial_no",
+  "has_batch_no",
+  "column_break_18",
+  "serial_no",
+  "batch_size",
   "required_items_section",
   "materials_and_operations_tab",
-  "required_items",
   "operations_section",
   "operations",
   "transfer_material_against",
@@ -586,7 +587,11 @@
   {
    "fieldname": "materials_and_operations_tab",
    "fieldtype": "Tab Break",
-   "label": "Materials & Operations"
+   "label": "Operations"
+  },
+  {
+   "fieldname": "section_break_ndpq",
+   "fieldtype": "Section Break"
   }
  ],
  "icon": "fa fa-cogs",
@@ -594,7 +599,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-03 14:16:35.427731",
+ "modified": "2023-04-06 12:35:12.149827",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Work Order",
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index ae9e9c6..66b871c 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -682,7 +682,7 @@
 
 			for node in bom_traversal:
 				if node.is_bom:
-					operations.extend(_get_operations(node.name, qty=node.exploded_qty))
+					operations.extend(_get_operations(node.name, qty=node.exploded_qty / node.bom_qty))
 
 		bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
 		operations.extend(_get_operations(self.bom_no, qty=1.0 / bom_qty))
diff --git a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
index cdf1541..3573a3a 100644
--- a/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
+++ b/erpnext/manufacturing/report/bom_stock_report/bom_stock_report.py
@@ -4,7 +4,8 @@
 
 import frappe
 from frappe import _
-from frappe.query_builder.functions import Sum
+from frappe.query_builder.functions import Floor, Sum
+from frappe.utils import cint
 from pypika.terms import ExistsCriterion
 
 
@@ -34,57 +35,55 @@
 
 
 def get_bom_stock(filters):
-	qty_to_produce = filters.get("qty_to_produce") or 1
-	if int(qty_to_produce) < 0:
-		frappe.throw(_("Quantity to Produce can not be less than Zero"))
+	qty_to_produce = filters.get("qty_to_produce")
+	if cint(qty_to_produce) <= 0:
+		frappe.throw(_("Quantity to Produce should be greater than zero."))
 
 	if filters.get("show_exploded_view"):
 		bom_item_table = "BOM Explosion Item"
 	else:
 		bom_item_table = "BOM Item"
 
-	bin = frappe.qb.DocType("Bin")
-	bom = frappe.qb.DocType("BOM")
-	bom_item = frappe.qb.DocType(bom_item_table)
-
-	query = (
-		frappe.qb.from_(bom)
-		.inner_join(bom_item)
-		.on(bom.name == bom_item.parent)
-		.left_join(bin)
-		.on(bom_item.item_code == bin.item_code)
-		.select(
-			bom_item.item_code,
-			bom_item.description,
-			bom_item.stock_qty,
-			bom_item.stock_uom,
-			(bom_item.stock_qty / bom.quantity) * qty_to_produce,
-			Sum(bin.actual_qty),
-			Sum(bin.actual_qty) / (bom_item.stock_qty / bom.quantity),
-		)
-		.where((bom_item.parent == filters.get("bom")) & (bom_item.parenttype == "BOM"))
-		.groupby(bom_item.item_code)
+	warehouse_details = frappe.db.get_value(
+		"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
 	)
 
-	if filters.get("warehouse"):
-		warehouse_details = frappe.db.get_value(
-			"Warehouse", filters.get("warehouse"), ["lft", "rgt"], as_dict=1
-		)
+	BOM = frappe.qb.DocType("BOM")
+	BOM_ITEM = frappe.qb.DocType(bom_item_table)
+	BIN = frappe.qb.DocType("Bin")
+	WH = frappe.qb.DocType("Warehouse")
+	CONDITIONS = ()
 
-		if warehouse_details:
-			wh = frappe.qb.DocType("Warehouse")
-			query = query.where(
-				ExistsCriterion(
-					frappe.qb.from_(wh)
-					.select(wh.name)
-					.where(
-						(wh.lft >= warehouse_details.lft)
-						& (wh.rgt <= warehouse_details.rgt)
-						& (bin.warehouse == wh.name)
-					)
-				)
+	if warehouse_details:
+		CONDITIONS = ExistsCriterion(
+			frappe.qb.from_(WH)
+			.select(WH.name)
+			.where(
+				(WH.lft >= warehouse_details.lft)
+				& (WH.rgt <= warehouse_details.rgt)
+				& (BIN.warehouse == WH.name)
 			)
-		else:
-			query = query.where(bin.warehouse == filters.get("warehouse"))
+		)
+	else:
+		CONDITIONS = BIN.warehouse == filters.get("warehouse")
 
-	return query.run()
+	QUERY = (
+		frappe.qb.from_(BOM)
+		.inner_join(BOM_ITEM)
+		.on(BOM.name == BOM_ITEM.parent)
+		.left_join(BIN)
+		.on((BOM_ITEM.item_code == BIN.item_code) & (CONDITIONS))
+		.select(
+			BOM_ITEM.item_code,
+			BOM_ITEM.description,
+			BOM_ITEM.stock_qty,
+			BOM_ITEM.stock_uom,
+			BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity,
+			Sum(BIN.actual_qty).as_("actual_qty"),
+			Sum(Floor(BIN.actual_qty / (BOM_ITEM.stock_qty * qty_to_produce / BOM.quantity))),
+		)
+		.where((BOM_ITEM.parent == filters.get("bom")) & (BOM_ITEM.parenttype == "BOM"))
+		.groupby(BOM_ITEM.item_code)
+	)
+
+	return QUERY.run()
diff --git a/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py b/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py
new file mode 100644
index 0000000..1c56ebe
--- /dev/null
+++ b/erpnext/manufacturing/report/bom_stock_report/test_bom_stock_report.py
@@ -0,0 +1,108 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+
+import frappe
+from frappe.exceptions import ValidationError
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import floor
+
+from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+from erpnext.manufacturing.report.bom_stock_report.bom_stock_report import (
+	get_bom_stock as bom_stock_report,
+)
+from erpnext.stock.doctype.item.test_item import make_item
+from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+
+class TestBomStockReport(FrappeTestCase):
+	def setUp(self):
+		self.warehouse = "_Test Warehouse - _TC"
+		self.fg_item, self.rm_items = create_items()
+		make_stock_entry(target=self.warehouse, item_code=self.rm_items[0], qty=20, basic_rate=100)
+		make_stock_entry(target=self.warehouse, item_code=self.rm_items[1], qty=40, basic_rate=200)
+		self.bom = make_bom(item=self.fg_item, quantity=1, raw_materials=self.rm_items, rm_qty=10)
+
+	def test_bom_stock_report(self):
+		# Test 1: When `qty_to_produce` is 0.
+		filters = frappe._dict(
+			{
+				"bom": self.bom.name,
+				"warehouse": "Stores - _TC",
+				"qty_to_produce": 0,
+			}
+		)
+		self.assertRaises(ValidationError, bom_stock_report, filters)
+
+		# Test 2: When stock is not available.
+		data = bom_stock_report(
+			frappe._dict(
+				{
+					"bom": self.bom.name,
+					"warehouse": "Stores - _TC",
+					"qty_to_produce": 1,
+				}
+			)
+		)
+		expected_data = get_expected_data(self.bom, "Stores - _TC", 1)
+		self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
+
+		# Test 3: When stock is available.
+		data = bom_stock_report(
+			frappe._dict(
+				{
+					"bom": self.bom.name,
+					"warehouse": self.warehouse,
+					"qty_to_produce": 1,
+				}
+			)
+		)
+		expected_data = get_expected_data(self.bom, self.warehouse, 1)
+		self.assertSetEqual(set(tuple(x) for x in data), set(tuple(x) for x in expected_data))
+
+
+def create_items():
+	fg_item = make_item(properties={"is_stock_item": 1}).name
+	rm_item1 = make_item(
+		properties={
+			"is_stock_item": 1,
+			"standard_rate": 100,
+			"opening_stock": 100,
+			"last_purchase_rate": 100,
+		}
+	).name
+	rm_item2 = make_item(
+		properties={
+			"is_stock_item": 1,
+			"standard_rate": 200,
+			"opening_stock": 200,
+			"last_purchase_rate": 200,
+		}
+	).name
+
+	return fg_item, [rm_item1, rm_item2]
+
+
+def get_expected_data(bom, warehouse, qty_to_produce, show_exploded_view=False):
+	expected_data = []
+
+	for item in bom.get("exploded_items") if show_exploded_view else bom.get("items"):
+		in_stock_qty = frappe.get_cached_value(
+			"Bin", {"item_code": item.item_code, "warehouse": warehouse}, "actual_qty"
+		)
+
+		expected_data.append(
+			[
+				item.item_code,
+				item.description,
+				item.stock_qty,
+				item.stock_uom,
+				item.stock_qty * qty_to_produce / bom.quantity,
+				in_stock_qty,
+				floor(in_stock_qty / (item.stock_qty * qty_to_produce / bom.quantity))
+				if in_stock_qty
+				else None,
+			]
+		)
+
+	return expected_data
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 2abd65b..3357b06 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -322,7 +322,12 @@
 erpnext.patches.v14_0.update_entry_type_for_journal_entry
 erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
 erpnext.patches.v14_0.set_pick_list_status
+erpnext.patches.v13_0.update_docs_link
 erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
-# below 2 migration patches should always run last
+erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
+erpnext.patches.v14_0.create_accounting_dimensions_for_closing_balance
+erpnext.patches.v14_0.update_closing_balances
+# below migration patches should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
-erpnext.patches.v14_0.migrate_remarks_from_gl_to_payment_ledger
+execute:frappe.delete_doc_if_exists("Report", "Tax Detail")
+erpnext.patches.v15_0.enable_all_leads
diff --git a/erpnext/patches/v13_0/update_docs_link.py b/erpnext/patches/v13_0/update_docs_link.py
new file mode 100644
index 0000000..4bc5c05
--- /dev/null
+++ b/erpnext/patches/v13_0/update_docs_link.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: MIT. See LICENSE
+
+
+import frappe
+
+
+def execute():
+	navbar_settings = frappe.get_single("Navbar Settings")
+	for item in navbar_settings.help_dropdown:
+		if item.is_standard and item.route == "https://erpnext.com/docs/user/manual":
+			item.route = "https://docs.erpnext.com/docs/v14/user/manual/en/introduction"
+
+	navbar_settings.save()
diff --git a/erpnext/patches/v14_0/create_accounting_dimensions_for_closing_balance.py b/erpnext/patches/v14_0/create_accounting_dimensions_for_closing_balance.py
new file mode 100644
index 0000000..43ad0d7
--- /dev/null
+++ b/erpnext/patches/v14_0/create_accounting_dimensions_for_closing_balance.py
@@ -0,0 +1,31 @@
+import frappe
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+
+
+def execute():
+	accounting_dimensions = frappe.db.get_all(
+		"Accounting Dimension", fields=["fieldname", "label", "document_type", "disabled"]
+	)
+
+	if not accounting_dimensions:
+		return
+
+	doctype = "Account Closing Balance"
+
+	for d in accounting_dimensions:
+		field = frappe.db.get_value("Custom Field", {"dt": doctype, "fieldname": d.fieldname})
+
+		if field:
+			continue
+
+		df = {
+			"fieldname": d.fieldname,
+			"label": d.label,
+			"fieldtype": "Link",
+			"options": d.document_type,
+			"insert_after": "accounting_dimensions_section",
+		}
+
+		create_custom_field(doctype, df, ignore_validate=True)
+
+	frappe.clear_cache(doctype=doctype)
diff --git a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
index 853a99a..72c8c07 100644
--- a/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
+++ b/erpnext/patches/v14_0/migrate_gl_to_payment_ledger.py
@@ -1,6 +1,6 @@
 import frappe
 from frappe import qb
-from frappe.query_builder import Case, CustomFunction
+from frappe.query_builder import CustomFunction
 from frappe.query_builder.custom import ConstantColumn
 from frappe.query_builder.functions import Count, IfNull
 from frappe.utils import flt
@@ -18,9 +18,21 @@
 			make_dimension_in_accounting_doctypes(dimension, ["Payment Ledger Entry"])
 
 
-def generate_name_for_payment_ledger_entries(gl_entries, start):
+def generate_name_and_calculate_amount(gl_entries, start, receivable_accounts):
 	for index, entry in enumerate(gl_entries, 0):
 		entry.name = start + index
+		if entry.account in receivable_accounts:
+			entry.account_type = "Receivable"
+			entry.amount = entry.debit - entry.credit
+			entry.amount_in_account_currency = (
+				entry.debit_in_account_currency - entry.credit_in_account_currency
+			)
+		else:
+			entry.account_type = "Payable"
+			entry.amount = entry.credit - entry.debit
+			entry.amount_in_account_currency = (
+				entry.credit_in_account_currency - entry.debit_in_account_currency
+			)
 
 
 def get_columns():
@@ -49,6 +61,9 @@
 		"finance_book",
 	]
 
+	if frappe.db.has_column("Payment Ledger Entry", "remarks"):
+		columns.append("remarks")
+
 	dimensions_and_defaults = get_dimensions()
 	if dimensions_and_defaults:
 		for dimension in dimensions_and_defaults[0]:
@@ -99,12 +114,17 @@
 		ifelse = CustomFunction("IF", ["condition", "then", "else"])
 
 		# Get Records Count
-		accounts = (
+		relavant_accounts = (
 			qb.from_(account)
-			.select(account.name)
+			.select(account.name, account.account_type)
 			.where((account.account_type == "Receivable") | (account.account_type == "Payable"))
 			.orderby(account.name)
+			.run(as_dict=True)
 		)
+
+		receivable_accounts = [x.name for x in relavant_accounts if x.account_type == "Receivable"]
+		accounts = [x.name for x in relavant_accounts]
+
 		un_processed = (
 			qb.from_(gl)
 			.select(Count(gl.name))
@@ -122,37 +142,21 @@
 
 			while True:
 				if last_name:
-					where_clause = gl.name.gt(last_name) & (gl.is_cancelled == 0)
+					where_clause = gl.name.gt(last_name) & gl.account.isin(accounts) & gl.is_cancelled == 0
 				else:
-					where_clause = gl.is_cancelled == 0
+					where_clause = gl.account.isin(accounts) & gl.is_cancelled == 0
 
 				gl_entries = (
 					qb.from_(gl)
-					.inner_join(account)
-					.on((gl.account == account.name) & (account.account_type.isin(["Receivable", "Payable"])))
 					.select(
 						gl.star,
 						ConstantColumn(1).as_("docstatus"),
-						account.account_type.as_("account_type"),
 						IfNull(
 							ifelse(gl.against_voucher_type == "", None, gl.against_voucher_type), gl.voucher_type
 						).as_("against_voucher_type"),
 						IfNull(ifelse(gl.against_voucher == "", None, gl.against_voucher), gl.voucher_no).as_(
 							"against_voucher_no"
 						),
-						# convert debit/credit to amount
-						Case()
-						.when(account.account_type == "Receivable", gl.debit - gl.credit)
-						.else_(gl.credit - gl.debit)
-						.as_("amount"),
-						# convert debit/credit in account currency to amount in account currency
-						Case()
-						.when(
-							account.account_type == "Receivable",
-							gl.debit_in_account_currency - gl.credit_in_account_currency,
-						)
-						.else_(gl.credit_in_account_currency - gl.debit_in_account_currency)
-						.as_("amount_in_account_currency"),
 					)
 					.where(where_clause)
 					.orderby(gl.name)
@@ -163,8 +167,8 @@
 				if gl_entries:
 					last_name = gl_entries[-1].name
 
-					# primary key(name) for payment ledger records
-					generate_name_for_payment_ledger_entries(gl_entries, processed)
+					# add primary key(name) and calculate based on debit and credit
+					generate_name_and_calculate_amount(gl_entries, processed, receivable_accounts)
 
 					try:
 						insert_query = build_insert_query()
diff --git a/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py b/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py
deleted file mode 100644
index 9d216c4..0000000
--- a/erpnext/patches/v14_0/migrate_remarks_from_gl_to_payment_ledger.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import frappe
-from frappe import qb
-from frappe.query_builder import CustomFunction
-from frappe.query_builder.functions import Count, IfNull
-from frappe.utils import flt
-
-
-def execute():
-	"""
-	Migrate 'remarks' field from 'tabGL Entry' to 'tabPayment Ledger Entry'
-	"""
-
-	if frappe.reload_doc("accounts", "doctype", "payment_ledger_entry"):
-
-		gle = qb.DocType("GL Entry")
-		ple = qb.DocType("Payment Ledger Entry")
-
-		# Get empty PLE records
-		un_processed = (
-			qb.from_(ple).select(Count(ple.name)).where((ple.remarks.isnull()) & (ple.delinked == 0)).run()
-		)[0][0]
-
-		if un_processed:
-			print(f"Remarks for {un_processed} Payment Ledger records will be updated from GL Entry")
-
-			ifelse = CustomFunction("IF", ["condition", "then", "else"])
-
-			processed = 0
-			last_percent_update = 0
-			batch_size = 1000
-			last_name = None
-
-			while True:
-				if last_name:
-					where_clause = (ple.name.gt(last_name)) & (ple.remarks.isnull()) & (ple.delinked == 0)
-				else:
-					where_clause = (ple.remarks.isnull()) & (ple.delinked == 0)
-
-				# results are deterministic
-				names = (
-					qb.from_(ple).select(ple.name).where(where_clause).orderby(ple.name).limit(batch_size).run()
-				)
-
-				if names:
-					last_name = names[-1][0]
-
-					pl_entries = (
-						qb.from_(ple)
-						.left_join(gle)
-						.on(
-							(ple.account == gle.account)
-							& (ple.party_type == gle.party_type)
-							& (ple.party == gle.party)
-							& (ple.voucher_type == gle.voucher_type)
-							& (ple.voucher_no == gle.voucher_no)
-							& (
-								ple.against_voucher_type
-								== IfNull(
-									ifelse(gle.against_voucher_type == "", None, gle.against_voucher_type), gle.voucher_type
-								)
-							)
-							& (
-								ple.against_voucher_no
-								== IfNull(ifelse(gle.against_voucher == "", None, gle.against_voucher), gle.voucher_no)
-							)
-							& (ple.company == gle.company)
-							& (
-								((ple.account_type == "Receivable") & (ple.amount == (gle.debit - gle.credit)))
-								| (ple.account_type == "Payable") & (ple.amount == (gle.credit - gle.debit))
-							)
-							& (gle.remarks.notnull())
-							& (gle.is_cancelled == 0)
-						)
-						.select(ple.name)
-						.distinct()
-						.select(
-							gle.remarks.as_("gle_remarks"),
-						)
-						.where(ple.name.isin(names))
-						.run(as_dict=True)
-					)
-
-					if pl_entries:
-						for entry in pl_entries:
-							query = qb.update(ple).set(ple.remarks, entry.gle_remarks).where((ple.name == entry.name))
-							query.run()
-
-						frappe.db.commit()
-
-						processed += len(pl_entries)
-						percentage = flt((processed / un_processed) * 100, 2)
-						if percentage - last_percent_update > 1:
-							print(f"{percentage}% ({processed}) PLE records updated")
-							last_percent_update = percentage
-
-				else:
-					break
-			print("Remarks succesfully migrated")
diff --git a/erpnext/patches/v14_0/update_closing_balances.py b/erpnext/patches/v14_0/update_closing_balances.py
new file mode 100644
index 0000000..40a1851
--- /dev/null
+++ b/erpnext/patches/v14_0/update_closing_balances.py
@@ -0,0 +1,33 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: MIT. See LICENSE
+
+
+import frappe
+
+from erpnext.accounts.doctype.account_closing_balance.account_closing_balance import (
+	make_closing_entries,
+)
+from erpnext.accounts.utils import get_fiscal_year
+
+
+def execute():
+	company_wise_order = {}
+	get_opening_entries = True
+	for pcv in frappe.db.get_all(
+		"Period Closing Voucher",
+		fields=["company", "posting_date", "name"],
+		filters={"docstatus": 1},
+		order_by="posting_date",
+	):
+
+		company_wise_order.setdefault(pcv.company, [])
+		if pcv.posting_date not in company_wise_order[pcv.company]:
+			pcv_doc = frappe.get_doc("Period Closing Voucher", pcv.name)
+			pcv_doc.year_start_date = get_fiscal_year(
+				pcv.posting_date, pcv.fiscal_year, company=pcv.company
+			)[1]
+			gl_entries = pcv_doc.get_gl_entries()
+			closing_entries = pcv_doc.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
+			make_closing_entries(gl_entries + closing_entries, voucher_name=pcv.name)
+			company_wise_order[pcv.company].append(pcv.posting_date)
+			get_opening_entries = False
diff --git a/erpnext/patches/v14_0/update_opportunity_currency_fields.py b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
index b803e9f..af73691 100644
--- a/erpnext/patches/v14_0/update_opportunity_currency_fields.py
+++ b/erpnext/patches/v14_0/update_opportunity_currency_fields.py
@@ -7,6 +7,9 @@
 
 
 def execute():
+	frappe.reload_doc(
+		"accounts", "doctype", "currency_exchange_settings"
+	)  # get_exchange_rate depends on Currency Exchange Settings
 	frappe.reload_doctype("Opportunity")
 	opportunities = frappe.db.get_list(
 		"Opportunity",
diff --git a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
index 371ecbc..5c46bf3 100644
--- a/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
+++ b/erpnext/patches/v15_0/create_asset_depreciation_schedules_from_assets.py
@@ -27,7 +27,13 @@
 
 	records = (
 		frappe.qb.from_(asset)
-		.select(asset.name, asset.opening_accumulated_depreciation, asset.docstatus)
+		.select(
+			asset.name,
+			asset.opening_accumulated_depreciation,
+			asset.gross_purchase_amount,
+			asset.number_of_depreciations_booked,
+			asset.docstatus,
+		)
 		.where(asset.calculate_depreciation == 1)
 		.where(asset.docstatus < 2)
 	).run(as_dict=True)
diff --git a/erpnext/patches/v15_0/enable_all_leads.py b/erpnext/patches/v15_0/enable_all_leads.py
new file mode 100644
index 0000000..c1f2b47
--- /dev/null
+++ b/erpnext/patches/v15_0/enable_all_leads.py
@@ -0,0 +1,8 @@
+import frappe
+
+
+def execute():
+	lead = frappe.qb.DocType("Lead")
+	frappe.qb.update(lead).set(lead.disabled, 0).set(lead.docstatus, 0).where(
+		lead.disabled == 1 and lead.docstatus == 1
+	).run()
diff --git a/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
new file mode 100644
index 0000000..afb59e0
--- /dev/null
+++ b/erpnext/patches/v15_0/update_gpa_and_ndb_for_assdeprsch.py
@@ -0,0 +1,20 @@
+import frappe
+
+
+def execute():
+	# not using frappe.qb because https://github.com/frappe/frappe/issues/20292
+	frappe.db.sql(
+		"""UPDATE `tabAsset Depreciation Schedule`
+        JOIN `tabAsset`
+        ON `tabAsset Depreciation Schedule`.`asset`=`tabAsset`.`name`
+        SET
+            `tabAsset Depreciation Schedule`.`gross_purchase_amount`=`tabAsset`.`gross_purchase_amount`,
+            `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`=`tabAsset`.`number_of_depreciations_booked`
+        WHERE
+        (
+            `tabAsset Depreciation Schedule`.`gross_purchase_amount`<>`tabAsset`.`gross_purchase_amount`
+            OR
+            `tabAsset Depreciation Schedule`.`number_of_depreciations_booked`<>`tabAsset`.`number_of_depreciations_booked`
+        )
+        AND `tabAsset Depreciation Schedule`.`docstatus`<2"""
+	)
diff --git a/erpnext/projects/doctype/timesheet/timesheet.js b/erpnext/projects/doctype/timesheet/timesheet.js
index a376bf4..d1d07a7 100644
--- a/erpnext/projects/doctype/timesheet/timesheet.js
+++ b/erpnext/projects/doctype/timesheet/timesheet.js
@@ -5,6 +5,8 @@
 	setup: function(frm) {
 		frappe.require("/assets/erpnext/js/projects/timer.js");
 
+		frm.ignore_doctypes_on_cancel_all = ['Sales Invoice'];
+
 		frm.fields_dict.employee.get_query = function() {
 			return {
 				filters:{
diff --git a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
index 17e3155..766e40e 100644
--- a/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
+++ b/erpnext/projects/report/delayed_tasks_summary/delayed_tasks_summary.py
@@ -46,6 +46,9 @@
 			# task has no end date, hence no delay
 			task.delay = 0
 
+		task.status = _(task.status)
+		task.priority = _(task.priority)
+
 	# Sort by descending order of delay
 	tasks.sort(key=lambda x: x["delay"], reverse=True)
 	return tasks
@@ -73,7 +76,7 @@
 			on_track = on_track + 1
 	charts = {
 		"data": {
-			"labels": ["On Track", "Delayed"],
+			"labels": [_("On Track"), _("Delayed")],
 			"datasets": [{"name": "Delayed", "values": [on_track, delay]}],
 		},
 		"type": "percentage",
diff --git a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
index 321b812..1271e38 100644
--- a/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
+++ b/erpnext/public/js/bank_reconciliation_tool/dialog_manager.js
@@ -391,14 +391,14 @@
 				fieldname: "deposit",
 				fieldtype: "Currency",
 				label: "Deposit",
-				options: "currency",
+				options: "account_currency",
 				read_only: 1,
 			},
 			{
 				fieldname: "withdrawal",
 				fieldtype: "Currency",
 				label: "Withdrawal",
-				options: "currency",
+				options: "account_currency",
 				read_only: 1,
 			},
 			{
@@ -416,18 +416,18 @@
 				fieldname: "allocated_amount",
 				fieldtype: "Currency",
 				label: "Allocated Amount",
-				options: "Currency",
+				options: "account_currency",
 				read_only: 1,
 			},
 			{
 				fieldname: "unallocated_amount",
 				fieldtype: "Currency",
 				label: "Unallocated Amount",
-				options: "Currency",
+				options: "account_currency",
 				read_only: 1,
 			},
 			{
-				fieldname: "currency",
+				fieldname: "account_currency",
 				fieldtype: "Link",
 				label: "Currency",
 				options: "Currency",
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index a07f75d..d943126 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -55,6 +55,14 @@
 	},
 
 	allocate_advances_automatically: function(frm) {
+		frm.trigger('fetch_advances');
+	},
+
+	only_include_allocated_payments: function(frm) {
+		frm.trigger('fetch_advances');
+	},
+
+	fetch_advances: function(frm) {
 		if(frm.doc.allocate_advances_automatically) {
 			frappe.call({
 				doc: frm.doc,
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index a87c3ec..8efc47d 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -91,6 +91,9 @@
 	}
 
 	_calculate_taxes_and_totals() {
+		const is_quotation = this.frm.doc.doctype == "Quotation";
+		this.frm.doc._items = is_quotation ? this.filtered_items() : this.frm.doc.items;
+
 		this.validate_conversion_rate();
 		this.calculate_item_values();
 		this.initialize_taxes();
@@ -122,7 +125,7 @@
 	calculate_item_values() {
 		var me = this;
 		if (!this.discount_amount_applied) {
-			for (const item of this.frm.doc.items || []) {
+			for (const item of this.frm.doc._items || []) {
 				frappe.model.round_floats_in(item);
 				item.net_rate = item.rate;
 				item.qty = item.qty === undefined ? (me.frm.doc.is_return ? -1 : 1) : item.qty;
@@ -131,8 +134,8 @@
 					item.net_amount = item.amount = flt(item.rate * item.qty, precision("amount", item));
 				}
 				else {
-					let qty = item.qty || 1;
-					qty = me.frm.doc.is_return ? -1 * qty : qty;
+					// allow for '0' qty on Credit/Debit notes
+					let qty = item.qty || (me.frm.doc.is_debit_note ? 1 : -1);
 					item.net_amount = item.amount = flt(item.rate * qty, precision("amount", item));
 				}
 
@@ -206,7 +209,7 @@
 		});
 		if(has_inclusive_tax==false) return;
 
-		$.each(me.frm.doc["items"] || [], function(n, item) {
+		$.each(me.frm.doc._items || [], function(n, item) {
 			var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
 			var cumulated_tax_fraction = 0.0;
 			var total_inclusive_tax_amount_per_qty = 0;
@@ -277,7 +280,7 @@
 		var me = this;
 		this.frm.doc.total_qty = this.frm.doc.total = this.frm.doc.base_total = this.frm.doc.net_total = this.frm.doc.base_net_total = 0.0;
 
-		$.each(this.frm.doc["items"] || [], function(i, item) {
+		$.each(this.frm.doc._items || [], function(i, item) {
 			me.frm.doc.total += item.amount;
 			me.frm.doc.total_qty += item.qty;
 			me.frm.doc.base_total += item.base_amount;
@@ -330,7 +333,7 @@
 			}
 		});
 
-		$.each(this.frm.doc["items"] || [], function(n, item) {
+		$.each(this.frm.doc._items || [], function(n, item) {
 			var item_tax_map = me._load_item_tax_rate(item.item_tax_rate);
 			$.each(me.frm.doc["taxes"] || [], function(i, tax) {
 				// tax_amount represents the amount of tax for the current step
@@ -339,7 +342,7 @@
 				// Adjust divisional loss to the last item
 				if (tax.charge_type == "Actual") {
 					actual_tax_dict[tax.idx] -= current_tax_amount;
-					if (n == me.frm.doc["items"].length - 1) {
+					if (n == me.frm.doc._items.length - 1) {
 						current_tax_amount += actual_tax_dict[tax.idx];
 					}
 				}
@@ -376,7 +379,7 @@
 				}
 
 				// set precision in the last item iteration
-				if (n == me.frm.doc["items"].length - 1) {
+				if (n == me.frm.doc._items.length - 1) {
 					me.round_off_totals(tax);
 					me.set_in_company_currency(tax,
 						["tax_amount", "tax_amount_after_discount_amount"]);
@@ -599,10 +602,11 @@
 
 	_cleanup() {
 		this.frm.doc.base_in_words = this.frm.doc.in_words = "";
+		let items = this.frm.doc._items;
 
-		if(this.frm.doc["items"] && this.frm.doc["items"].length) {
-			if(!frappe.meta.get_docfield(this.frm.doc["items"][0].doctype, "item_tax_amount", this.frm.doctype)) {
-				$.each(this.frm.doc["items"] || [], function(i, item) {
+		if(items && items.length) {
+			if(!frappe.meta.get_docfield(items[0].doctype, "item_tax_amount", this.frm.doctype)) {
+				$.each(items || [], function(i, item) {
 					delete item["item_tax_amount"];
 				});
 			}
@@ -655,7 +659,7 @@
 			var net_total = 0;
 			// calculate item amount after Discount Amount
 			if (total_for_discount_amount) {
-				$.each(this.frm.doc["items"] || [], function(i, item) {
+				$.each(this.frm.doc._items || [], function(i, item) {
 					distributed_amount = flt(me.frm.doc.discount_amount) * item.net_amount / total_for_discount_amount;
 					item.net_amount = flt(item.net_amount - distributed_amount,
 						precision("base_amount", item));
@@ -663,7 +667,7 @@
 
 					// discount amount rounding loss adjustment if no taxes
 					if ((!(me.frm.doc.taxes || []).length || total_for_discount_amount==me.frm.doc.net_total || (me.frm.doc.apply_discount_on == "Net Total"))
-							&& i == (me.frm.doc.items || []).length - 1) {
+							&& i == (me.frm.doc._items || []).length - 1) {
 						var discount_amount_loss = flt(me.frm.doc.net_total - net_total
 							- me.frm.doc.discount_amount, precision("net_total"));
 						item.net_amount = flt(item.net_amount + discount_amount_loss,
@@ -892,4 +896,8 @@
 		}
 
 	}
+
+	filtered_items() {
+		return this.frm.doc.items.filter(item => !item["is_alternative"]);
+	}
 };
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 09f2c5d..0bd4d91 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -488,7 +488,7 @@
 								() => {
 									var d = locals[cdt][cdn];
 									me.add_taxes_from_item_tax_template(d.item_tax_rate);
-									if (d.free_item_data) {
+									if (d.free_item_data && d.free_item_data.length > 0) {
 										me.apply_product_discount(d);
 									}
 								},
@@ -1884,31 +1884,73 @@
 
 	get_advances() {
 		if(!this.frm.is_return) {
+			var me = this;
 			return this.frm.call({
 				method: "set_advances",
 				doc: this.frm.doc,
 				callback: function(r, rt) {
 					refresh_field("advances");
+					me.frm.dirty();
 				}
 			})
 		}
 	}
 
 	make_payment_entry() {
+		let via_journal_entry = this.frm.doc.__onload && this.frm.doc.__onload.make_payment_via_journal_entry;
+		if(this.has_discount_in_schedule() && !via_journal_entry) {
+			// If early payment discount is applied, ask user for reference date
+			this.prompt_user_for_reference_date();
+		} else {
+			this.make_mapped_payment_entry();
+		}
+	}
+
+	make_mapped_payment_entry(args) {
+		var me = this;
+		args = args || { "dt": this.frm.doc.doctype, "dn": this.frm.doc.name };
 		return frappe.call({
-			method: cur_frm.cscript.get_method_for_payment(),
-			args: {
-				"dt": cur_frm.doc.doctype,
-				"dn": cur_frm.doc.name
-			},
+			method: me.get_method_for_payment(),
+			args: args,
 			callback: function(r) {
 				var doclist = frappe.model.sync(r.message);
 				frappe.set_route("Form", doclist[0].doctype, doclist[0].name);
-				// cur_frm.refresh_fields()
 			}
 		});
 	}
 
+	prompt_user_for_reference_date(){
+		var me = this;
+		frappe.prompt({
+			label: __("Cheque/Reference Date"),
+			fieldname: "reference_date",
+			fieldtype: "Date",
+			reqd: 1,
+		}, (values) => {
+			let args = {
+				"dt": me.frm.doc.doctype,
+				"dn": me.frm.doc.name,
+				"reference_date": values.reference_date
+			}
+			me.make_mapped_payment_entry(args);
+		},
+		__("Reference Date for Early Payment Discount"),
+		__("Continue")
+		);
+	}
+
+	has_discount_in_schedule() {
+		let is_eligible = in_list(
+			["Sales Order", "Sales Invoice", "Purchase Order", "Purchase Invoice"],
+			this.frm.doctype
+		);
+		let has_payment_schedule = this.frm.doc.payment_schedule && this.frm.doc.payment_schedule.length;
+		if(!is_eligible || !has_payment_schedule) return false;
+
+		let has_discount = this.frm.doc.payment_schedule.some(row => row.discount_date);
+		return has_discount;
+	}
+
 	make_quality_inspection() {
 		let data = [];
 		const fields = [
diff --git a/erpnext/public/js/templates/crm_notes.html b/erpnext/public/js/templates/crm_notes.html
index fddeb1c..53df933 100644
--- a/erpnext/public/js/templates/crm_notes.html
+++ b/erpnext/public/js/templates/crm_notes.html
@@ -17,7 +17,7 @@
 								{{ frappe.avatar(notes[i].added_by) }}
 							</div>
 							<div class="col-xs-10">
-								<div class="mr-2 title font-weight-bold">
+								<div class="mr-2 title font-weight-bold ellipsis" title="{{ strip_html(notes[i].added_by) }}">
 									{{ strip_html(notes[i].added_by) }}
 								</div>
 								<div class="time small text-muted">
diff --git a/erpnext/public/js/website_utils.js b/erpnext/public/js/website_utils.js
index b541606..2bb5255 100644
--- a/erpnext/public/js/website_utils.js
+++ b/erpnext/public/js/website_utils.js
@@ -3,18 +3,6 @@
 
 if(!window.erpnext) window.erpnext = {};
 
-// Add / update a new Lead / Communication
-// subject, sender, description
-frappe.send_message = function(opts, btn) {
-	return frappe.call({
-		type: "POST",
-		method: "erpnext.templates.utils.send_message",
-		btn: btn,
-		args: opts,
-		callback: opts.callback
-	});
-};
-
 erpnext.subscribe_to_newsletter = function(opts, btn) {
 	return frappe.call({
 		type: "POST",
@@ -24,6 +12,3 @@
 		callback: opts.callback
 	});
 }
-
-// for backward compatibility
-erpnext.send_message = frappe.send_message;
diff --git a/erpnext/selling/doctype/customer/customer.js b/erpnext/selling/doctype/customer/customer.js
index 107e4a4..b53f339 100644
--- a/erpnext/selling/doctype/customer/customer.js
+++ b/erpnext/selling/doctype/customer/customer.js
@@ -123,7 +123,7 @@
 
 			frm.add_custom_button(__('Accounting Ledger'), function () {
 				frappe.set_route('query-report', 'General Ledger',
-					{party_type: 'Customer', party: frm.doc.name});
+					{party_type: 'Customer', party: frm.doc.name, party_name: frm.doc.customer_name});
 			}, __('View'));
 
 			frm.add_custom_button(__('Pricing Rule'), function () {
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index d9dab33..18336d2 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -11,10 +11,9 @@
 	delete_contact_and_address,
 	load_address_and_contact,
 )
-from frappe.desk.reportview import build_match_conditions, get_filters_cond
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
-from frappe.model.rename_doc import update_linked_doctypes
+from frappe.model.utils.rename_doc import update_linked_doctypes
 from frappe.utils import cint, cstr, flt, get_formatted_email, today
 from frappe.utils.user import get_users_with_role
 
@@ -272,18 +271,9 @@
 
 	def on_trash(self):
 		if self.customer_primary_contact:
-			frappe.db.sql(
-				"""
-				UPDATE `tabCustomer`
-				SET
-					customer_primary_contact=null,
-					customer_primary_address=null,
-					mobile_no=null,
-					email_id=null,
-					primary_address=null
-				WHERE name=%(name)s""",
-				{"name": self.name},
-			)
+			self.db_set("customer_primary_contact", None)
+		if self.customer_primary_address:
+			self.db_set("customer_primary_address", None)
 
 		delete_contact_and_address("Customer", self.name)
 		if self.lead_name:
@@ -454,44 +444,6 @@
 	return links
 
 
-@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def get_customer_list(doctype, txt, searchfield, start, page_len, filters=None):
-	from erpnext.controllers.queries import get_fields
-
-	fields = ["name", "customer_name", "customer_group", "territory"]
-
-	if frappe.db.get_default("cust_master_name") == "Customer Name":
-		fields = ["name", "customer_group", "territory"]
-
-	fields = get_fields("Customer", fields)
-
-	match_conditions = build_match_conditions("Customer")
-	match_conditions = "and {}".format(match_conditions) if match_conditions else ""
-
-	if filters:
-		filter_conditions = get_filters_cond(doctype, filters, [])
-		match_conditions += "{}".format(filter_conditions)
-
-	return frappe.db.sql(
-		"""
-		select %s
-		from `tabCustomer`
-		where docstatus < 2
-			and (%s like %s or customer_name like %s)
-			{match_conditions}
-		order by
-			case when name like %s then 0 else 1 end,
-			case when customer_name like %s then 0 else 1 end,
-			name, customer_name limit %s, %s
-		""".format(
-			match_conditions=match_conditions
-		)
-		% (", ".join(fields), searchfield, "%s", "%s", "%s", "%s", "%s", "%s"),
-		("%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, "%%%s%%" % txt, start, page_len),
-	)
-
-
 def check_credit_limit(customer, company, ignore_outstanding_sales_order=False, extra_amount=0):
 	credit_limit = get_credit_limit(customer, company)
 	if not credit_limit:
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index b348bd3..83fa472 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -90,7 +90,7 @@
 				|| frappe.datetime.get_diff(doc.valid_till, frappe.datetime.get_today()) >= 0) {
 					this.frm.add_custom_button(
 						__("Sales Order"),
-						this.frm.cscript["Make Sales Order"],
+						() => this.make_sales_order(),
 						__("Create")
 					);
 				}
@@ -145,6 +145,20 @@
 
 	}
 
+	make_sales_order() {
+		var me = this;
+
+		let has_alternative_item = this.frm.doc.items.some((item) => item.is_alternative);
+		if (has_alternative_item) {
+			this.show_alternative_items_dialog();
+		} else {
+			frappe.model.open_mapped_doc({
+				method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
+				frm: me.frm
+			});
+		}
+	}
+
 	set_dynamic_field_label(){
 		if (this.frm.doc.quotation_to == "Customer")
 		{
@@ -220,17 +234,112 @@
 			}
 		})
 	}
+
+	show_alternative_items_dialog() {
+		let me = this;
+
+		const table_fields = [
+		{
+			fieldtype:"Data",
+			fieldname:"name",
+			label: __("Name"),
+			read_only: 1,
+		},
+		{
+			fieldtype:"Link",
+			fieldname:"item_code",
+			options: "Item",
+			label: __("Item Code"),
+			read_only: 1,
+			in_list_view: 1,
+			columns: 2,
+			formatter: (value, df, options, doc) => {
+				return doc.is_alternative ? `<span class="indicator yellow">${value}</span>` : value;
+			}
+		},
+		{
+			fieldtype:"Data",
+			fieldname:"description",
+			label: __("Description"),
+			in_list_view: 1,
+			read_only: 1,
+		},
+		{
+			fieldtype:"Currency",
+			fieldname:"amount",
+			label: __("Amount"),
+			options: "currency",
+			in_list_view: 1,
+			read_only: 1,
+		},
+		{
+			fieldtype:"Check",
+			fieldname:"is_alternative",
+			label: __("Is Alternative"),
+			read_only: 1,
+		}];
+
+
+		this.data = this.frm.doc.items.filter(
+			(item) => item.is_alternative || item.has_alternative_item
+		).map((item) => {
+			return {
+				"name": item.name,
+				"item_code": item.item_code,
+				"description": item.description,
+				"amount": item.amount,
+				"is_alternative": item.is_alternative,
+			}
+		});
+
+		const dialog = new frappe.ui.Dialog({
+			title: __("Select Alternative Items for Sales Order"),
+			fields: [
+				{
+					fieldname: "info",
+					fieldtype: "HTML",
+					read_only: 1
+				},
+				{
+					fieldname: "alternative_items",
+					fieldtype: "Table",
+					cannot_add_rows: true,
+					cannot_delete_rows: true,
+					in_place_edit: true,
+					reqd: 1,
+					data: this.data,
+					description: __("Select an item from each set to be used in the Sales Order."),
+					get_data: () => {
+						return this.data;
+					},
+					fields: table_fields
+				},
+			],
+			primary_action: function() {
+				frappe.model.open_mapped_doc({
+					method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
+					frm: me.frm,
+					args: {
+						selected_items: dialog.fields_dict.alternative_items.grid.get_selected_children()
+					}
+				});
+				dialog.hide();
+			},
+			primary_action_label: __('Continue')
+		});
+
+		dialog.fields_dict.info.$wrapper.html(
+			`<p class="small text-muted">
+				<span class="indicator yellow"></span>
+				${__("Alternative Items")}
+			</p>`
+		)
+		dialog.show();
+	}
 };
 
 cur_frm.script_manager.make(erpnext.selling.QuotationController);
 
-cur_frm.cscript['Make Sales Order'] = function() {
-	frappe.model.open_mapped_doc({
-		method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
-		frm: cur_frm
-	})
-}
-
 frappe.ui.form.on("Quotation Item", "items_on_form_rendered", "packed_items_on_form_rendered", function(frm, cdt, cdn) {
 	// enable tax_amount field if Actual
 })
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 063813b..fc66db2 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -35,6 +35,9 @@
 
 		make_packing_list(self)
 
+	def before_submit(self):
+		self.set_has_alternative_item()
+
 	def validate_valid_till(self):
 		if self.valid_till and getdate(self.valid_till) < getdate(self.transaction_date):
 			frappe.throw(_("Valid till date cannot be before transaction date"))
@@ -59,7 +62,18 @@
 					title=_("Unpublished Item"),
 				)
 
+	def set_has_alternative_item(self):
+		"""Mark 'Has Alternative Item' for rows."""
+		if not any(row.is_alternative for row in self.get("items")):
+			return
+
+		items_with_alternatives = self.get_rows_with_alternatives()
+		for row in self.get("items"):
+			if not row.is_alternative and row.name in items_with_alternatives:
+				row.has_alternative_item = 1
+
 	def get_ordered_status(self):
+		status = "Open"
 		ordered_items = frappe._dict(
 			frappe.db.get_all(
 				"Sales Order Item",
@@ -70,16 +84,40 @@
 			)
 		)
 
-		status = "Open"
-		if ordered_items:
+		if not ordered_items:
+			return status
+
+		has_alternatives = any(row.is_alternative for row in self.get("items"))
+		self._items = self.get_valid_items() if has_alternatives else self.get("items")
+
+		if any(row.qty > ordered_items.get(row.item_code, 0.0) for row in self._items):
+			status = "Partially Ordered"
+		else:
 			status = "Ordered"
 
-			for item in self.get("items"):
-				if item.qty > ordered_items.get(item.item_code, 0.0):
-					status = "Partially Ordered"
-
 		return status
 
+	def get_valid_items(self):
+		"""
+		Filters out items in an alternatives set that were not ordered.
+		"""
+
+		def is_in_sales_order(row):
+			in_sales_order = bool(
+				frappe.db.exists(
+					"Sales Order Item", {"quotation_item": row.name, "item_code": row.item_code, "docstatus": 1}
+				)
+			)
+			return in_sales_order
+
+		def can_map(row) -> bool:
+			if row.is_alternative or row.has_alternative_item:
+				return is_in_sales_order(row)
+
+			return True
+
+		return list(filter(can_map, self.get("items")))
+
 	def is_fully_ordered(self):
 		return self.get_ordered_status() == "Ordered"
 
@@ -176,6 +214,22 @@
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 		self.valid_till = None
 
+	def get_rows_with_alternatives(self):
+		rows_with_alternatives = []
+		table_length = len(self.get("items"))
+
+		for idx, row in enumerate(self.get("items")):
+			if row.is_alternative:
+				continue
+
+			if idx == (table_length - 1):
+				break
+
+			if self.get("items")[idx + 1].is_alternative:
+				rows_with_alternatives.append(row.name)
+
+		return rows_with_alternatives
+
 
 def get_list_context(context=None):
 	from erpnext.controllers.website_list_for_contact import get_list_context
@@ -221,6 +275,8 @@
 		)
 	)
 
+	selected_rows = [x.get("name") for x in frappe.flags.get("args", {}).get("selected_items", [])]
+
 	def set_missing_values(source, target):
 		if customer:
 			target.customer = customer.name
@@ -244,6 +300,24 @@
 			target.blanket_order = obj.blanket_order
 			target.blanket_order_rate = obj.blanket_order_rate
 
+	def can_map_row(item) -> bool:
+		"""
+		Row mapping from Quotation to Sales order:
+		1. If no selections, map all non-alternative rows (that sum up to the grand total)
+		2. If selections: Is Alternative Item/Has Alternative Item: Map if selected and adequate qty
+		3. If selections: Simple row: Map if adequate qty
+		"""
+		has_qty = item.qty > 0
+
+		if not selected_rows:
+			return not item.is_alternative
+
+		if selected_rows and (item.is_alternative or item.has_alternative_item):
+			return (item.name in selected_rows) and has_qty
+
+		# Simple row
+		return has_qty
+
 	doclist = get_mapped_doc(
 		"Quotation",
 		source_name,
@@ -253,7 +327,7 @@
 				"doctype": "Sales Order Item",
 				"field_map": {"parent": "prevdoc_docname", "name": "quotation_item"},
 				"postprocess": update_item,
-				"condition": lambda doc: doc.qty > 0,
+				"condition": can_map_row,
 			},
 			"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
 			"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
@@ -322,7 +396,11 @@
 		source_name,
 		{
 			"Quotation": {"doctype": "Sales Invoice", "validation": {"docstatus": ["=", 1]}},
-			"Quotation Item": {"doctype": "Sales Invoice Item", "postprocess": update_item},
+			"Quotation Item": {
+				"doctype": "Sales Invoice Item",
+				"postprocess": update_item,
+				"condition": lambda row: not row.is_alternative,
+			},
 			"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
 			"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
 		},
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index cdf5f5d..67f6518 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -457,6 +457,139 @@
 			expected_index = id + 1
 			self.assertEqual(item.idx, expected_index)
 
+	def test_alternative_items_with_stock_items(self):
+		"""
+		Check if taxes & totals considers only non-alternative items with:
+		- One set of non-alternative & alternative items [first 3 rows]
+		- One simple stock item
+		"""
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		item_list = []
+		stock_items = {
+			"_Test Simple Item 1": 100,
+			"_Test Alt 1": 120,
+			"_Test Alt 2": 110,
+			"_Test Simple Item 2": 200,
+		}
+
+		for item, rate in stock_items.items():
+			make_item(item, {"is_stock_item": 1})
+			item_list.append(
+				{
+					"item_code": item,
+					"qty": 1,
+					"rate": rate,
+					"is_alternative": bool("Alt" in item),
+				}
+			)
+
+		quotation = make_quotation(item_list=item_list, do_not_submit=1)
+		quotation.append(
+			"taxes",
+			{
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 10,
+			},
+		)
+		quotation.submit()
+
+		self.assertEqual(quotation.net_total, 300)
+		self.assertEqual(quotation.grand_total, 330)
+
+	def test_alternative_items_with_service_items(self):
+		"""
+		Check if taxes & totals considers only non-alternative items with:
+		- One set of non-alternative & alternative service items [first 3 rows]
+		- One simple non-alternative service item
+		All having the same item code and unique item name/description due to
+		dynamic services
+		"""
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		item_list = []
+		service_items = {
+			"Tiling with Standard Tiles": 100,
+			"Alt Tiling with Durable Tiles": 150,
+			"Alt Tiling with Premium Tiles": 180,
+			"False Ceiling with Material #234": 190,
+		}
+
+		make_item("_Test Dynamic Service Item", {"is_stock_item": 0})
+
+		for name, rate in service_items.items():
+			item_list.append(
+				{
+					"item_code": "_Test Dynamic Service Item",
+					"item_name": name,
+					"description": name,
+					"qty": 1,
+					"rate": rate,
+					"is_alternative": bool("Alt" in name),
+				}
+			)
+
+		quotation = make_quotation(item_list=item_list, do_not_submit=1)
+		quotation.append(
+			"taxes",
+			{
+				"account_head": "_Test Account VAT - _TC",
+				"charge_type": "On Net Total",
+				"cost_center": "_Test Cost Center - _TC",
+				"description": "VAT",
+				"doctype": "Sales Taxes and Charges",
+				"rate": 10,
+			},
+		)
+		quotation.submit()
+
+		self.assertEqual(quotation.net_total, 290)
+		self.assertEqual(quotation.grand_total, 319)
+
+	def test_alternative_items_sales_order_mapping_with_stock_items(self):
+		from erpnext.selling.doctype.quotation.quotation import make_sales_order
+		from erpnext.stock.doctype.item.test_item import make_item
+
+		frappe.flags.args = frappe._dict()
+		item_list = []
+		stock_items = {
+			"_Test Simple Item 1": 100,
+			"_Test Alt 1": 120,
+			"_Test Alt 2": 110,
+			"_Test Simple Item 2": 200,
+		}
+
+		for item, rate in stock_items.items():
+			make_item(item, {"is_stock_item": 1})
+			item_list.append(
+				{
+					"item_code": item,
+					"qty": 1,
+					"rate": rate,
+					"is_alternative": bool("Alt" in item),
+					"warehouse": "_Test Warehouse - _TC",
+				}
+			)
+
+		quotation = make_quotation(item_list=item_list)
+
+		frappe.flags.args.selected_items = [quotation.items[2]]
+		sales_order = make_sales_order(quotation.name)
+		sales_order.delivery_date = add_days(sales_order.transaction_date, 10)
+		sales_order.save()
+
+		self.assertEqual(sales_order.items[0].item_code, "_Test Alt 2")
+		self.assertEqual(sales_order.items[1].item_code, "_Test Simple Item 2")
+		self.assertEqual(sales_order.net_total, 310)
+
+		sales_order.submit()
+		quotation.reload()
+		self.assertEqual(quotation.status, "Ordered")
+
 
 test_records = frappe.get_test_records("Quotation")
 
diff --git a/erpnext/selling/doctype/quotation_item/quotation_item.json b/erpnext/selling/doctype/quotation_item/quotation_item.json
index ca7dfd2..f2aabc5 100644
--- a/erpnext/selling/doctype/quotation_item/quotation_item.json
+++ b/erpnext/selling/doctype/quotation_item/quotation_item.json
@@ -49,6 +49,8 @@
   "pricing_rules",
   "stock_uom_rate",
   "is_free_item",
+  "is_alternative",
+  "has_alternative_item",
   "section_break_43",
   "valuation_rate",
   "column_break_45",
@@ -643,12 +645,28 @@
    "no_copy": 1,
    "options": "currency",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "is_alternative",
+   "fieldtype": "Check",
+   "label": "Is Alternative",
+   "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "has_alternative_item",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Has Alternative Item",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-12-25 02:49:53.926625",
+ "modified": "2023-02-06 11:00:07.042364",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Quotation Item",
@@ -656,5 +674,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index fb64772..449d461 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -275,7 +275,7 @@
 		if (this.frm.doc.docstatus===0) {
 			this.frm.add_custom_button(__('Quotation'),
 				function() {
-					erpnext.utils.map_current_doc({
+					let d = erpnext.utils.map_current_doc({
 						method: "erpnext.selling.doctype.quotation.quotation.make_sales_order",
 						source_doctype: "Quotation",
 						target: me.frm,
@@ -293,7 +293,16 @@
 							docstatus: 1,
 							status: ["!=", "Lost"]
 						}
-					})
+					});
+
+					setTimeout(() => {
+						d.$parent.append(`
+							<span class='small text-muted'>
+								${__("Note: Please create Sales Orders from individual Quotations to select from among Alternative Items.")}
+							</span>
+					`);
+					}, 200);
+
 				}, __("Get Items From"));
 		}
 
@@ -309,9 +318,12 @@
 
 	make_work_order() {
 		var me = this;
-		this.frm.call({
-			doc: this.frm.doc,
-			method: 'get_work_order_items',
+		me.frm.call({
+			method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
+			args: {
+				sales_order: this.frm.docname,
+			},
+			freeze: true,
 			callback: function(r) {
 				if(!r.message) {
 					frappe.msgprint({
@@ -321,14 +333,7 @@
 					});
 					return;
 				}
-				else if(!r.message) {
-					frappe.msgprint({
-						title: __('Work Order not created'),
-						message: __('Work Order already created for all items with BOM'),
-						indicator: 'orange'
-					});
-					return;
-				} else {
+				else {
 					const fields = [{
 						label: 'Items',
 						fieldtype: 'Table',
@@ -429,9 +434,9 @@
 	make_raw_material_request() {
 		var me = this;
 		this.frm.call({
-			doc: this.frm.doc,
-			method: 'get_work_order_items',
+			method: "erpnext.selling.doctype.sales_order.sales_order.get_work_order_items",
 			args: {
+				sales_order: this.frm.docname,
 				for_raw_material_request: 1
 			},
 			callback: function(r) {
@@ -450,6 +455,7 @@
 	}
 
 	make_raw_material_request_dialog(r) {
+		var me = this;
 		var fields = [
 			{fieldtype:'Check', fieldname:'include_exploded_items',
 				label: __('Include Exploded Items')},
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index ca6a51a..ee9161b 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -6,11 +6,12 @@
 
 import frappe
 import frappe.utils
-from frappe import _
+from frappe import _, qb
 from frappe.contacts.doctype.address.address import get_company_address
 from frappe.desk.notifications import clear_doctype_notifications
 from frappe.model.mapper import get_mapped_doc
 from frappe.model.utils import get_fetch_values
+from frappe.query_builder.functions import Sum
 from frappe.utils import add_days, cint, cstr, flt, get_link_to_form, getdate, nowdate, strip_html
 
 from erpnext.accounts.doctype.sales_invoice.sales_invoice import (
@@ -20,6 +21,9 @@
 )
 from erpnext.accounts.party import get_party_account
 from erpnext.controllers.selling_controller import SellingController
+from erpnext.manufacturing.doctype.blanket_order.blanket_order import (
+	validate_against_blanket_order,
+)
 from erpnext.manufacturing.doctype.production_plan.production_plan import (
 	get_items_for_material_requests,
 )
@@ -51,6 +55,7 @@
 		self.validate_warehouse()
 		self.validate_drop_ship()
 		self.validate_serial_no_based_delivery()
+		validate_against_blanket_order(self)
 		validate_inter_company_party(
 			self.doctype, self.customer, self.company, self.inter_company_order_reference
 		)
@@ -414,51 +419,6 @@
 			self.indicator_color = "green"
 			self.indicator_title = _("Paid")
 
-	@frappe.whitelist()
-	def get_work_order_items(self, for_raw_material_request=0):
-		"""Returns items with BOM that already do not have a linked work order"""
-		items = []
-		item_codes = [i.item_code for i in self.items]
-		product_bundle_parents = [
-			pb.new_item_code
-			for pb in frappe.get_all(
-				"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
-			)
-		]
-
-		for table in [self.items, self.packed_items]:
-			for i in table:
-				bom = get_default_bom(i.item_code)
-				stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
-
-				if not for_raw_material_request:
-					total_work_order_qty = flt(
-						frappe.db.sql(
-							"""select sum(qty) from `tabWork Order`
-						where production_item=%s and sales_order=%s and sales_order_item = %s and docstatus<2""",
-							(i.item_code, self.name, i.name),
-						)[0][0]
-					)
-					pending_qty = stock_qty - total_work_order_qty
-				else:
-					pending_qty = stock_qty
-
-				if pending_qty and i.item_code not in product_bundle_parents:
-					items.append(
-						dict(
-							name=i.name,
-							item_code=i.item_code,
-							description=i.description,
-							bom=bom or "",
-							warehouse=i.warehouse,
-							pending_qty=pending_qty,
-							required_qty=pending_qty if for_raw_material_request else 0,
-							sales_order_item=i.name,
-						)
-					)
-
-		return items
-
 	def on_recurring(self, reference_doc, auto_repeat_doc):
 		def _get_delivery_date(ref_doc_delivery_date, red_doc_transaction_date, transaction_date):
 			delivery_date = auto_repeat_doc.get_next_schedule_date(schedule_date=ref_doc_delivery_date)
@@ -1350,3 +1310,57 @@
 		return
 
 	frappe.db.set_value("Sales Order Item", sales_order_item, "produced_qty", total_produced_qty)
+
+
+@frappe.whitelist()
+def get_work_order_items(sales_order, for_raw_material_request=0):
+	"""Returns items with BOM that already do not have a linked work order"""
+	if sales_order:
+		so = frappe.get_doc("Sales Order", sales_order)
+
+		wo = qb.DocType("Work Order")
+
+		items = []
+		item_codes = [i.item_code for i in so.items]
+		product_bundle_parents = [
+			pb.new_item_code
+			for pb in frappe.get_all(
+				"Product Bundle", {"new_item_code": ["in", item_codes]}, ["new_item_code"]
+			)
+		]
+
+		for table in [so.items, so.packed_items]:
+			for i in table:
+				bom = get_default_bom(i.item_code)
+				stock_qty = i.qty if i.doctype == "Packed Item" else i.stock_qty
+
+				if not for_raw_material_request:
+					total_work_order_qty = flt(
+						qb.from_(wo)
+						.select(Sum(wo.qty))
+						.where(
+							(wo.production_item == i.item_code)
+							& (wo.sales_order == so.name) * (wo.sales_order_item == i.name)
+							& (wo.docstatus.lte(2))
+						)
+						.run()[0][0]
+					)
+					pending_qty = stock_qty - total_work_order_qty
+				else:
+					pending_qty = stock_qty
+
+				if pending_qty and i.item_code not in product_bundle_parents:
+					items.append(
+						dict(
+							name=i.name,
+							item_code=i.item_code,
+							description=i.description,
+							bom=bom or "",
+							warehouse=i.warehouse,
+							pending_qty=pending_qty,
+							required_qty=pending_qty if for_raw_material_request else 0,
+							sales_order_item=i.name,
+						)
+					)
+
+		return items
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index d4d7c58..627914f 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1217,6 +1217,8 @@
 		self.assertTrue(si.get("payment_schedule"))
 
 	def test_make_work_order(self):
+		from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
+
 		# Make a new Sales Order
 		so = make_sales_order(
 			**{
@@ -1230,7 +1232,7 @@
 		# Raise Work Orders
 		po_items = []
 		so_item_name = {}
-		for item in so.get_work_order_items():
+		for item in get_work_order_items(so.name):
 			po_items.append(
 				{
 					"warehouse": item.get("warehouse"),
@@ -1448,6 +1450,7 @@
 
 		from erpnext.controllers.item_variant import create_variant
 		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
+		from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
 
 		make_item(  # template item
 			"Test-WO-Tshirt",
@@ -1487,7 +1490,7 @@
 				]
 			}
 		)
-		wo_items = so.get_work_order_items()
+		wo_items = get_work_order_items(so.name)
 
 		self.assertEqual(wo_items[0].get("item_code"), "Test-WO-Tshirt-R")
 		self.assertEqual(wo_items[0].get("bom"), red_var_bom.name)
@@ -1497,6 +1500,8 @@
 		self.assertEqual(wo_items[1].get("bom"), template_bom.name)
 
 	def test_request_for_raw_materials(self):
+		from erpnext.selling.doctype.sales_order.sales_order import get_work_order_items
+
 		item = make_item(
 			"_Test Finished Item",
 			{
@@ -1529,7 +1534,7 @@
 		so = make_sales_order(**{"item_list": [{"item_code": item.item_code, "qty": 1, "rate": 1000}]})
 		so.submit()
 		mr_dict = frappe._dict()
-		items = so.get_work_order_items(1)
+		items = get_work_order_items(so.name, 1)
 		mr_dict["items"] = items
 		mr_dict["include_exploded_items"] = 0
 		mr_dict["ignore_existing_ordered_qty"] = 1
diff --git a/erpnext/selling/doctype/selling_settings/selling_settings.json b/erpnext/selling/doctype/selling_settings/selling_settings.json
index 6ea66a0..045227f 100644
--- a/erpnext/selling/doctype/selling_settings/selling_settings.json
+++ b/erpnext/selling/doctype/selling_settings/selling_settings.json
@@ -24,10 +24,12 @@
   "so_required",
   "dn_required",
   "sales_update_frequency",
+  "over_order_allowance",
   "column_break_5",
   "allow_multiple_items",
   "allow_against_multiple_purchase_orders",
   "allow_sales_order_creation_for_expired_quotation",
+  "dont_reserve_sales_order_qty_on_sales_return",
   "hide_tax_id",
   "enable_discount_accounting"
  ],
@@ -179,6 +181,18 @@
    "fieldname": "allow_sales_order_creation_for_expired_quotation",
    "fieldtype": "Check",
    "label": "Allow Sales Order Creation For Expired Quotation"
+  },
+  {
+   "description": "Percentage you are allowed to order more against the Blanket Order Quantity. For example: If you have a Blanket Order of Quantity 100 units. and your Allowance is 10% then you are allowed to order 110 units.",
+   "fieldname": "over_order_allowance",
+   "fieldtype": "Float",
+   "label": "Over Order Allowance (%)"
+  },
+  {
+   "default": "0",
+   "fieldname": "dont_reserve_sales_order_qty_on_sales_return",
+   "fieldtype": "Check",
+   "label": "Don't Reserve Sales Order Qty on Sales Return"
   }
  ],
  "icon": "fa fa-cog",
@@ -215,4 +229,4 @@
  "sort_order": "DESC",
  "states": [],
  "track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/erpnext/selling/page/point_of_sale/point_of_sale.py b/erpnext/selling/page/point_of_sale/point_of_sale.py
index 158ac1d..62b3105 100644
--- a/erpnext/selling/page/point_of_sale/point_of_sale.py
+++ b/erpnext/selling/page/point_of_sale/point_of_sale.py
@@ -55,7 +55,7 @@
 			)
 
 	item_stock_qty, is_stock_item = get_stock_availability(item_code, warehouse)
-	item_stock_qty = item_stock_qty // item.get("conversion_factor")
+	item_stock_qty = item_stock_qty // item.get("conversion_factor", 1)
 	item.update({"actual_qty": item_stock_qty})
 
 	price = frappe.get_list(
@@ -63,8 +63,9 @@
 		filters={
 			"price_list": price_list,
 			"item_code": item_code,
+			"batch_no": batch_no,
 		},
-		fields=["uom", "stock_uom", "currency", "price_list_rate"],
+		fields=["uom", "stock_uom", "currency", "price_list_rate", "batch_no"],
 	)
 
 	def __sort(p):
@@ -167,7 +168,7 @@
 
 		item_price = frappe.get_all(
 			"Item Price",
-			fields=["price_list_rate", "currency", "uom"],
+			fields=["price_list_rate", "currency", "uom", "batch_no"],
 			filters={
 				"price_list": price_list,
 				"item_code": item.item_code,
@@ -190,9 +191,9 @@
 					"price_list_rate": price.get("price_list_rate"),
 					"currency": price.get("currency"),
 					"uom": price.uom or item.uom,
+					"batch_no": price.batch_no,
 				}
 			)
-
 	return {"items": result}
 
 
diff --git a/erpnext/selling/page/point_of_sale/pos_controller.js b/erpnext/selling/page/point_of_sale/pos_controller.js
index c442774..46320e5 100644
--- a/erpnext/selling/page/point_of_sale/pos_controller.js
+++ b/erpnext/selling/page/point_of_sale/pos_controller.js
@@ -522,7 +522,7 @@
 
 			const from_selector = field === 'qty' && value === "+1";
 			if (from_selector)
-				value = flt(item_row.qty) + flt(value);
+				value = flt(item_row.stock_qty) + flt(value);
 
 			if (item_row_exists) {
 				if (field === 'qty')
diff --git a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
index 525ae8e..58516f6 100644
--- a/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
+++ b/erpnext/selling/report/payment_terms_status_for_sales_order/test_payment_terms_status_for_sales_order.py
@@ -2,7 +2,7 @@
 
 import frappe
 from frappe.tests.utils import FrappeTestCase
-from frappe.utils import add_days, nowdate
+from frappe.utils import add_days, add_months, nowdate
 
 from erpnext.selling.doctype.sales_order.sales_order import make_sales_invoice
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
@@ -15,9 +15,16 @@
 
 
 class TestPaymentTermsStatusForSalesOrder(FrappeTestCase):
+	def setUp(self):
+		self.cleanup_old_entries()
+
 	def tearDown(self):
 		frappe.db.rollback()
 
+	def cleanup_old_entries(self):
+		frappe.db.delete("Sales Invoice", filters={"company": "_Test Company"})
+		frappe.db.delete("Sales Order", filters={"company": "_Test Company"})
+
 	def create_payment_terms_template(self):
 		# create template for 50-50 payments
 		template = None
@@ -348,7 +355,7 @@
 		item = create_item(item_code="_Test Excavator 1", is_stock_item=0)
 		transaction_date = nowdate()
 		so = make_sales_order(
-			transaction_date=add_days(transaction_date, -30),
+			transaction_date=add_months(transaction_date, -1),
 			delivery_date=add_days(transaction_date, -15),
 			item=item.item_code,
 			qty=10,
@@ -369,13 +376,15 @@
 		sinv.items[0].qty = 6
 		sinv.insert()
 		sinv.submit()
+
+		first_due_date = add_days(add_months(transaction_date, -1), 15)
 		columns, data, message, chart = execute(
 			frappe._dict(
 				{
 					"company": "_Test Company",
 					"item": item.item_code,
-					"from_due_date": add_days(transaction_date, -30),
-					"to_due_date": add_days(transaction_date, -15),
+					"from_due_date": add_months(transaction_date, -1),
+					"to_due_date": first_due_date,
 				}
 			)
 		)
@@ -384,11 +393,11 @@
 			{
 				"name": so.name,
 				"customer": so.customer,
-				"submitted": datetime.date.fromisoformat(add_days(transaction_date, -30)),
+				"submitted": datetime.date.fromisoformat(add_months(transaction_date, -1)),
 				"status": "Completed",
 				"payment_term": None,
 				"description": "_Test 50-50",
-				"due_date": datetime.date.fromisoformat(add_days(transaction_date, -15)),
+				"due_date": datetime.date.fromisoformat(first_due_date),
 				"invoice_portion": 50.0,
 				"currency": "INR",
 				"base_payment_amount": 500000.0,
diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
index f34f3e3..7d28f2b 100644
--- a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
+++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/item_group_wise_sales_target_variance.py
@@ -44,20 +44,30 @@
 
 	if not sales_users_data:
 		return
-	sales_users, item_groups = [], []
+	sales_users = []
+	sales_user_wise_item_groups = {}
 
 	for d in sales_users_data:
 		if d.parent not in sales_users:
 			sales_users.append(d.parent)
 
-		if d.item_group not in item_groups:
-			item_groups.append(d.item_group)
+		sales_user_wise_item_groups.setdefault(d.parent, [])
+		if d.item_group:
+			sales_user_wise_item_groups[d.parent].append(d.item_group)
 
 	date_field = "transaction_date" if filters.get("doctype") == "Sales Order" else "posting_date"
 
-	actual_data = get_actual_data(filters, item_groups, sales_users, date_field, sales_field)
+	actual_data = get_actual_data(filters, sales_users, date_field, sales_field)
 
-	return prepare_data(filters, sales_users_data, actual_data, date_field, period_list, sales_field)
+	return prepare_data(
+		filters,
+		sales_users_data,
+		sales_user_wise_item_groups,
+		actual_data,
+		date_field,
+		period_list,
+		sales_field,
+	)
 
 
 def get_columns(filters, period_list, partner_doctype):
@@ -142,7 +152,15 @@
 	return columns
 
 
-def prepare_data(filters, sales_users_data, actual_data, date_field, period_list, sales_field):
+def prepare_data(
+	filters,
+	sales_users_data,
+	sales_user_wise_item_groups,
+	actual_data,
+	date_field,
+	period_list,
+	sales_field,
+):
 	rows = {}
 
 	target_qty_amt_field = "target_qty" if filters.get("target_on") == "Quantity" else "target_amount"
@@ -173,9 +191,9 @@
 			for r in actual_data:
 				if (
 					r.get(sales_field) == d.parent
-					and r.item_group == d.item_group
 					and period.from_date <= r.get(date_field)
 					and r.get(date_field) <= period.to_date
+					and (not sales_user_wise_item_groups.get(d.parent) or r.item_group == d.item_group)
 				):
 					details[p_key] += r.get(qty_or_amount_field, 0)
 					details[variance_key] = details.get(p_key) - details.get(target_key)
@@ -186,7 +204,7 @@
 	return rows
 
 
-def get_actual_data(filters, item_groups, sales_users_or_territory_data, date_field, sales_field):
+def get_actual_data(filters, sales_users_or_territory_data, date_field, sales_field):
 	fiscal_year = get_fiscal_year(fiscal_year=filters.get("fiscal_year"), as_dict=1)
 	dates = [fiscal_year.year_start_date, fiscal_year.year_end_date]
 
@@ -213,7 +231,6 @@
 		WHERE
 			`tab{child_doc}`.parent = `tab{parent_doc}`.name
 			and `tab{parent_doc}`.docstatus = 1 and {cond}
-			and `tab{child_doc}`.item_group in ({item_groups})
 			and `tab{parent_doc}`.{date_field} between %s and %s""".format(
 			cond=cond,
 			date_field=date_field,
@@ -221,9 +238,8 @@
 			child_table=child_table,
 			parent_doc=filters.get("doctype"),
 			child_doc=filters.get("doctype") + " Item",
-			item_groups=",".join(["%s"] * len(item_groups)),
 		),
-		tuple(sales_users_or_territory_data + item_groups + dates),
+		tuple(sales_users_or_territory_data + dates),
 		as_dict=1,
 	)
 
diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py
index dda2466..8207122 100644
--- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py
+++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/sales_person_target_variance_based_on_item_group.py
@@ -8,6 +8,4 @@
 
 
 def execute(filters=None):
-	data = []
-
 	return get_data_column(filters, "Sales Person")
diff --git a/erpnext/selling/sales_common.js b/erpnext/selling/sales_common.js
index 5ce6e9c..f1df3a1 100644
--- a/erpnext/selling/sales_common.js
+++ b/erpnext/selling/sales_common.js
@@ -253,7 +253,7 @@
 	}
 
 	calculate_commission() {
-		if(!this.frm.fields_dict.commission_rate) return;
+		if(!this.frm.fields_dict.commission_rate || this.frm.doc.docstatus === 1) return;
 
 		if(this.frm.doc.commission_rate > 100) {
 			this.frm.set_value("commission_rate", 100);
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 07ee289..fcdf245 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -808,7 +808,7 @@
 			return existing_address
 
 	if out:
-		return min(out, key=lambda x: x[1])[0]  # find min by sort_key
+		return max(out, key=lambda x: x[1])[0]  # find max by sort_key
 	else:
 		return None
 
diff --git a/erpnext/setup/doctype/company/test_company.py b/erpnext/setup/doctype/company/test_company.py
index 29e056e..fd2fe30 100644
--- a/erpnext/setup/doctype/company/test_company.py
+++ b/erpnext/setup/doctype/company/test_company.py
@@ -11,6 +11,7 @@
 from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
 	get_charts_for_country,
 )
+from erpnext.setup.doctype.company.company import get_default_company_address
 
 test_ignore = ["Account", "Cost Center", "Payment Terms Template", "Salary Component", "Warehouse"]
 test_dependencies = ["Fiscal Year"]
@@ -132,6 +133,38 @@
 			self.assertTrue(lft >= min_lft)
 			self.assertTrue(rgt <= max_rgt)
 
+	def test_primary_address(self):
+		company = "_Test Company"
+
+		secondary = frappe.get_doc(
+			{
+				"address_title": "Non Primary",
+				"doctype": "Address",
+				"address_type": "Billing",
+				"address_line1": "Something",
+				"city": "Mumbai",
+				"state": "Maharashtra",
+				"country": "India",
+				"is_primary_address": 1,
+				"pincode": "400098",
+				"links": [
+					{
+						"link_doctype": "Company",
+						"link_name": company,
+					}
+				],
+			}
+		)
+		secondary.insert()
+		self.addCleanup(secondary.delete)
+
+		primary = frappe.copy_doc(secondary)
+		primary.is_primary_address = 1
+		primary.insert()
+		self.addCleanup(primary.delete)
+
+		self.assertEqual(get_default_company_address(company), primary.name)
+
 	def get_no_of_children(self, company):
 		def get_no_of_children(companies, no_of_children):
 			children = []
diff --git a/erpnext/setup/doctype/item_group/item_group.py b/erpnext/setup/doctype/item_group/item_group.py
index 2fdfcf6..f5432c1 100644
--- a/erpnext/setup/doctype/item_group/item_group.py
+++ b/erpnext/setup/doctype/item_group/item_group.py
@@ -36,8 +36,24 @@
 
 		self.make_route()
 		self.validate_item_group_defaults()
+		self.check_item_tax()
 		ECommerceSettings.validate_field_filters(self.filter_fields, enable_field_filters=True)
 
+	def check_item_tax(self):
+		"""Check whether Tax Rate is not entered twice for same Tax Type"""
+		check_list = []
+		for d in self.get("taxes"):
+			if d.item_tax_template:
+				if (d.item_tax_template, d.tax_category) in check_list:
+					frappe.throw(
+						_("{0} entered twice {1} in Item Taxes").format(
+							frappe.bold(d.item_tax_template),
+							"for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "",
+						)
+					)
+				else:
+					check_list.append((d.item_tax_template, d.tax_category))
+
 	def on_update(self):
 		NestedSet.on_update(self)
 		invalidate_cache_for(self)
@@ -148,12 +164,17 @@
 
 
 def get_parent_item_groups(item_group_name, from_item=False):
-	base_nav_page = {"name": _("All Products"), "route": "/all-products"}
+	settings = frappe.get_cached_doc("E Commerce Settings")
+
+	if settings.enable_field_filters:
+		base_nav_page = {"name": _("Shop by Category"), "route": "/shop-by-category"}
+	else:
+		base_nav_page = {"name": _("All Products"), "route": "/all-products"}
 
 	if from_item and frappe.request.environ.get("HTTP_REFERER"):
 		# base page after 'Home' will vary on Item page
 		last_page = frappe.request.environ["HTTP_REFERER"].split("/")[-1].split("?")[0]
-		if last_page and last_page == "shop-by-category":
+		if last_page and last_page in ("shop-by-category", "all-products"):
 			base_nav_page_title = " ".join(last_page.split("-")).title()
 			base_nav_page = {"name": _(base_nav_page_title), "route": "/" + last_page}
 
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 1f7dddf..088958d 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -155,7 +155,7 @@
 		{
 			"item_label": "Documentation",
 			"item_type": "Route",
-			"route": "https://erpnext.com/docs/user/manual",
+			"route": "https://docs.erpnext.com/docs/v14/user/manual/en/introduction",
 			"is_standard": 1,
 		},
 		{
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index f14288b..3b9fe7b 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -6,7 +6,8 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.model.naming import make_autoname, revert_series_if_last
-from frappe.utils import cint, flt, get_link_to_form
+from frappe.query_builder.functions import CurDate, Sum, Timestamp
+from frappe.utils import cint, flt, get_link_to_form, nowtime
 from frappe.utils.data import add_days
 from frappe.utils.jinja import render_template
 
@@ -176,45 +177,41 @@
 	:param warehouse: Optional - give qty for this warehouse
 	:param item_code: Optional - give qty for this item"""
 
+	sle = frappe.qb.DocType("Stock Ledger Entry")
+
 	out = 0
 	if batch_no and warehouse:
-		cond = ""
-		if posting_date and posting_time:
-			cond = " and timestamp(posting_date, posting_time) <= timestamp('{0}', '{1}')".format(
-				posting_date, posting_time
+		query = (
+			frappe.qb.from_(sle)
+			.select(Sum(sle.actual_qty))
+			.where((sle.is_cancelled == 0) & (sle.warehouse == warehouse) & (sle.batch_no == batch_no))
+		)
+
+		if posting_date:
+			if posting_time is None:
+				posting_time = nowtime()
+
+			query = query.where(
+				Timestamp(sle.posting_date, sle.posting_time) <= Timestamp(posting_date, posting_time)
 			)
 
-		out = float(
-			frappe.db.sql(
-				"""select sum(actual_qty)
-			from `tabStock Ledger Entry`
-			where is_cancelled = 0 and warehouse=%s and batch_no=%s {0}""".format(
-					cond
-				),
-				(warehouse, batch_no),
-			)[0][0]
-			or 0
-		)
+		out = query.run(as_list=True)[0][0] or 0
 
 	if batch_no and not warehouse:
-		out = frappe.db.sql(
-			"""select warehouse, sum(actual_qty) as qty
-			from `tabStock Ledger Entry`
-			where is_cancelled = 0 and batch_no=%s
-			group by warehouse""",
-			batch_no,
-			as_dict=1,
-		)
+		out = (
+			frappe.qb.from_(sle)
+			.select(sle.warehouse, Sum(sle.actual_qty).as_("qty"))
+			.where((sle.is_cancelled == 0) & (sle.batch_no == batch_no))
+			.groupby(sle.warehouse)
+		).run(as_dict=True)
 
 	if not batch_no and item_code and warehouse:
-		out = frappe.db.sql(
-			"""select batch_no, sum(actual_qty) as qty
-			from `tabStock Ledger Entry`
-			where is_cancelled = 0 and item_code = %s and warehouse=%s
-			group by batch_no""",
-			(item_code, warehouse),
-			as_dict=1,
-		)
+		out = (
+			frappe.qb.from_(sle)
+			.select(sle.batch_no, Sum(sle.actual_qty).as_("qty"))
+			.where((sle.is_cancelled == 0) & (sle.item_code == item_code) & (sle.warehouse == warehouse))
+			.groupby(sle.batch_no)
+		).run(as_dict=True)
 
 	return out
 
@@ -310,40 +307,44 @@
 def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
-	cond = ""
+	batch = frappe.qb.DocType("Batch")
+	sle = frappe.qb.DocType("Stock Ledger Entry")
+
+	query = (
+		frappe.qb.from_(batch)
+		.join(sle)
+		.on(batch.batch_id == sle.batch_no)
+		.select(
+			batch.batch_id,
+			Sum(sle.actual_qty).as_("qty"),
+		)
+		.where(
+			(sle.item_code == item_code)
+			& (sle.warehouse == warehouse)
+			& (sle.is_cancelled == 0)
+			& ((batch.expiry_date >= CurDate()) | (batch.expiry_date.isnull()))
+		)
+		.groupby(batch.batch_id)
+		.orderby(batch.expiry_date, batch.creation)
+	)
+
 	if serial_no and frappe.get_cached_value("Item", item_code, "has_batch_no"):
 		serial_nos = get_serial_nos(serial_no)
-		batch = frappe.get_all(
+		batches = frappe.get_all(
 			"Serial No",
 			fields=["distinct batch_no"],
 			filters={"item_code": item_code, "warehouse": warehouse, "name": ("in", serial_nos)},
 		)
 
-		if not batch:
+		if not batches:
 			validate_serial_no_with_batch(serial_nos, item_code)
 
-		if batch and len(batch) > 1:
+		if batches and len(batches) > 1:
 			return []
 
-		cond = " and `tabBatch`.name = %s" % (frappe.db.escape(batch[0].batch_no))
+		query = query.where(batch.name == batches[0].batch_no)
 
-	return frappe.db.sql(
-		"""
-		select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty
-		from `tabBatch`
-			join `tabStock Ledger Entry` ignore index (item_code, warehouse)
-				on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
-		where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
-			and `tabStock Ledger Entry`.is_cancelled = 0
-			and (`tabBatch`.expiry_date >= CURRENT_DATE or `tabBatch`.expiry_date IS NULL) {0}
-		group by batch_id
-		order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
-	""".format(
-			cond
-		),
-		(item_code, warehouse),
-		as_dict=True,
-	)
+	return query.run(as_dict=True)
 
 
 def validate_serial_no_with_batch(serial_nos, item_code):
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 903e2af..22d8135 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1180,6 +1180,53 @@
 
 		self.assertTrue(return_dn.docstatus == 1)
 
+	def test_reserve_qty_on_sales_return(self):
+		frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
+		self.reserved_qty_check()
+
+	def test_dont_reserve_qty_on_sales_return(self):
+		frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 1)
+		self.reserved_qty_check()
+
+	def reserved_qty_check(self):
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+		from erpnext.selling.doctype.sales_order.sales_order import make_delivery_note
+		from erpnext.stock.stock_balance import get_reserved_qty
+
+		dont_reserve_qty = frappe.db.get_single_value(
+			"Selling Settings", "dont_reserve_sales_order_qty_on_sales_return"
+		)
+
+		item = make_item().name
+		warehouse = "_Test Warehouse - _TC"
+		qty_to_reserve = 5
+
+		so = make_sales_order(item_code=item, qty=qty_to_reserve)
+
+		# Make qty avl for test.
+		make_stock_entry(item_code=item, to_warehouse=warehouse, qty=10, basic_rate=100)
+
+		# Test that item qty has been reserved on submit of sales order.
+		self.assertEqual(get_reserved_qty(item, warehouse), qty_to_reserve)
+
+		dn = make_delivery_note(so.name)
+		dn.save().submit()
+
+		# Test that item qty is no longer reserved since qty has been delivered.
+		self.assertEqual(get_reserved_qty(item, warehouse), 0)
+
+		dn_return = make_return_doc("Delivery Note", dn.name)
+		dn_return.save().submit()
+
+		returned = frappe.get_doc("Delivery Note", dn_return.name)
+		returned.update_prevdoc_status()
+
+		# Test that item qty is not reserved on sales return, if selling setting don't reserve qty is checked.
+		self.assertEqual(get_reserved_qty(item, warehouse), 0 if dont_reserve_qty else qty_to_reserve)
+
+	def tearDown(self):
+		frappe.db.set_single_value("Selling Settings", "dont_reserve_sales_order_qty_on_sales_return", 0)
+
 
 def create_delivery_note(**args):
 	dn = frappe.new_doc("Delivery Note")
diff --git a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
index 916ab2a..180adee 100644
--- a/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
+++ b/erpnext/stock/doctype/delivery_note_item/delivery_note_item.json
@@ -83,6 +83,8 @@
   "actual_qty",
   "installed_qty",
   "item_tax_rate",
+  "column_break_atna",
+  "received_qty",
   "accounting_details_section",
   "expense_account",
   "allow_zero_valuation_rate",
@@ -636,7 +638,8 @@
    "no_copy": 1,
    "options": "Sales Invoice",
    "print_hide": 1,
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "fieldname": "so_detail",
@@ -831,13 +834,27 @@
    "fieldname": "material_request_item",
    "fieldtype": "Data",
    "label": "Material Request Item"
+  },
+  {
+   "fieldname": "column_break_atna",
+   "fieldtype": "Column Break"
+  },
+  {
+   "depends_on": "eval: parent.is_internal_customer",
+   "fieldname": "received_qty",
+   "fieldtype": "Float",
+   "label": "Received Qty",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1,
+   "report_hide": 1
   }
  ],
  "idx": 1,
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-11-09 12:17:50.850142",
+ "modified": "2023-04-06 09:28:29.182053",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note Item",
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index c06700a..3cc59be 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -117,7 +117,6 @@
 		self.validate_auto_reorder_enabled_in_stock_settings()
 		self.cant_change()
 		self.validate_item_tax_net_rate_range()
-		set_item_tax_from_hsn_code(self)
 
 		if not self.is_new():
 			self.old_item_group = frappe.db.get_value(self.doctype, self.name, "item_group")
@@ -352,10 +351,15 @@
 		check_list = []
 		for d in self.get("taxes"):
 			if d.item_tax_template:
-				if d.item_tax_template in check_list:
-					frappe.throw(_("{0} entered twice in Item Tax").format(d.item_tax_template))
+				if (d.item_tax_template, d.tax_category) in check_list:
+					frappe.throw(
+						_("{0} entered twice {1} in Item Taxes").format(
+							frappe.bold(d.item_tax_template),
+							"for tax category {0}".format(frappe.bold(d.tax_category)) if d.tax_category else "",
+						)
+					)
 				else:
-					check_list.append(d.item_tax_template)
+					check_list.append((d.item_tax_template, d.tax_category))
 
 	def validate_barcode(self):
 		import barcodenumber
@@ -377,7 +381,9 @@
 						"" if item_barcode.barcode_type not in options else item_barcode.barcode_type
 					)
 					if item_barcode.barcode_type:
-						barcode_type = convert_erpnext_to_barcodenumber(item_barcode.barcode_type.upper())
+						barcode_type = convert_erpnext_to_barcodenumber(
+							item_barcode.barcode_type.upper(), item_barcode.barcode
+						)
 						if barcode_type in barcodenumber.barcodes():
 							if not barcodenumber.check_code(barcode_type, item_barcode.barcode):
 								frappe.throw(
@@ -982,20 +988,29 @@
 				)
 
 
-def convert_erpnext_to_barcodenumber(erpnext_number):
+def convert_erpnext_to_barcodenumber(erpnext_number, barcode):
+	if erpnext_number == "EAN":
+		ean_type = {
+			8: "EAN8",
+			13: "EAN13",
+		}
+		barcode_length = len(barcode)
+		if barcode_length in ean_type:
+			return ean_type[barcode_length]
+
+		return erpnext_number
+
 	convert = {
 		"UPC-A": "UPCA",
 		"CODE-39": "CODE39",
-		"EAN": "EAN13",
-		"EAN-12": "EAN",
-		"EAN-8": "EAN8",
 		"ISBN-10": "ISBN10",
 		"ISBN-13": "ISBN13",
 	}
+
 	if erpnext_number in convert:
 		return convert[erpnext_number]
-	else:
-		return erpnext_number
+
+	return erpnext_number
 
 
 def make_item_price(item, price_list_name, item_price):
@@ -1305,11 +1320,6 @@
 			frappe.publish_progress(count / total * 100, title=_("Updating Variants..."))
 
 
-@erpnext.allow_regional
-def set_item_tax_from_hsn_code(item):
-	pass
-
-
 def validate_item_default_company_links(item_defaults: List[ItemDefault]) -> None:
 	for item_default in item_defaults:
 		for doctype, field in [
diff --git a/erpnext/stock/doctype/item/test_item.py b/erpnext/stock/doctype/item/test_item.py
index 67ed90d..0c6dc77 100644
--- a/erpnext/stock/doctype/item/test_item.py
+++ b/erpnext/stock/doctype/item/test_item.py
@@ -581,8 +581,9 @@
 			},
 			{"barcode": "72527273070", "barcode_type": "UPC-A"},
 			{"barcode": "123456", "barcode_type": "CODE-39"},
-			{"barcode": "401268452363", "barcode_type": "EAN-12"},
-			{"barcode": "90311017", "barcode_type": "EAN-8"},
+			{"barcode": "401268452363", "barcode_type": "EAN"},
+			{"barcode": "90311017", "barcode_type": "EAN"},
+			{"barcode": "73513537", "barcode_type": "EAN"},
 			{"barcode": "0123456789012", "barcode_type": "GS1"},
 			{"barcode": "2211564566668", "barcode_type": "GTIN"},
 			{"barcode": "0256480249", "barcode_type": "ISBN"},
diff --git a/erpnext/stock/doctype/item_alternative/item_alternative.py b/erpnext/stock/doctype/item_alternative/item_alternative.py
index fb1a28d..0c24d3c 100644
--- a/erpnext/stock/doctype/item_alternative/item_alternative.py
+++ b/erpnext/stock/doctype/item_alternative/item_alternative.py
@@ -54,7 +54,7 @@
 		if not item_data.allow_alternative_item:
 			frappe.throw(alternate_item_check_msg.format(self.item_code))
 		if self.two_way and not alternative_item_data.allow_alternative_item:
-			frappe.throw(alternate_item_check_msg.format(self.item_code))
+			frappe.throw(alternate_item_check_msg.format(self.alternative_item_code))
 
 	def validate_duplicate(self):
 		if frappe.db.get_value(
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 6426fe8..8aeb751 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -10,6 +10,7 @@
 import frappe
 from frappe import _, msgprint
 from frappe.model.mapper import get_mapped_doc
+from frappe.query_builder.functions import Sum
 from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, new_line_sep, nowdate
 
 from erpnext.buying.utils import check_on_hold_or_closed_status, validate_for_items
@@ -180,6 +181,34 @@
 		self.update_requested_qty()
 		self.update_requested_qty_in_production_plan()
 
+	def get_mr_items_ordered_qty(self, mr_items):
+		mr_items_ordered_qty = {}
+		mr_items = [d.name for d in self.get("items") if d.name in mr_items]
+
+		doctype = qty_field = None
+		if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
+			doctype = frappe.qb.DocType("Stock Entry Detail")
+			qty_field = doctype.transfer_qty
+		elif self.material_request_type == "Manufacture":
+			doctype = frappe.qb.DocType("Work Order")
+			qty_field = doctype.qty
+
+		if doctype and qty_field:
+			query = (
+				frappe.qb.from_(doctype)
+				.select(doctype.material_request_item, Sum(qty_field))
+				.where(
+					(doctype.material_request == self.name)
+					& (doctype.material_request_item.isin(mr_items))
+					& (doctype.docstatus == 1)
+				)
+				.groupby(doctype.material_request_item)
+			)
+
+			mr_items_ordered_qty = frappe._dict(query.run())
+
+		return mr_items_ordered_qty
+
 	def update_completed_qty(self, mr_items=None, update_modified=True):
 		if self.material_request_type == "Purchase":
 			return
@@ -187,18 +216,13 @@
 		if not mr_items:
 			mr_items = [d.name for d in self.get("items")]
 
+		mr_items_ordered_qty = self.get_mr_items_ordered_qty(mr_items)
+		mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
+
 		for d in self.get("items"):
 			if d.name in mr_items:
 				if self.material_request_type in ("Material Issue", "Material Transfer", "Customer Provided"):
-					d.ordered_qty = flt(
-						frappe.db.sql(
-							"""select sum(transfer_qty)
-						from `tabStock Entry Detail` where material_request = %s
-						and material_request_item = %s and docstatus = 1""",
-							(self.name, d.name),
-						)[0][0]
-					)
-					mr_qty_allowance = frappe.db.get_single_value("Stock Settings", "mr_qty_allowance")
+					d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
 
 					if mr_qty_allowance:
 						allowed_qty = d.qty + (d.qty * (mr_qty_allowance / 100))
@@ -217,14 +241,7 @@
 						)
 
 				elif self.material_request_type == "Manufacture":
-					d.ordered_qty = flt(
-						frappe.db.sql(
-							"""select sum(qty)
-						from `tabWork Order` where material_request = %s
-						and material_request_item = %s and docstatus = 1""",
-							(self.name, d.name),
-						)[0][0]
-					)
+					d.ordered_qty = flt(mr_items_ordered_qty.get(d.name))
 
 				frappe.db.set_value(d.doctype, d.name, "ordered_qty", d.ordered_qty)
 
@@ -587,6 +604,9 @@
 
 	def set_missing_values(source, target):
 		target.purpose = source.material_request_type
+		target.from_warehouse = source.set_from_warehouse
+		target.to_warehouse = source.set_warehouse
+
 		if source.job_card:
 			target.purpose = "Material Transfer for Manufacture"
 
@@ -722,6 +742,7 @@
 def make_in_transit_stock_entry(source_name, in_transit_warehouse):
 	ste_doc = make_stock_entry(source_name)
 	ste_doc.add_to_transit = 1
+	ste_doc.to_warehouse = in_transit_warehouse
 
 	for row in ste_doc.items:
 		row.t_warehouse = in_transit_warehouse
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index bf3b5dd..46d6e9e 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -172,8 +172,8 @@
 			if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
 				frappe.throw(
 					_(
-						f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}."
-					)
+						"You are picking more than required quantity for the item {0}. Check if there is any other pick list created for the sales order {1}."
+					).format(row.item_code, row.sales_order)
 				)
 
 	@frappe.whitelist()
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index c8a4bd3..d268cc1 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -65,6 +65,16 @@
 				"percent_join_field": "purchase_invoice",
 				"overflow_type": "receipt",
 			},
+			{
+				"source_dt": "Purchase Receipt Item",
+				"target_dt": "Delivery Note Item",
+				"join_field": "delivery_note_item",
+				"source_field": "received_qty",
+				"target_field": "received_qty",
+				"target_parent_dt": "Delivery Note",
+				"target_ref_field": "qty",
+				"overflow_type": "receipt",
+			},
 		]
 
 		if cint(self.is_return):
@@ -293,6 +303,7 @@
 			get_purchase_document_details,
 		)
 
+		stock_rbnb = None
 		if erpnext.is_perpetual_inventory_enabled(self.company):
 			stock_rbnb = self.get_company_default("stock_received_but_not_billed")
 			landed_cost_entries = get_item_account_wise_additional_cost(self.name)
@@ -450,6 +461,21 @@
 								item=d,
 							)
 
+					if d.rate_difference_with_purchase_invoice and stock_rbnb:
+						account_currency = get_account_currency(stock_rbnb)
+						self.add_gl_entry(
+							gl_entries=gl_entries,
+							account=stock_rbnb,
+							cost_center=d.cost_center,
+							debit=0.0,
+							credit=flt(d.rate_difference_with_purchase_invoice),
+							remarks=_("Adjustment based on Purchase Invoice rate"),
+							against_account=warehouse_account_name,
+							account_currency=account_currency,
+							project=d.project,
+							item=d,
+						)
+
 					# sub-contracting warehouse
 					if flt(d.rm_supp_cost) and warehouse_account.get(self.supplier_warehouse):
 						self.add_gl_entry(
@@ -470,6 +496,7 @@
 						+ flt(d.landed_cost_voucher_amount)
 						+ flt(d.rm_supp_cost)
 						+ flt(d.item_tax_amount)
+						+ flt(d.rate_difference_with_purchase_invoice)
 					)
 
 					divisional_loss = flt(
@@ -765,7 +792,7 @@
 			updated_pr += update_billed_amount_based_on_po(po_details, update_modified)
 
 		for pr in set(updated_pr):
-			pr_doc = self if (pr == self.name) else frappe.get_cached_doc("Purchase Receipt", pr)
+			pr_doc = self if (pr == self.name) else frappe.get_doc("Purchase Receipt", pr)
 			update_billing_percentage(pr_doc, update_modified=update_modified)
 
 		self.load_from_db()
@@ -881,7 +908,7 @@
 	return {d.po_detail: flt(d.billed_amt) for d in query}
 
 
-def update_billing_percentage(pr_doc, update_modified=True):
+def update_billing_percentage(pr_doc, update_modified=True, adjust_incoming_rate=False):
 	# Reload as billed amount was set in db directly
 	pr_doc.load_from_db()
 
@@ -897,6 +924,12 @@
 
 		total_amount += total_billable_amount
 		total_billed_amount += flt(item.billed_amt)
+		if adjust_incoming_rate:
+			adjusted_amt = 0.0
+			if item.billed_amt and item.amount:
+				adjusted_amt = flt(item.billed_amt) - flt(item.amount)
+
+			item.db_set("rate_difference_with_purchase_invoice", adjusted_amt, update_modified=False)
 
 	percent_billed = round(100 * (total_billed_amount / (total_amount or 1)), 6)
 	pr_doc.db_set("per_billed", percent_billed)
@@ -906,6 +939,26 @@
 		pr_doc.set_status(update=True)
 		pr_doc.notify_update()
 
+	if adjust_incoming_rate:
+		adjust_incoming_rate_for_pr(pr_doc)
+
+
+def adjust_incoming_rate_for_pr(doc):
+	doc.update_valuation_rate(reset_outgoing_rate=False)
+
+	for item in doc.get("items"):
+		item.db_update()
+
+	doc.docstatus = 2
+	doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
+	doc.make_gl_entries_on_cancel()
+
+	# update stock & gl entries for submit state of PR
+	doc.docstatus = 1
+	doc.update_stock_ledger(allow_negative_stock=True, via_landed_cost_voucher=True)
+	doc.make_gl_entries()
+	doc.repost_future_sle_and_gle()
+
 
 def get_item_wise_returned_qty(pr_doc):
 	items = [d.name for d in pr_doc.items]
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index b634146..7567cfe 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -1544,6 +1544,72 @@
 		res = get_item_details(args)
 		self.assertEqual(res.get("last_purchase_rate"), 100)
 
+	def test_validate_received_qty_for_internal_pr(self):
+		prepare_data_for_internal_transfer()
+		customer = "_Test Internal Customer 2"
+		company = "_Test Company with perpetual inventory"
+		from_warehouse = create_warehouse("_Test Internal From Warehouse New", company=company)
+		target_warehouse = create_warehouse("_Test Internal GIT Warehouse New", company=company)
+		to_warehouse = create_warehouse("_Test Internal To Warehouse New", company=company)
+
+		# Step 1: Create Item
+		item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})
+
+		# Step 2: Create Stock Entry (Material Receipt)
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+		make_stock_entry(
+			purpose="Material Receipt",
+			item_code=item.name,
+			qty=15,
+			company=company,
+			to_warehouse=from_warehouse,
+		)
+
+		# Step 3: Create Delivery Note with Internal Customer
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+		dn = create_delivery_note(
+			item_code=item.name,
+			company=company,
+			customer=customer,
+			cost_center="Main - TCP1",
+			expense_account="Cost of Goods Sold - TCP1",
+			qty=10,
+			rate=100,
+			warehouse=from_warehouse,
+			target_warehouse=target_warehouse,
+		)
+
+		# Step 4: Create Internal Purchase Receipt
+		from erpnext.controllers.status_updater import OverAllowanceError
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
+
+		pr = make_inter_company_purchase_receipt(dn.name)
+		pr.items[0].qty = 15
+		pr.items[0].from_warehouse = target_warehouse
+		pr.items[0].warehouse = to_warehouse
+		pr.items[0].rejected_warehouse = from_warehouse
+		pr.save()
+
+		self.assertRaises(OverAllowanceError, pr.submit)
+
+		# Step 5: Test Over Receipt Allowance
+		frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 50)
+
+		make_stock_entry(
+			purpose="Material Transfer",
+			item_code=item.name,
+			qty=5,
+			company=company,
+			from_warehouse=from_warehouse,
+			to_warehouse=target_warehouse,
+		)
+
+		pr.submit()
+
+		frappe.db.set_single_value("Stock Settings", "over_delivery_receipt_allowance", 0)
+
 
 def prepare_data_for_internal_transfer():
 	from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
index 7a350b9..cd320fd 100644
--- a/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
+++ b/erpnext/stock/doctype/purchase_receipt_item/purchase_receipt_item.json
@@ -69,6 +69,7 @@
   "item_tax_amount",
   "rm_supp_cost",
   "landed_cost_voucher_amount",
+  "rate_difference_with_purchase_invoice",
   "billed_amt",
   "warehouse_and_reference",
   "warehouse",
@@ -1007,12 +1008,20 @@
    "fieldtype": "Check",
    "label": "Has Item Scanned",
    "read_only": 1
+  },
+  {
+   "fieldname": "rate_difference_with_purchase_invoice",
+   "fieldtype": "Currency",
+   "label": "Rate Difference with Purchase Invoice",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-01-18 15:48:58.114923",
+ "modified": "2023-02-28 15:43:04.470104",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt Item",
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.py b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
index 2a9f091..9673c81 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.py
@@ -6,7 +6,7 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.model.mapper import get_mapped_doc
-from frappe.utils import cint, cstr, flt
+from frappe.utils import cint, cstr, flt, get_number_format_info
 
 from erpnext.stock.doctype.quality_inspection_template.quality_inspection_template import (
 	get_template_details,
@@ -156,7 +156,9 @@
 		for i in range(1, 11):
 			reading_value = reading.get("reading_" + str(i))
 			if reading_value is not None and reading_value.strip():
-				result = flt(reading.get("min_value")) <= flt(reading_value) <= flt(reading.get("max_value"))
+				result = (
+					flt(reading.get("min_value")) <= parse_float(reading_value) <= flt(reading.get("max_value"))
+				)
 				if not result:
 					return False
 		return True
@@ -196,7 +198,7 @@
 			# numeric readings
 			for i in range(1, 11):
 				field = "reading_" + str(i)
-				data[field] = flt(reading.get(field))
+				data[field] = parse_float(reading.get(field))
 			data["mean"] = self.calculate_mean(reading)
 
 		return data
@@ -210,7 +212,7 @@
 		for i in range(1, 11):
 			reading_value = reading.get("reading_" + str(i))
 			if reading_value is not None and reading_value.strip():
-				readings_list.append(flt(reading_value))
+				readings_list.append(parse_float(reading_value))
 
 		actual_mean = mean(readings_list) if readings_list else 0
 		return actual_mean
@@ -324,3 +326,19 @@
 	)
 
 	return doc
+
+
+def parse_float(num: str) -> float:
+	"""Since reading_# fields are `Data` field they might contain number which
+	is representation in user's prefered number format instead of machine
+	readable format. This function converts them to machine readable format."""
+
+	number_format = frappe.db.get_default("number_format") or "#,###.##"
+	decimal_str, comma_str, _number_format_precision = get_number_format_info(number_format)
+
+	if decimal_str == "," and comma_str == ".":
+		num = num.replace(",", "#$")
+		num = num.replace(".", ",")
+		num = num.replace("#$", ".")
+
+	return flt(num)
diff --git a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
index 4f19643..9d2e139 100644
--- a/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
+++ b/erpnext/stock/doctype/quality_inspection/test_quality_inspection.py
@@ -2,7 +2,7 @@
 # See license.txt
 
 import frappe
-from frappe.tests.utils import FrappeTestCase
+from frappe.tests.utils import FrappeTestCase, change_settings
 from frappe.utils import nowdate
 
 from erpnext.controllers.stock_controller import (
@@ -216,6 +216,40 @@
 		qa.save()
 		self.assertEqual(qa.status, "Accepted")
 
+	@change_settings("System Settings", {"number_format": "#.###,##"})
+	def test_diff_number_format(self):
+		self.assertEqual(frappe.db.get_default("number_format"), "#.###,##")  # sanity check
+
+		# Test QI based on acceptance values (Non formula)
+		dn = create_delivery_note(item_code="_Test Item with QA", do_not_submit=True)
+		readings = [
+			{
+				"specification": "Iron Content",  # numeric reading
+				"min_value": 60,
+				"max_value": 100,
+				"reading_1": "70,000",
+			},
+			{
+				"specification": "Iron Content",  # numeric reading
+				"min_value": 60,
+				"max_value": 100,
+				"reading_1": "1.100,00",
+			},
+		]
+
+		qa = create_quality_inspection(
+			reference_type="Delivery Note", reference_name=dn.name, readings=readings, do_not_save=True
+		)
+
+		qa.save()
+
+		# status must be auto set as per formula
+		self.assertEqual(qa.readings[0].status, "Accepted")
+		self.assertEqual(qa.readings[1].status, "Rejected")
+
+		qa.delete()
+		dn.delete()
+
 
 def create_quality_inspection(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.json b/erpnext/stock/doctype/stock_entry/stock_entry.json
index 9c0f1fc..bc5533f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.json
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.json
@@ -27,7 +27,6 @@
   "set_posting_time",
   "inspection_required",
   "apply_putaway_rule",
-  "items_tab",
   "bom_info_section",
   "from_bom",
   "use_multi_level_bom",
@@ -256,7 +255,7 @@
    "description": "As per Stock UOM",
    "fieldname": "fg_completed_qty",
    "fieldtype": "Float",
-   "label": "For Quantity",
+   "label": "Finished Good Quantity ",
    "oldfieldname": "fg_completed_qty",
    "oldfieldtype": "Currency",
    "print_hide": 1
@@ -612,11 +611,7 @@
    "read_only": 1
   },
   {
-   "fieldname": "items_tab",
-   "fieldtype": "Tab Break",
-   "label": "Items"
-  },
-  {
+   "collapsible": 1,
    "fieldname": "bom_info_section",
    "fieldtype": "Section Break",
    "label": "BOM Info"
@@ -644,8 +639,10 @@
    "oldfieldtype": "Section Break"
   },
   {
+   "collapsible": 1,
    "fieldname": "section_break_7qsm",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "Process Loss"
   },
   {
    "depends_on": "process_loss_percentage",
@@ -677,7 +674,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-01-03 16:02:50.741816",
+ "modified": "2023-04-06 12:42:56.673180",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Entry",
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 7f69397..36c875f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -658,6 +658,7 @@
 		)
 		finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
 
+		items = []
 		# Set basic rate for incoming items
 		for d in self.get("items"):
 			if d.s_warehouse or d.set_basic_rate_manually:
@@ -665,12 +666,7 @@
 
 			if d.allow_zero_valuation_rate:
 				d.basic_rate = 0.0
-				frappe.msgprint(
-					_(
-						"Row {0}: Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {1}"
-					).format(d.idx, d.item_code),
-					alert=1,
-				)
+				items.append(d.item_code)
 
 			elif d.is_finished_item:
 				if self.purpose == "Manufacture":
@@ -697,6 +693,20 @@
 			d.basic_rate = flt(d.basic_rate)
 			d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
 
+		if items:
+			message = ""
+
+			if len(items) > 1:
+				message = _(
+					"Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}"
+				).format(", ".join(frappe.bold(item) for item in items))
+			else:
+				message = _(
+					"Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}"
+				).format(frappe.bold(items[0]))
+
+			frappe.msgprint(message, alert=True)
+
 	def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
 		outgoing_items_cost = 0.0
 		for d in self.get("items"):
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 398b3c9..e304bd1 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -4,7 +4,8 @@
 from typing import Optional
 
 import frappe
-from frappe import _, msgprint
+from frappe import _, bold, msgprint
+from frappe.query_builder.functions import CombineDatetime, Sum
 from frappe.utils import cint, cstr, flt
 
 import erpnext
@@ -89,7 +90,7 @@
 
 				if item_dict.get("serial_nos"):
 					item.current_serial_no = item_dict.get("serial_nos")
-					if self.purpose == "Stock Reconciliation" and not item.serial_no:
+					if self.purpose == "Stock Reconciliation" and not item.serial_no and item.qty:
 						item.serial_no = item.current_serial_no
 
 				item.current_qty = item_dict.get("qty")
@@ -140,6 +141,14 @@
 
 			self.validate_item(row.item_code, row)
 
+			if row.serial_no and not row.qty:
+				self.validation_messages.append(
+					_get_msg(
+						row_num,
+						f"Quantity should not be zero for the {bold(row.item_code)} since serial nos are specified",
+					)
+				)
+
 			# validate warehouse
 			if not frappe.db.get_value("Warehouse", row.warehouse):
 				self.validation_messages.append(_get_msg(row_num, _("Warehouse not found in the system")))
@@ -397,6 +406,7 @@
 				"voucher_type": self.doctype,
 				"voucher_no": self.name,
 				"voucher_detail_no": row.name,
+				"actual_qty": 0,
 				"company": self.company,
 				"stock_uom": frappe.db.get_value("Item", row.item_code, "stock_uom"),
 				"is_cancelled": 1 if self.docstatus == 2 else 0,
@@ -423,6 +433,8 @@
 				data.valuation_rate = flt(row.valuation_rate)
 				data.stock_value_difference = -1 * flt(row.amount_difference)
 
+		self.update_inventory_dimensions(row, data)
+
 		return data
 
 	def make_sle_on_cancel(self):
@@ -558,6 +570,64 @@
 		else:
 			self._cancel()
 
+	def recalculate_current_qty(self, item_code, batch_no):
+		for row in self.items:
+			if not (row.item_code == item_code and row.batch_no == batch_no):
+				continue
+
+			row.current_qty = get_batch_qty_for_stock_reco(
+				item_code, row.warehouse, batch_no, self.posting_date, self.posting_time, self.name
+			)
+
+			qty, val_rate = get_stock_balance(
+				item_code,
+				row.warehouse,
+				self.posting_date,
+				self.posting_time,
+				with_valuation_rate=True,
+			)
+
+			row.current_valuation_rate = val_rate
+
+			row.db_set(
+				{
+					"current_qty": row.current_qty,
+					"current_valuation_rate": row.current_valuation_rate,
+					"current_amount": flt(row.current_qty * row.current_valuation_rate),
+				}
+			)
+
+
+def get_batch_qty_for_stock_reco(
+	item_code, warehouse, batch_no, posting_date, posting_time, voucher_no
+):
+	ledger = frappe.qb.DocType("Stock Ledger Entry")
+
+	query = (
+		frappe.qb.from_(ledger)
+		.select(
+			Sum(ledger.actual_qty).as_("batch_qty"),
+		)
+		.where(
+			(ledger.item_code == item_code)
+			& (ledger.warehouse == warehouse)
+			& (ledger.docstatus == 1)
+			& (ledger.is_cancelled == 0)
+			& (ledger.batch_no == batch_no)
+			& (ledger.posting_date <= posting_date)
+			& (
+				CombineDatetime(ledger.posting_date, ledger.posting_time)
+				<= CombineDatetime(posting_date, posting_time)
+			)
+			& (ledger.voucher_no != voucher_no)
+		)
+		.groupby(ledger.batch_no)
+	)
+
+	sle = query.run(as_dict=True)
+
+	return flt(sle[0].batch_qty) if sle else 0
+
 
 @frappe.whitelist()
 def get_items(
diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
index eaea301..7d59441 100644
--- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py
@@ -676,6 +676,79 @@
 		self.assertEqual(flt(sl_entry.actual_qty), 1.0)
 		self.assertEqual(flt(sl_entry.qty_after_transaction), 1.0)
 
+	def test_backdated_stock_reco_entry(self):
+		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+
+		item_code = self.make_item(
+			"Test New Batch Item ABCV",
+			{
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"batch_number_series": "BNS9.####",
+				"create_new_batch": 1,
+			},
+		).name
+
+		warehouse = "_Test Warehouse - _TC"
+
+		# Added 100 Qty, Balace Qty 100
+		se1 = make_stock_entry(
+			item_code=item_code, posting_time="09:00:00", target=warehouse, qty=100, basic_rate=700
+		)
+
+		# Removed 50 Qty, Balace Qty 50
+		se2 = make_stock_entry(
+			item_code=item_code,
+			batch_no=se1.items[0].batch_no,
+			posting_time="10:00:00",
+			source=warehouse,
+			qty=50,
+			basic_rate=700,
+		)
+
+		# Stock Reco for 100, Balace Qty 100
+		stock_reco = create_stock_reconciliation(
+			item_code=item_code,
+			posting_time="11:00:00",
+			warehouse=warehouse,
+			batch_no=se1.items[0].batch_no,
+			qty=100,
+			rate=100,
+		)
+
+		# Removed 50 Qty, Balace Qty 50
+		make_stock_entry(
+			item_code=item_code,
+			batch_no=se1.items[0].batch_no,
+			posting_time="12:00:00",
+			source=warehouse,
+			qty=50,
+			basic_rate=700,
+		)
+
+		self.assertFalse(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name}))
+
+		# Cancel the backdated Stock Entry se2,
+		# Since Stock Reco entry in the future the Balace Qty should remain as it's (50)
+
+		se2.cancel()
+
+		self.assertTrue(frappe.db.exists("Repost Item Valuation", {"voucher_no": stock_reco.name}))
+
+		self.assertEqual(
+			frappe.db.get_value("Repost Item Valuation", {"voucher_no": stock_reco.name}, "status"),
+			"Completed",
+		)
+
+		sle = frappe.get_all(
+			"Stock Ledger Entry",
+			filters={"item_code": item_code, "warehouse": warehouse, "is_cancelled": 0},
+			fields=["qty_after_transaction"],
+			order_by="posting_time desc, creation desc",
+		)
+
+		self.assertEqual(flt(sle[0].qty_after_transaction), flt(50.0))
+
 
 def create_batch_item_with_batch(item_name, batch_id):
 	batch_item_doc = create_item(item_name, is_stock_item=1)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index b53f429..ce85702 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -8,6 +8,7 @@
 from frappe import _, throw
 from frappe.model import child_table_fields, default_fields
 from frappe.model.meta import get_field_precision
+from frappe.query_builder.functions import CombineDatetime, IfNull, Sum
 from frappe.utils import add_days, add_months, cint, cstr, flt, getdate
 
 from erpnext import get_company_currency
@@ -73,6 +74,7 @@
 			args["bill_date"] = doc.get("bill_date")
 
 	out = get_basic_details(args, item, overwrite_warehouse)
+
 	get_item_tax_template(args, item, out)
 	out["item_tax_rate"] = get_item_tax_map(
 		args.company,
@@ -526,12 +528,8 @@
 
 	itemwise_barcode = {}
 	for item in items_list:
-		barcodes = frappe.db.sql(
-			"""
-			select barcode from `tabItem Barcode` where parent = %s
-		""",
-			item.item_code,
-			as_dict=1,
+		barcodes = frappe.db.get_all(
+			"Item Barcode", filters={"parent": item.item_code}, fields="barcode"
 		)
 
 		for barcode in barcodes:
@@ -623,7 +621,9 @@
 				taxes_with_no_validity.append(tax)
 
 	if taxes_with_validity:
-		taxes = sorted(taxes_with_validity, key=lambda i: i.valid_from, reverse=True)
+		taxes = sorted(
+			taxes_with_validity, key=lambda i: i.valid_from or tax.maximum_net_rate, reverse=True
+		)
 	else:
 		taxes = taxes_with_no_validity
 
@@ -891,34 +891,36 @@
 	:param item_code: str, Item Doctype field item_code
 	"""
 
-	args["item_code"] = item_code
-
-	conditions = """where item_code=%(item_code)s
-		and price_list=%(price_list)s
-		and ifnull(uom, '') in ('', %(uom)s)"""
-
-	conditions += "and ifnull(batch_no, '') in ('', %(batch_no)s)"
+	ip = frappe.qb.DocType("Item Price")
+	query = (
+		frappe.qb.from_(ip)
+		.select(ip.name, ip.price_list_rate, ip.uom)
+		.where(
+			(ip.item_code == item_code)
+			& (ip.price_list == args.get("price_list"))
+			& (IfNull(ip.uom, "").isin(["", args.get("uom")]))
+			& (IfNull(ip.batch_no, "").isin(["", args.get("batch_no")]))
+		)
+		.orderby(ip.valid_from, order=frappe.qb.desc)
+		.orderby(IfNull(ip.batch_no, ""), order=frappe.qb.desc)
+		.orderby(ip.uom, order=frappe.qb.desc)
+	)
 
 	if not ignore_party:
 		if args.get("customer"):
-			conditions += " and customer=%(customer)s"
+			query = query.where(ip.customer == args.get("customer"))
 		elif args.get("supplier"):
-			conditions += " and supplier=%(supplier)s"
+			query = query.where(ip.supplier == args.get("supplier"))
 		else:
-			conditions += "and (customer is null or customer = '') and (supplier is null or supplier = '')"
+			query = query.where((IfNull(ip.customer, "") == "") & (IfNull(ip.supplier, "") == ""))
 
 	if args.get("transaction_date"):
-		conditions += """ and %(transaction_date)s between
-			ifnull(valid_from, '2000-01-01') and ifnull(valid_upto, '2500-12-31')"""
+		query = query.where(
+			(IfNull(ip.valid_from, "2000-01-01") <= args["transaction_date"])
+			& (IfNull(ip.valid_upto, "2500-12-31") >= args["transaction_date"])
+		)
 
-	return frappe.db.sql(
-		""" select name, price_list_rate, uom
-		from `tabItem Price` {conditions}
-		order by valid_from desc, ifnull(batch_no, '') desc, uom desc """.format(
-			conditions=conditions
-		),
-		args,
-	)
+	return query.run()
 
 
 def get_price_list_rate_for(args, item_code):
@@ -1091,91 +1093,68 @@
 	if not user:
 		user = frappe.session["user"]
 
-	condition = "pfu.user = %(user)s AND pfu.default=1"
-	if user and company:
-		condition = "pfu.user = %(user)s AND pf.company = %(company)s AND pfu.default=1"
+	pf = frappe.qb.DocType("POS Profile")
+	pfu = frappe.qb.DocType("POS Profile User")
 
-	pos_profile = frappe.db.sql(
-		"""SELECT pf.*
-		FROM
-			`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
-		ON
-				pf.name = pfu.parent
-		WHERE
-			{cond} AND pf.disabled = 0
-	""".format(
-			cond=condition
-		),
-		{"user": user, "company": company},
-		as_dict=1,
+	query = (
+		frappe.qb.from_(pf)
+		.left_join(pfu)
+		.on(pf.name == pfu.parent)
+		.select(pf.star)
+		.where((pfu.user == user) & (pfu.default == 1))
 	)
 
+	if company:
+		query = query.where(pf.company == company)
+
+	pos_profile = query.run(as_dict=True)
+
 	if not pos_profile and company:
-		pos_profile = frappe.db.sql(
-			"""SELECT pf.*
-			FROM
-				`tabPOS Profile` pf LEFT JOIN `tabPOS Profile User` pfu
-			ON
-					pf.name = pfu.parent
-			WHERE
-				pf.company = %(company)s AND pf.disabled = 0
-		""",
-			{"company": company},
-			as_dict=1,
-		)
+		pos_profile = (
+			frappe.qb.from_(pf)
+			.left_join(pfu)
+			.on(pf.name == pfu.parent)
+			.select(pf.star)
+			.where((pf.company == company) & (pf.disabled == 0))
+		).run(as_dict=True)
 
 	return pos_profile and pos_profile[0] or None
 
 
 def get_serial_nos_by_fifo(args, sales_order=None):
 	if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
-		return "\n".join(
-			frappe.db.sql_list(
-				"""select name from `tabSerial No`
-			where item_code=%(item_code)s and warehouse=%(warehouse)s and
-			sales_order=IF(%(sales_order)s IS NULL, sales_order, %(sales_order)s)
-			order by timestamp(purchase_date, purchase_time)
-			asc limit %(qty)s""",
-				{
-					"item_code": args.item_code,
-					"warehouse": args.warehouse,
-					"qty": abs(cint(args.stock_qty)),
-					"sales_order": sales_order,
-				},
-			)
+		sn = frappe.qb.DocType("Serial No")
+		query = (
+			frappe.qb.from_(sn)
+			.select(sn.name)
+			.where((sn.item_code == args.item_code) & (sn.warehouse == args.warehouse))
+			.orderby(CombineDatetime(sn.purchase_date, sn.purchase_time))
+			.limit(abs(cint(args.stock_qty)))
 		)
 
+		if sales_order:
+			query = query.where(sn.sales_order == sales_order)
+		if args.batch_no:
+			query = query.where(sn.batch_no == args.batch_no)
 
-def get_serial_no_batchwise(args, sales_order=None):
-	if frappe.db.get_single_value("Stock Settings", "automatically_set_serial_nos_based_on_fifo"):
-		return "\n".join(
-			frappe.db.sql_list(
-				"""select name from `tabSerial No`
-			where item_code=%(item_code)s and warehouse=%(warehouse)s and
-			sales_order=IF(%(sales_order)s IS NULL, sales_order, %(sales_order)s)
-			and batch_no=IF(%(batch_no)s IS NULL, batch_no, %(batch_no)s) order
-			by timestamp(purchase_date, purchase_time) asc limit %(qty)s""",
-				{
-					"item_code": args.item_code,
-					"warehouse": args.warehouse,
-					"batch_no": args.batch_no,
-					"qty": abs(cint(args.stock_qty)),
-					"sales_order": sales_order,
-				},
-			)
-		)
+		serial_nos = query.run(as_list=True)
+		serial_nos = [s[0] for s in serial_nos]
+
+		return "\n".join(serial_nos)
 
 
 @frappe.whitelist()
 def get_conversion_factor(item_code, uom):
 	variant_of = frappe.db.get_value("Item", item_code, "variant_of", cache=True)
 	filters = {"parent": item_code, "uom": uom}
+
 	if variant_of:
 		filters["parent"] = ("in", (item_code, variant_of))
 	conversion_factor = frappe.db.get_value("UOM Conversion Detail", filters, "conversion_factor")
 	if not conversion_factor:
 		stock_uom = frappe.db.get_value("Item", item_code, "stock_uom")
 		conversion_factor = get_uom_conv_factor(uom, stock_uom)
+
 	return {"conversion_factor": conversion_factor or 1.0}
 
 
@@ -1217,12 +1196,16 @@
 
 
 def get_company_total_stock(item_code, company):
-	return frappe.db.sql(
-		"""SELECT sum(actual_qty) from
-		(`tabBin` INNER JOIN `tabWarehouse` ON `tabBin`.warehouse = `tabWarehouse`.name)
-		WHERE `tabWarehouse`.company = %s and `tabBin`.item_code = %s""",
-		(company, item_code),
-	)[0][0]
+	bin = frappe.qb.DocType("Bin")
+	wh = frappe.qb.DocType("Warehouse")
+
+	return (
+		frappe.qb.from_(bin)
+		.inner_join(wh)
+		.on(bin.warehouse == wh.name)
+		.select(Sum(bin.actual_qty))
+		.where((wh.company == company) & (bin.item_code == item_code))
+	).run()[0][0]
 
 
 @frappe.whitelist()
@@ -1231,6 +1214,7 @@
 		{"item_code": item_code, "warehouse": warehouse, "stock_qty": stock_qty, "serial_no": serial_no}
 	)
 	serial_no = get_serial_no(args)
+
 	return {"serial_no": serial_no}
 
 
@@ -1250,6 +1234,7 @@
 		bin_details_and_serial_nos.update(
 			get_serial_no_details(item_code, warehouse, stock_qty, serial_no)
 		)
+
 	return bin_details_and_serial_nos
 
 
@@ -1264,6 +1249,7 @@
 		)
 		serial_no = get_serial_no(args)
 		batch_qty_and_serial_no.update({"serial_no": serial_no})
+
 	return batch_qty_and_serial_no
 
 
@@ -1336,7 +1322,6 @@
 def apply_price_list_on_item(args):
 	item_doc = frappe.db.get_value("Item", args.item_code, ["name", "variant_of"], as_dict=1)
 	item_details = get_price_list_rate(args, item_doc)
-
 	item_details.update(get_pricing_rule_for_item(args))
 
 	return item_details
@@ -1420,12 +1405,12 @@
 		) or {"valuation_rate": 0}
 
 	elif not item.get("is_stock_item"):
-		valuation_rate = frappe.db.sql(
-			"""select sum(base_net_amount) / sum(qty*conversion_factor)
-			from `tabPurchase Invoice Item`
-			where item_code = %s and docstatus=1""",
-			item_code,
-		)
+		pi_item = frappe.qb.DocType("Purchase Invoice Item")
+		valuation_rate = (
+			frappe.qb.from_(pi_item)
+			.select((Sum(pi_item.base_net_amount) / Sum(pi_item.qty * pi_item.conversion_factor)))
+			.where((pi_item.docstatus == 1) & (pi_item.item_code == item_code))
+		).run()
 
 		if valuation_rate:
 			return {"valuation_rate": valuation_rate[0][0] or 0.0}
@@ -1451,7 +1436,7 @@
 	if args.get("warehouse") and args.get("stock_qty") and args.get("item_code"):
 		has_serial_no = frappe.get_value("Item", {"item_code": args.item_code}, "has_serial_no")
 		if args.get("batch_no") and has_serial_no == 1:
-			return get_serial_no_batchwise(args, sales_order)
+			return get_serial_nos_by_fifo(args, sales_order)
 		elif has_serial_no == 1:
 			args = json.dumps(
 				{
@@ -1483,31 +1468,35 @@
 		args = frappe._dict(json.loads(args))
 
 	blanket_order_details = None
-	condition = ""
-	if args.item_code:
-		if args.customer and args.doctype == "Sales Order":
-			condition = " and bo.customer=%(customer)s"
-		elif args.supplier and args.doctype == "Purchase Order":
-			condition = " and bo.supplier=%(supplier)s"
-		if args.blanket_order:
-			condition += " and bo.name =%(blanket_order)s"
-		if args.transaction_date:
-			condition += " and bo.to_date>=%(transaction_date)s"
 
-		blanket_order_details = frappe.db.sql(
-			"""
-				select boi.rate as blanket_order_rate, bo.name as blanket_order
-				from `tabBlanket Order` bo, `tabBlanket Order Item` boi
-				where bo.company=%(company)s and boi.item_code=%(item_code)s
-					and bo.docstatus=1 and bo.name = boi.parent {0}
-			""".format(
-				condition
-			),
-			args,
-			as_dict=True,
+	if args.item_code:
+		bo = frappe.qb.DocType("Blanket Order")
+		bo_item = frappe.qb.DocType("Blanket Order Item")
+
+		query = (
+			frappe.qb.from_(bo)
+			.from_(bo_item)
+			.select(bo_item.rate.as_("blanket_order_rate"), bo.name.as_("blanket_order"))
+			.where(
+				(bo.company == args.company)
+				& (bo_item.item_code == args.item_code)
+				& (bo.docstatus == 1)
+				& (bo.name == bo_item.parent)
+			)
 		)
 
+		if args.customer and args.doctype == "Sales Order":
+			query = query.where(bo.customer == args.customer)
+		elif args.supplier and args.doctype == "Purchase Order":
+			query = query.where(bo.supplier == args.supplier)
+		if args.blanket_order:
+			query = query.where(bo.name == args.blanket_order)
+		if args.transaction_date:
+			query = query.where(bo.to_date >= args.transaction_date)
+
+		blanket_order_details = query.run(as_dict=True)
 		blanket_order_details = blanket_order_details[0] if blanket_order_details else ""
+
 	return blanket_order_details
 
 
@@ -1517,10 +1506,10 @@
 		if get_reserved_qty_for_so(args.get("against_sales_order"), args.get("item_code")):
 			reserved_so = args.get("against_sales_order")
 	elif args.get("against_sales_invoice"):
-		sales_order = frappe.db.sql(
-			"""select sales_order from `tabSales Invoice Item` where
-		parent=%s and item_code=%s""",
-			(args.get("against_sales_invoice"), args.get("item_code")),
+		sales_order = frappe.db.get_all(
+			"Sales Invoice Item",
+			filters={"parent": args.get("against_sales_invoice"), "item_code": args.get("item_code")},
+			fields="sales_order",
 		)
 		if sales_order and sales_order[0]:
 			if get_reserved_qty_for_so(sales_order[0][0], args.get("item_code")):
@@ -1532,13 +1521,14 @@
 
 
 def get_reserved_qty_for_so(sales_order, item_code):
-	reserved_qty = frappe.db.sql(
-		"""select sum(qty) from `tabSales Order Item`
-	where parent=%s and item_code=%s and ensure_delivery_based_on_produced_serial_no=1
-	""",
-		(sales_order, item_code),
+	reserved_qty = frappe.db.get_value(
+		"Sales Order Item",
+		filters={
+			"parent": sales_order,
+			"item_code": item_code,
+			"ensure_delivery_based_on_produced_serial_no": 1,
+		},
+		fieldname="sum(qty)",
 	)
-	if reserved_qty and reserved_qty[0][0]:
-		return reserved_qty[0][0]
-	else:
-		return 0
+
+	return reserved_qty or 0
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 0fc642e..66991a9 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -7,7 +7,7 @@
 
 import frappe
 from frappe import _
-from frappe.query_builder.functions import CombineDatetime
+from frappe.query_builder.functions import Coalesce, CombineDatetime
 from frappe.utils import cint, date_diff, flt, getdate
 from frappe.utils.nestedset import get_descendants_of
 
@@ -322,6 +322,34 @@
 	return query.run(as_dict=True)
 
 
+def get_opening_vouchers(to_date):
+	opening_vouchers = {"Stock Entry": [], "Stock Reconciliation": []}
+
+	se = frappe.qb.DocType("Stock Entry")
+	sr = frappe.qb.DocType("Stock Reconciliation")
+
+	vouchers_data = (
+		frappe.qb.from_(
+			(
+				frappe.qb.from_(se)
+				.select(se.name, Coalesce("Stock Entry").as_("voucher_type"))
+				.where((se.docstatus == 1) & (se.posting_date <= to_date) & (se.is_opening == "Yes"))
+			)
+			+ (
+				frappe.qb.from_(sr)
+				.select(sr.name, Coalesce("Stock Reconciliation").as_("voucher_type"))
+				.where((sr.docstatus == 1) & (sr.posting_date <= to_date) & (sr.purpose == "Opening Stock"))
+			)
+		).select("voucher_type", "name")
+	).run(as_dict=True)
+
+	if vouchers_data:
+		for d in vouchers_data:
+			opening_vouchers[d.voucher_type].append(d.name)
+
+	return opening_vouchers
+
+
 def get_inventory_dimension_fields():
 	return [dimension.fieldname for dimension in get_inventory_dimensions()]
 
@@ -330,9 +358,8 @@
 	iwb_map = {}
 	from_date = getdate(filters.get("from_date"))
 	to_date = getdate(filters.get("to_date"))
-
+	opening_vouchers = get_opening_vouchers(to_date)
 	float_precision = cint(frappe.db.get_default("float_precision")) or 3
-
 	inventory_dimensions = get_inventory_dimension_fields()
 
 	for d in sle:
@@ -363,11 +390,7 @@
 
 		value_diff = flt(d.stock_value_difference)
 
-		if d.posting_date < from_date or (
-			d.posting_date == from_date
-			and d.voucher_type == "Stock Reconciliation"
-			and frappe.db.get_value("Stock Reconciliation", d.voucher_no, "purpose") == "Opening Stock"
-		):
+		if d.posting_date < from_date or d.voucher_no in opening_vouchers.get(d.voucher_type, []):
 			qty_dict.opening_qty += qty_diff
 			qty_dict.opening_val += value_diff
 
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index da17cde..77bc4e0 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -34,6 +34,9 @@
 		conversion_factors.append(0)
 
 	actual_qty = stock_value = 0
+	if opening_row:
+		actual_qty = opening_row.get("qty_after_transaction")
+		stock_value = opening_row.get("stock_value")
 
 	available_serial_nos = {}
 	inventory_dimension_filters_applied = check_inventory_dimension_filters_applied(filters)
diff --git a/erpnext/stock/stock_balance.py b/erpnext/stock/stock_balance.py
index 439ed7a..e3cbb43 100644
--- a/erpnext/stock/stock_balance.py
+++ b/erpnext/stock/stock_balance.py
@@ -94,10 +94,13 @@
 
 
 def get_reserved_qty(item_code, warehouse):
+	dont_reserve_on_return = frappe.get_cached_value(
+		"Selling Settings", "Selling Settings", "dont_reserve_sales_order_qty_on_sales_return"
+	)
 	reserved_qty = frappe.db.sql(
-		"""
+		f"""
 		select
-			sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty) / so_item_qty))
+			sum(dnpi_qty * ((so_item_qty - so_item_delivered_qty - if(dont_reserve_qty_on_return, so_item_returned_qty, 0)) / so_item_qty))
 		from
 			(
 				(select
@@ -112,6 +115,12 @@
 						where name = dnpi.parent_detail_docname
 						and delivered_by_supplier = 0
 					) as so_item_delivered_qty,
+					(
+						select returned_qty from `tabSales Order Item`
+						where name = dnpi.parent_detail_docname
+						and delivered_by_supplier = 0
+					) as so_item_returned_qty,
+					{dont_reserve_on_return} as dont_reserve_qty_on_return,
 					parent, name
 				from
 				(
@@ -125,7 +134,9 @@
 				) dnpi)
 			union
 				(select stock_qty as dnpi_qty, qty as so_item_qty,
-					delivered_qty as so_item_delivered_qty, parent, name
+					delivered_qty as so_item_delivered_qty,
+					returned_qty as so_item_returned_qty,
+					{dont_reserve_on_return}, parent, name
 				from `tabSales Order Item` so_item
 				where item_code = %s and warehouse = %s
 				and (so_item.delivered_by_supplier is null or so_item.delivered_by_supplier = 0)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 08fc6fb..b0a093d 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1337,6 +1337,9 @@
 	next_stock_reco_detail = get_next_stock_reco(args)
 	if next_stock_reco_detail:
 		detail = next_stock_reco_detail[0]
+		if detail.batch_no:
+			regenerate_sle_for_batch_stock_reco(detail)
+
 		# add condition to update SLEs before this date & time
 		datetime_limit_condition = get_datetime_limit_condition(detail)
 
@@ -1364,6 +1367,17 @@
 	validate_negative_qty_in_future_sle(args, allow_negative_stock)
 
 
+def regenerate_sle_for_batch_stock_reco(detail):
+	doc = frappe.get_cached_doc("Stock Reconciliation", detail.voucher_no)
+	doc.docstatus = 2
+	doc.update_stock_ledger()
+
+	doc.recalculate_current_qty(detail.item_code, detail.batch_no)
+	doc.docstatus = 1
+	doc.update_stock_ledger()
+	doc.repost_future_sle_and_gle()
+
+
 def get_stock_reco_qty_shift(args):
 	stock_reco_qty_shift = 0
 	if args.get("is_cancelled"):
@@ -1393,7 +1407,7 @@
 	return frappe.db.sql(
 		"""
 		select
-			name, posting_date, posting_time, creation, voucher_no
+			name, posting_date, posting_time, creation, voucher_no, item_code, batch_no, actual_qty
 		from
 			`tabStock Ledger Entry`
 		where
diff --git a/erpnext/stock/tests/test_valuation.py b/erpnext/stock/tests/test_valuation.py
index e60c1ca..05f153b 100644
--- a/erpnext/stock/tests/test_valuation.py
+++ b/erpnext/stock/tests/test_valuation.py
@@ -132,7 +132,7 @@
 		total_qty = 0
 
 		for qty, rate in stock_queue:
-			if qty == 0:
+			if round_off_if_near_zero(qty) == 0:
 				continue
 			if qty > 0:
 				self.queue.add_stock(qty, rate)
@@ -154,7 +154,7 @@
 
 		for qty, rate in stock_queue:
 			# don't allow negative stock
-			if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+			if round_off_if_near_zero(qty) == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
 				continue
 			if qty > 0:
 				self.queue.add_stock(qty, rate)
@@ -179,7 +179,7 @@
 
 		for qty, rate in stock_queue:
 			# don't allow negative stock
-			if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+			if round_off_if_near_zero(qty) == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
 				continue
 			if qty > 0:
 				self.queue.add_stock(qty, rate)
@@ -282,7 +282,7 @@
 		total_qty = 0
 
 		for qty, rate in stock_stack:
-			if qty == 0:
+			if round_off_if_near_zero(qty) == 0:
 				continue
 			if qty > 0:
 				self.stack.add_stock(qty, rate)
@@ -304,7 +304,7 @@
 
 		for qty, rate in stock_stack:
 			# don't allow negative stock
-			if qty == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
+			if round_off_if_near_zero(qty) == 0 or total_qty + qty < 0 or abs(qty) < 0.1:
 				continue
 			if qty > 0:
 				self.stack.add_stock(qty, rate)
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index f4fd4de..4f8e045 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -191,14 +191,17 @@
 
 	def validate_available_qty_for_consumption(self):
 		for item in self.get("supplied_items"):
+			precision = item.precision("consumed_qty")
 			if (
-				item.available_qty_for_consumption and item.available_qty_for_consumption < item.consumed_qty
+				item.available_qty_for_consumption
+				and flt(item.available_qty_for_consumption, precision) - flt(item.consumed_qty, precision) < 0
 			):
-				frappe.throw(
-					_(
-						"Row {0}: Consumed Qty must be less than or equal to Available Qty For Consumption in Consumed Items Table."
-					).format(item.idx)
-				)
+				msg = f"""Row {item.idx}: Consumed Qty {flt(item.consumed_qty, precision)}
+					must be less than or equal to Available Qty For Consumption
+					{flt(item.available_qty_for_consumption, precision)}
+					in Consumed Items Table."""
+
+				frappe.throw(_(msg))
 
 	def validate_items_qty(self):
 		for item in self.items:
@@ -242,17 +245,17 @@
 					item.expense_account = expense_account
 
 	def update_status(self, status=None, update_modified=False):
-		if self.docstatus >= 1 and not status:
-			if self.docstatus == 1:
+		if not status:
+			if self.docstatus == 0:
+				status = "Draft"
+			elif self.docstatus == 1:
+				status = "Completed"
 				if self.is_return:
 					status = "Return"
 					return_against = frappe.get_doc("Subcontracting Receipt", self.return_against)
 					return_against.run_method("update_status")
-				else:
-					if self.per_returned == 100:
-						status = "Return Issued"
-					elif self.status == "Draft":
-						status = "Completed"
+				elif self.per_returned == 100:
+					status = "Return Issued"
 			elif self.docstatus == 2:
 				status = "Cancelled"
 
diff --git a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
index 7f4e9ef..2a078c4 100644
--- a/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
+++ b/erpnext/support/doctype/service_level_agreement/service_level_agreement.py
@@ -13,8 +13,8 @@
 	get_datetime,
 	get_datetime_str,
 	get_link_to_form,
+	get_system_timezone,
 	get_time,
-	get_time_zone,
 	get_weekdays,
 	getdate,
 	nowdate,
@@ -981,7 +981,7 @@
 
 
 def get_tz(user):
-	return frappe.db.get_value("User", user, "time_zone") or get_time_zone()
+	return frappe.db.get_value("User", user, "time_zone") or get_system_timezone()
 
 
 @frappe.whitelist()
diff --git a/erpnext/templates/generators/item/item_configure.js b/erpnext/templates/generators/item/item_configure.js
index 231ae05..613c967 100644
--- a/erpnext/templates/generators/item/item_configure.js
+++ b/erpnext/templates/generators/item/item_configure.js
@@ -186,14 +186,14 @@
 		this.dialog.$status_area.empty();
 	}
 
-	get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info }) {
+	get_html_for_item_found({ filtered_items_count, filtered_items, exact_match, product_info, available_qty, settings }) {
 		const one_item = exact_match.length === 1
 			? exact_match[0]
 			: filtered_items_count === 1
 				? filtered_items[0]
 				: '';
 
-		const item_add_to_cart = one_item ? `
+		let item_add_to_cart = one_item ? `
 			<button data-item-code="${one_item}"
 				class="btn btn-primary btn-add-to-cart w-100"
 				data-action="btn_add_to_cart"
@@ -218,6 +218,9 @@
 						? '(' + product_info.price.formatted_price_sales_uom + ')'
 						: ''
 					}
+
+					${available_qty === 0 ? '<span class="text-danger">(' + __('Out of Stock') + ')</span>' : ''}
+
 				</div></div>
 				<a href data-action="btn_clear_values" data-item-code="${one_item}">
 					${__('Clear Values')}
@@ -233,6 +236,10 @@
 			</div>`;
 		/* eslint-disable indent */
 
+		if (!product_info?.allow_items_not_in_stock && available_qty === 0) {
+			item_add_to_cart = '';
+		}
+
 		return `
 			${item_found_status}
 			${item_add_to_cart}
@@ -257,12 +264,15 @@
 
 	btn_clear_values() {
 		this.dialog.fields_list.forEach(f => {
-			f.df.options = f.df.options.map(option => {
-				option.disabled = false;
-				return option;
-			});
+			if (f.df?.options) {
+				f.df.options = f.df.options.map(option => {
+					option.disabled = false;
+					return option;
+				});
+			}
 		});
 		this.dialog.clear();
+		this.dialog.$status_area.empty();
 		this.on_attribute_selection();
 	}
 
diff --git a/erpnext/templates/utils.py b/erpnext/templates/utils.py
index 48b4480..57750a5 100644
--- a/erpnext/templates/utils.py
+++ b/erpnext/templates/utils.py
@@ -6,13 +6,12 @@
 
 
 @frappe.whitelist(allow_guest=True)
-def send_message(subject="Website Query", message="", sender="", status="Open"):
+def send_message(sender, message, subject="Website Query"):
 	from frappe.www.contact import send_message as website_send_message
 
+	website_send_message(sender, message, subject)
+
 	lead = customer = None
-
-	website_send_message(subject, message, sender)
-
 	customer = frappe.db.sql(
 		"""select distinct dl.link_name from `tabDynamic Link` dl
 		left join `tabContact` c on dl.parent=c.name where dl.link_doctype='Customer'
@@ -58,5 +57,3 @@
 		}
 	)
 	comm.insert(ignore_permissions=True)
-
-	return "okay"
diff --git a/erpnext/translations/af.csv b/erpnext/translations/af.csv
index 265e85c..f2458e3 100644
--- a/erpnext/translations/af.csv
+++ b/erpnext/translations/af.csv
@@ -3505,7 +3505,6 @@
 Reviews,resensies,
 Sender,sender,
 Shop,Winkel,
-Sign Up,Teken aan,
 Subsidiary,filiaal,
 There is some problem with the file url: {0},Daar is &#39;n probleem met die lêer url: {0},
 There were errors while sending email. Please try again.,Daar was foute tydens die stuur van e-pos. Probeer asseblief weer.,
diff --git a/erpnext/translations/am.csv b/erpnext/translations/am.csv
index d131404..d4db285 100644
--- a/erpnext/translations/am.csv
+++ b/erpnext/translations/am.csv
@@ -3505,7 +3505,6 @@
 Reviews,ግምገማዎች,
 Sender,የላኪ,
 Shop,ሱቅ,
-Sign Up,ክፈት,
 Subsidiary,ተጪማሪ,
 There is some problem with the file url: {0},ፋይል ዩ አር ኤል ጋር አንድ ችግር አለ: {0},
 There were errors while sending email. Please try again.,ኢሜይል በመላክ ላይ ሳለ ስህተቶች ነበሩ. እባክዎ ዳግም ይሞክሩ.,
diff --git a/erpnext/translations/ar.csv b/erpnext/translations/ar.csv
index c0da1c4..ea2777f 100644
--- a/erpnext/translations/ar.csv
+++ b/erpnext/translations/ar.csv
@@ -3505,7 +3505,6 @@
 Reviews,التعليقات,
 Sender,مرسل,
 Shop,تسوق,
-Sign Up,سجل,
 Subsidiary,شركة فرعية,
 There is some problem with the file url: {0},هناك بعض المشاكل مع رابط الملف: {0},
 There were errors while sending email. Please try again.,كانت هناك أخطاء أثناء إرسال البريد الإلكتروني. يرجى المحاولة مرة أخرى.,
diff --git a/erpnext/translations/bg.csv b/erpnext/translations/bg.csv
index ac6dc78..6839129 100644
--- a/erpnext/translations/bg.csv
+++ b/erpnext/translations/bg.csv
@@ -3505,7 +3505,6 @@
 Reviews,Отзиви,
 Sender,Подател,
 Shop,Магазин,
-Sign Up,Регистрирай се,
 Subsidiary,Филиал,
 There is some problem with the file url: {0},Има някакъв проблем с адреса на файл: {0},
 There were errors while sending email. Please try again.,"Имаше грешки при изпращане на имейл. Моля, опитайте отново.",
diff --git a/erpnext/translations/bn.csv b/erpnext/translations/bn.csv
index 52f7b1c..a944d99 100644
--- a/erpnext/translations/bn.csv
+++ b/erpnext/translations/bn.csv
@@ -3505,7 +3505,6 @@
 Reviews,পর্যালোচনা,
 Sender,প্রেরকের,
 Shop,দোকান,
-Sign Up,নিবন্ধন করুন,
 Subsidiary,সহায়ক,
 There is some problem with the file url: {0},ফাইলের URL সঙ্গে কিছু সমস্যা আছে: {0},
 There were errors while sending email. Please try again.,ইমেইল পাঠানোর সময় কিছু সমস্যা হয়েছে. অনুগ্রহ করে আবার চেষ্টা করুন.,
diff --git a/erpnext/translations/bs.csv b/erpnext/translations/bs.csv
index 267434f..2d9c26d 100644
--- a/erpnext/translations/bs.csv
+++ b/erpnext/translations/bs.csv
@@ -3505,7 +3505,6 @@
 Reviews,Recenzije,
 Sender,Pošiljaoc,
 Shop,Prodavnica,
-Sign Up,Prijaviti se,
 Subsidiary,Podružnica,
 There is some problem with the file url: {0},Postoji neki problem sa URL datoteku: {0},
 There were errors while sending email. Please try again.,Bilo je grešaka tijekom slanja e-pošte. Molimo pokušajte ponovno .,
diff --git a/erpnext/translations/ca.csv b/erpnext/translations/ca.csv
index d8c2ef6..85c6285 100644
--- a/erpnext/translations/ca.csv
+++ b/erpnext/translations/ca.csv
@@ -3505,7 +3505,6 @@
 Reviews,Ressenyes,
 Sender,Remitent,
 Shop,Botiga,
-Sign Up,Registra&#39;t,
 Subsidiary,Filial,
 There is some problem with the file url: {0},Hi ha una mica de problema amb la url de l&#39;arxiu: {0},
 There were errors while sending email. Please try again.,"Hi ha hagut errors a l'enviar el correu electrònic. Si us plau, torna a intentar-ho.",
diff --git a/erpnext/translations/cs.csv b/erpnext/translations/cs.csv
index 7d570bb..3fb67e7 100644
--- a/erpnext/translations/cs.csv
+++ b/erpnext/translations/cs.csv
@@ -3505,7 +3505,6 @@
 Reviews,Recenze,
 Sender,Odesilatel,
 Shop,Obchod,
-Sign Up,Přihlásit se,
 Subsidiary,Dceřiný,
 There is some problem with the file url: {0},Tam je nějaký problém s URL souboru: {0},
 There were errors while sending email. Please try again.,Narazili jsme na problémy při odesílání emailu. Prosím zkuste to znovu.,
diff --git a/erpnext/translations/da.csv b/erpnext/translations/da.csv
index 16b2e87..f0654b9 100644
--- a/erpnext/translations/da.csv
+++ b/erpnext/translations/da.csv
@@ -3505,7 +3505,6 @@
 Reviews,Anmeldelser,
 Sender,Afsender,
 Shop,Butik,
-Sign Up,Tilmelde,
 Subsidiary,Datterselskab,
 There is some problem with the file url: {0},Der er nogle problemer med filen url: {0},
 There were errors while sending email. Please try again.,Der var fejl under afsendelse af e-mail. Prøv venligst igen.,
diff --git a/erpnext/translations/de.csv b/erpnext/translations/de.csv
index 5a0a863..57556f3 100644
--- a/erpnext/translations/de.csv
+++ b/erpnext/translations/de.csv
@@ -1875,6 +1875,7 @@
 Part-time,Teilzeit,
 Partially Depreciated,Teilweise abgeschrieben,
 Partially Received,Teilweise erhalten,
+Partly Paid,Teilweise bezahlt,
 Party,Partei,
 Party Name,Name der Partei,
 Party Type,Partei-Typ,
@@ -2012,30 +2013,27 @@
 Please login as another user to register on Marketplace,"Bitte melden Sie sich als anderer Benutzer an, um sich auf dem Marktplatz zu registrieren",
 Please make sure you really want to delete all the transactions for this company. Your master data will remain as it is. This action cannot be undone.,"Bitte sicher stellen, dass wirklich alle Transaktionen dieses Unternehmens gelöscht werden sollen. Die Stammdaten bleiben bestehen. Diese Aktion kann nicht rückgängig gemacht werden.",
 Please mention Basic and HRA component in Company,Bitte erwähnen Sie die Basis- und HRA-Komponente in der Firma,
-Please mention Round Off Account in Company,Bitte Abschlusskonto in Unternehmen vermerken,
-Please mention Round Off Cost Center in Company,Bitte Abschlusskostenstelle in Unternehmen vermerken,
-Please mention no of visits required,"Bitte bei ""Besuche erforderlich"" NEIN angeben",
-Please mention the Lead Name in Lead {0},Bitte erwähnen Sie den Lead Name in Lead {0},
-Please pull items from Delivery Note,Bitte Artikel vom Lieferschein nehmen,
+Please mention Round Off Account in Company,Bitte ein Standardkonto Konto für Rundungsdifferenzen in Unternehmen einstellen,
+Please mention Round Off Cost Center in Company,Bitte eine Kostenstelle für Rundungsdifferenzen in Unternehmen einstellen,
+Please mention no of visits required,Bitte die Anzahl der benötigten Wartungsbesuche angeben,
+Please pull items from Delivery Note,Bitte Artikel aus dem Lieferschein ziehen,
 Please register the SIREN number in the company information file,Bitte registrieren Sie die SIREN-Nummer in der Unternehmensinformationsdatei,
 Please remove this Invoice {0} from C-Form {1},Bitte diese Rechnung {0} vom Kontaktformular {1} entfernen,
 Please save the patient first,Bitte speichern Sie den Patienten zuerst,
 Please save the report again to rebuild or update,"Speichern Sie den Bericht erneut, um ihn neu zu erstellen oder zu aktualisieren",
 "Please select Allocated Amount, Invoice Type and Invoice Number in atleast one row","Bitte zugewiesenen Betrag, Rechnungsart und Rechnungsnummer in mindestens einer Zeile auswählen",
 Please select Apply Discount On,"Bitte ""Rabatt anwenden auf"" auswählen",
-Please select BOM against item {0},Bitte wählen Sie Stückliste gegen Artikel {0},
-Please select BOM for Item in Row {0},Bitte Stückliste für Artikel in Zeile {0} auswählen,
-Please select BOM in BOM field for Item {0},Bitte aus dem Stücklistenfeld eine Stückliste für Artikel {0} auswählen,
-Please select Category first,Bitte zuerst Kategorie auswählen,
-Please select Charge Type first,Bitte zuerst Chargentyp auswählen,
-Please select Company,Bitte Unternehmen auswählen,
+Please select BOM against item {0},Bitte eine Stückliste für Artikel {0} auswählen,
+Please select BOM for Item in Row {0},Bitte eine Stückliste für den Artikel in Zeile {0} auswählen,
+Please select BOM in BOM field for Item {0},Bitte im Stücklistenfeld eine Stückliste für Artikel {0} auswählen,
+Please select Category first,Bitte zuerst eine Kategorie auswählen,
+Please select Charge Type first,Bitte zuerst einen Chargentyp auswählen,
+Please select Company,Bitte ein Unternehmen auswählen,
 Please select Company and Designation,Bitte wählen Sie Unternehmen und Position,
 Please select Company and Posting Date to getting entries,"Bitte wählen Sie Unternehmen und Buchungsdatum, um Einträge zu erhalten",
 Please select Company first,Bitte zuerst Unternehmen auswählen,
 Please select Completion Date for Completed Asset Maintenance Log,Bitte wählen Sie Fertigstellungsdatum für das abgeschlossene Wartungsprotokoll für den Vermögenswert,
 Please select Completion Date for Completed Repair,Bitte wählen Sie das Abschlussdatum für die abgeschlossene Reparatur,
-Please select Course,Bitte wählen Sie Kurs,
-Please select Drug,Bitte wählen Sie Arzneimittel,
 Please select Employee,Bitte wählen Sie Mitarbeiter,
 Please select Existing Company for creating Chart of Accounts,Bitte wählen Sie Bestehende Unternehmen für die Erstellung von Konten,
 Please select Healthcare Service,Bitte wählen Sie Gesundheitsdienst,
@@ -3514,7 +3512,6 @@
 Reviews,Bewertungen,
 Sender,Absender,
 Shop,Laden,
-Sign Up,Anmelden,
 Subsidiary,Tochtergesellschaft,
 There is some problem with the file url: {0},Es gibt irgend ein Problem mit der Datei-URL: {0},
 There were errors while sending email. Please try again.,Beim Versand der E-Mail ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.,
@@ -5067,7 +5064,7 @@
 Rate ,Preis,
 Rate (Company Currency),Preis (Unternehmenswährung),
 Amount (Company Currency),Betrag (Unternehmenswährung),
-Is Free Item,Ist freies Einzelteil,
+Is Free Item,Ist kostenlos,
 Net Rate,Nettopreis,
 Net Rate (Company Currency),Nettopreis (Unternehmenswährung),
 Net Amount (Company Currency),Nettobetrag (Unternehmenswährung),
@@ -7805,7 +7802,7 @@
 Default Cost of Goods Sold Account,Standard-Herstellkosten,
 Default Income Account,Standard-Ertragskonto,
 Default Deferred Revenue Account,Standardkonto für passive Rechnungsabgrenzung,
-Default Deferred Expense Account,Standard-Rechnungsabgrenzungsposten,
+Default Deferred Expense Account,Standardkonto für aktive Rechnungsabgrenzung,
 Default Payroll Payable Account,Standardkonto für Verbindlichkeiten aus Lohn und Gehalt,
 Default Expense Claim Payable Account,Standard-Expense Claim Zahlbares Konto,
 Stock Settings,Lager-Einstellungen,
@@ -8873,7 +8870,7 @@
 This topic is already added to the existing courses,Dieses Thema wurde bereits zu den bestehenden Kursen hinzugefügt,
 "If Shopify does not have a customer in the order, then while syncing the orders, the system will consider the default customer for the order","Wenn Shopify keinen Kunden in der Bestellung hat, berücksichtigt das System beim Synchronisieren der Bestellungen den Standardkunden für die Bestellung",
 The accounts are set by the system automatically but do confirm these defaults,"Die Konten werden vom System automatisch festgelegt, bestätigen jedoch diese Standardeinstellungen",
-Default Round Off Account,Standard-Rundungskonto,
+Default Round Off Account,Standardkonto für Rundungsdifferenzen,
 Failed Import Log,Importprotokoll fehlgeschlagen,
 Fixed Error Log,Fehlerprotokoll behoben,
 Company {0} already exists. Continuing will overwrite the Company and Chart of Accounts,Firma {0} existiert bereits. Durch Fortfahren werden das Unternehmen und der Kontenplan überschrieben,
@@ -9916,3 +9913,9 @@
 Delivered at Place,Geliefert benannter Ort,
 Delivered at Place Unloaded,Geliefert benannter Ort entladen,
 Delivered Duty Paid,Geliefert verzollt,
+Discount Validity,Frist für den Rabatt,
+Discount Validity Based On,Frist für den Rabatt berechnet sich nach,
+Select Alternative Items for Sales Order,Alternativpositionen für Auftragsbestätigung auswählen,
+Select an item from each set to be used in the Sales Order.,"Wählen Sie aus den Alternativen jeweils einen Artikel aus, der in die Auftragsbestätigung übernommen werden soll.",
+Is Alternative,Ist Alternative,
+Alternative Items,Alternativpositionen,
diff --git a/erpnext/translations/el.csv b/erpnext/translations/el.csv
index 06b8060..c241558 100644
--- a/erpnext/translations/el.csv
+++ b/erpnext/translations/el.csv
@@ -3505,7 +3505,6 @@
 Reviews,Κριτικές,
 Sender,Αποστολέας,
 Shop,Κατάστημα,
-Sign Up,Εγγραφείτε,
 Subsidiary,Θυγατρική,
 There is some problem with the file url: {0},Υπάρχει κάποιο πρόβλημα με το url αρχείο: {0},
 There were errors while sending email. Please try again.,Υπήρξαν σφάλματα κατά την αποστολή ηλεκτρονικού ταχυδρομείου. Παρακαλώ δοκιμάστε ξανά .,
diff --git a/erpnext/translations/es.csv b/erpnext/translations/es.csv
index b216b86..9996fe5 100644
--- a/erpnext/translations/es.csv
+++ b/erpnext/translations/es.csv
@@ -3505,7 +3505,6 @@
 Reviews,Comentarios,
 Sender,Remitente,
 Shop,Tienda.,
-Sign Up,Regístrate,
 Subsidiary,Subsidiaria,
 There is some problem with the file url: {0},Hay un poco de problema con la url del archivo: {0},
 There were errors while sending email. Please try again.,"Ha ocurrido un error al enviar el correo electrónico. Por favor, inténtelo de nuevo.",
diff --git a/erpnext/translations/et.csv b/erpnext/translations/et.csv
index 5d67d81..6e60809 100644
--- a/erpnext/translations/et.csv
+++ b/erpnext/translations/et.csv
@@ -3505,7 +3505,6 @@
 Reviews,Ülevaated,
 Sender,Lähetaja,
 Shop,Kauplus,
-Sign Up,Registreeri,
 Subsidiary,Tütarettevõte,
 There is some problem with the file url: {0},Seal on mõned probleem faili url: {0},
 There were errors while sending email. Please try again.,Vigu samas saates email. Palun proovi uuesti.,
diff --git a/erpnext/translations/fa.csv b/erpnext/translations/fa.csv
index 040034d..7d18e27 100644
--- a/erpnext/translations/fa.csv
+++ b/erpnext/translations/fa.csv
@@ -3505,7 +3505,6 @@
 Reviews,بررسی ها,
 Sender,فرستنده,
 Shop,فروشگاه,
-Sign Up,ثبت نام,
 Subsidiary,فرعی,
 There is some problem with the file url: {0},بعضی از مشکل با آدرس فایل وجود دارد: {0},
 There were errors while sending email. Please try again.,بودند خطاهای هنگام ارسال ایمیل وجود دارد. لطفا دوباره تلاش کنید.,
diff --git a/erpnext/translations/fi.csv b/erpnext/translations/fi.csv
index 27ea3b8..c700f60 100644
--- a/erpnext/translations/fi.csv
+++ b/erpnext/translations/fi.csv
@@ -3505,7 +3505,6 @@
 Reviews,Arvostelut,
 Sender,Lähettäjä,
 Shop,Osta,
-Sign Up,Kirjaudu,
 Subsidiary,tytäryhtiö,
 There is some problem with the file url: {0},Tiedosto-URL:issa  {0} on ongelma,
 There were errors while sending email. Please try again.,"Lähetettäessä sähköpostia oli virheitä, yrita uudelleen",
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 8367afd..ab9bf7d 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -2801,7 +2801,7 @@
 Stock Levels,Niveaux du Stocks,
 Stock Liabilities,Passif du Stock,
 Stock Options,Options du Stock,
-Stock Qty,Qté en Stock,
+Stock Qty,Qté en unité de stock,
 Stock Received But Not Billed,Stock Reçus Mais Non Facturés,
 Stock Reports,Rapports de stock,
 Stock Summary,Résumé du Stock,
@@ -3505,7 +3505,6 @@
 Reviews,Avis,
 Sender,Expéditeur,
 Shop,Magasin,
-Sign Up,S'inscrire,
 Subsidiary,Filiale,
 There is some problem with the file url: {0},Il y a un problème avec l'url du fichier : {0},
 There were errors while sending email. Please try again.,Il y a eu des erreurs lors de l'envoi d’emails. Veuillez essayer à nouveau.,
diff --git a/erpnext/translations/gu.csv b/erpnext/translations/gu.csv
index 97adac9..b26d2f3 100644
--- a/erpnext/translations/gu.csv
+++ b/erpnext/translations/gu.csv
@@ -3505,7 +3505,6 @@
 Reviews,સમીક્ષાઓ,
 Sender,પ્રેષક,
 Shop,દુકાન,
-Sign Up,સાઇન અપ કરો,
 Subsidiary,સબસિડીયરી,
 There is some problem with the file url: {0},ફાઈલ URL સાથે કેટલાક સમસ્યા છે: {0},
 There were errors while sending email. Please try again.,ઇમેઇલ મોકલતી વખતે ભૂલો આવી હતી. ફરી પ્રયત્ન કરો.,
diff --git a/erpnext/translations/he.csv b/erpnext/translations/he.csv
index 22b2522..e40b68e 100644
--- a/erpnext/translations/he.csv
+++ b/erpnext/translations/he.csv
@@ -3505,7 +3505,6 @@
 Reviews,ביקורות,
 Sender,שולח,
 Shop,חנות,
-Sign Up,הירשם,
 Subsidiary,חברת בת,
 There is some problem with the file url: {0},יש קצת בעיה עם כתובת אתר הקובץ: {0},
 There were errors while sending email. Please try again.,היו שגיאות בעת שליחת דואר אלקטרוני. אנא נסה שוב.,
diff --git a/erpnext/translations/hi.csv b/erpnext/translations/hi.csv
index ca41cf3..78094d7 100644
--- a/erpnext/translations/hi.csv
+++ b/erpnext/translations/hi.csv
@@ -3505,7 +3505,6 @@
 Reviews,समीक्षा,
 Sender,प्रेषक,
 Shop,दुकान,
-Sign Up,साइन अप करें,
 Subsidiary,सहायक,
 There is some problem with the file url: {0},फ़ाइल यूआरएल के साथ कुछ समस्या है: {0},
 There were errors while sending email. Please try again.,ईमेल भेजने के दौरान त्रुटि . पुन: प्रयास करें .,
diff --git a/erpnext/translations/hr.csv b/erpnext/translations/hr.csv
index 319b80b..232832f 100644
--- a/erpnext/translations/hr.csv
+++ b/erpnext/translations/hr.csv
@@ -3505,7 +3505,6 @@
 Reviews,Recenzije,
 Sender,Pošiljalac,
 Shop,Dućan,
-Sign Up,Prijavite se,
 Subsidiary,Podružnica,
 There is some problem with the file url: {0},Postoji neki problem s datotečnog URL: {0},
 There were errors while sending email. Please try again.,Bilo je grešaka tijekom slanja e-pošte. Molimo pokušajte ponovno .,
diff --git a/erpnext/translations/hu.csv b/erpnext/translations/hu.csv
index 0664728..e3dcd61 100644
--- a/erpnext/translations/hu.csv
+++ b/erpnext/translations/hu.csv
@@ -3505,7 +3505,6 @@
 Reviews,Vélemények,
 Sender,Küldő,
 Shop,Bolt,
-Sign Up,Regisztrálj,
 Subsidiary,Leányvállalat,
 There is some problem with the file url: {0},Van valami probléma a fájl URL-el: {0},
 There were errors while sending email. Please try again.,"Email küldés közben hibák voltak. Kérjük, próbálja újra.",
diff --git a/erpnext/translations/id.csv b/erpnext/translations/id.csv
index 1e50747..ccbb002 100644
--- a/erpnext/translations/id.csv
+++ b/erpnext/translations/id.csv
@@ -3505,7 +3505,6 @@
 Reviews,Ulasan,
 Sender,Pengirim,
 Shop,Toko,
-Sign Up,Daftar,
 Subsidiary,Anak Perusahaan,
 There is some problem with the file url: {0},Ada beberapa masalah dengan url berkas: {0},
 There were errors while sending email. Please try again.,Ada kesalahan saat mengirim email. Silakan coba lagi.,
diff --git a/erpnext/translations/is.csv b/erpnext/translations/is.csv
index c20c21e..5f11c63 100644
--- a/erpnext/translations/is.csv
+++ b/erpnext/translations/is.csv
@@ -3505,7 +3505,6 @@
 Reviews,Umsagnir,
 Sender,sendanda,
 Shop,Shop,
-Sign Up,Skráðu þig,
 Subsidiary,dótturfélag,
 There is some problem with the file url: {0},Það er einhver vandamál með skrá url: {0},
 There were errors while sending email. Please try again.,Það komu upp villur við að senda tölvupóst. Vinsamlegast reyndu aftur.,
diff --git a/erpnext/translations/it.csv b/erpnext/translations/it.csv
index 3d15d55..0dbde45 100644
--- a/erpnext/translations/it.csv
+++ b/erpnext/translations/it.csv
@@ -3505,7 +3505,6 @@
 Reviews,Recensioni,
 Sender,Mittente,
 Shop,Negozio,
-Sign Up,Iscriviti,
 Subsidiary,Sussidiario,
 There is some problem with the file url: {0},C&#39;è qualche problema con il file url: {0},
 There were errors while sending email. Please try again.,Ci sono stati errori durante l'invio email. Riprova.,
diff --git a/erpnext/translations/ja.csv b/erpnext/translations/ja.csv
index a11a9a1..210c78e 100644
--- a/erpnext/translations/ja.csv
+++ b/erpnext/translations/ja.csv
@@ -3505,7 +3505,6 @@
 Reviews,レビュー,
 Sender,送信者,
 Shop,店,
-Sign Up,サインアップ,
 Subsidiary,子会社,
 There is some problem with the file url: {0},ファイルURLに問題があります:{0},
 There were errors while sending email. Please try again.,メールの送信中にエラーが発生しました。もう一度お試しください。,
diff --git a/erpnext/translations/km.csv b/erpnext/translations/km.csv
index bd70595..1eb85cc 100644
--- a/erpnext/translations/km.csv
+++ b/erpnext/translations/km.csv
@@ -3505,7 +3505,6 @@
 Reviews,ការពិនិត្យឡើងវិញ។,
 Sender,អ្នកផ្ញើ,
 Shop,ហាងលក់,
-Sign Up,ចុះឈ្មោះ,
 Subsidiary,ក្រុមហ៊ុនបុត្រសម្ព័ន្ធ,
 There is some problem with the file url: {0},មានបញ្ហាមួយចំនួនជាមួយនឹងឯកសារ URL គឺ: {0},
 There were errors while sending email. Please try again.,មានកំហុសខណៈពេលដែលការផ្ញើអ៊ីម៉ែលនោះទេ។ សូមព្យាយាមម្តងទៀត។,
diff --git a/erpnext/translations/kn.csv b/erpnext/translations/kn.csv
index 7572a09..4e40c63 100644
--- a/erpnext/translations/kn.csv
+++ b/erpnext/translations/kn.csv
@@ -3505,7 +3505,6 @@
 Reviews,ವಿಮರ್ಶೆಗಳು,
 Sender,ಪ್ರೇಷಕ,
 Shop,ಅಂಗಡಿ,
-Sign Up,ಸೈನ್ ಅಪ್,
 Subsidiary,ಸಹಕಾರಿ,
 There is some problem with the file url: {0},ಕಡತ URL ನೊಂದಿಗೆ ಕೆಲವು ಸಮಸ್ಯೆಯಿದೆ: {0},
 There were errors while sending email. Please try again.,ಇಮೇಲ್ ಕಳುಹಿಸುವಾಗ ದೋಷಗಳು ಇದ್ದವು. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ .,
diff --git a/erpnext/translations/ko.csv b/erpnext/translations/ko.csv
index b873b73..36ec3af 100644
--- a/erpnext/translations/ko.csv
+++ b/erpnext/translations/ko.csv
@@ -3505,7 +3505,6 @@
 Reviews,리뷰,
 Sender,보낸 사람,
 Shop,상점,
-Sign Up,가입,
 Subsidiary,자회사,
 There is some problem with the file url: {0},파일 URL 몇 가지 문제가 있습니다 : {0},
 There were errors while sending email. Please try again.,이메일을 보내는 동안 오류가 발생했습니다.재시도하십시오.,
diff --git a/erpnext/translations/ku.csv b/erpnext/translations/ku.csv
index 89e12c0..28927a0 100644
--- a/erpnext/translations/ku.csv
+++ b/erpnext/translations/ku.csv
@@ -3505,7 +3505,6 @@
 Reviews,Nirxandin,
 Sender,virrêkerî,
 Shop,Dikan,
-Sign Up,Tomar kirin,
 Subsidiary,Şîrketa girêdayî,
 There is some problem with the file url: {0},e hinek pirsgirêk bi url file hene: {0},
 There were errors while sending email. Please try again.,bûn çewtî dema şandina email heye. Ji kerema xwe careke din biceribîne.,
diff --git a/erpnext/translations/lo.csv b/erpnext/translations/lo.csv
index 778a59b..3904308 100644
--- a/erpnext/translations/lo.csv
+++ b/erpnext/translations/lo.csv
@@ -3505,7 +3505,6 @@
 Reviews,ການທົບທວນຄືນ,
 Sender,ຜູ້ສົ່ງ,
 Shop,ບໍລິການຜ່ານ,
-Sign Up,ລົງທະບຽນ,
 Subsidiary,ບໍລິສັດຍ່ອຍ,
 There is some problem with the file url: {0},ມີບັນຫາບາງຢ່າງກັບ url ໄຟລ໌ແມ່ນ: {0},
 There were errors while sending email. Please try again.,ມີຄວາມຜິດພາດໃນຂະນະທີ່ການສົ່ງອີເມວໄດ້. ກະລຸນາພະຍາຍາມອີກເທື່ອຫນຶ່ງ.,
diff --git a/erpnext/translations/lt.csv b/erpnext/translations/lt.csv
index 4721ce4..d05688c 100644
--- a/erpnext/translations/lt.csv
+++ b/erpnext/translations/lt.csv
@@ -3505,7 +3505,6 @@
 Reviews,Atsiliepimai,
 Sender,Siuntėjas,
 Shop,parduotuvė,
-Sign Up,Registruotis,
 Subsidiary,filialas,
 There is some problem with the file url: {0},Yra kai kurie su URL failui problema: {0},
 There were errors while sending email. Please try again.,"Nebuvo klaidos Nors siunčiant laišką. Prašau, pabandykite dar kartą.",
diff --git a/erpnext/translations/lv.csv b/erpnext/translations/lv.csv
index b8499b2..d5cf852 100644
--- a/erpnext/translations/lv.csv
+++ b/erpnext/translations/lv.csv
@@ -3505,7 +3505,6 @@
 Reviews,Atsauksmes,
 Sender,Nosūtītājs,
 Shop,Veikals,
-Sign Up,Pierakstīties,
 Subsidiary,Filiāle,
 There is some problem with the file url: {0},Ir dažas problēmas ar faila url: {0},
 There were errors while sending email. Please try again.,"Bija kļūdas, nosūtot e-pastu. Lūdzu, mēģiniet vēlreiz.",
diff --git a/erpnext/translations/mk.csv b/erpnext/translations/mk.csv
index 8ecae03..e01cb70 100644
--- a/erpnext/translations/mk.csv
+++ b/erpnext/translations/mk.csv
@@ -3505,7 +3505,6 @@
 Reviews,Прегледи,
 Sender,Испраќачот,
 Shop,Продавница,
-Sign Up,Регистрирај се,
 Subsidiary,Подружница,
 There is some problem with the file url: {0},Има некој проблем со URL-то на фајл: {0},
 There were errors while sending email. Please try again.,Имаше грешка при испраќање на е-мејл. Ве молиме обидете се повторно.,
diff --git a/erpnext/translations/ml.csv b/erpnext/translations/ml.csv
index f649e6c..c5a98b6 100644
--- a/erpnext/translations/ml.csv
+++ b/erpnext/translations/ml.csv
@@ -3505,7 +3505,6 @@
 Reviews,അവലോകനങ്ങൾ,
 Sender,അയച്ചയാളുടെ,
 Shop,കട,
-Sign Up,സൈൻ അപ്പ് ചെയ്യുക,
 Subsidiary,സഹായകന്,
 There is some problem with the file url: {0},ഫയൽ URL ഉള്ള ചില പ്രശ്നം ഉണ്ട്: {0},
 There were errors while sending email. Please try again.,ഇമെയിൽ അയയ്ക്കുമ്പോൾ പിശകുകളുണ്ടായിരുന്നു. വീണ്ടും ശ്രമിക്കുക.,
diff --git a/erpnext/translations/mr.csv b/erpnext/translations/mr.csv
index 38effc1..21aaa3f 100644
--- a/erpnext/translations/mr.csv
+++ b/erpnext/translations/mr.csv
@@ -3505,7 +3505,6 @@
 Reviews,पुनरावलोकने,
 Sender,प्रेषक,
 Shop,दुकान,
-Sign Up,साइन अप करा,
 Subsidiary,उपकंपनी,
 There is some problem with the file url: {0},फाइल URL सह काही समस्या आहे: {0},
 There were errors while sending email. Please try again.,ई-मेल पाठविताना त्रुटी होत्या. कृपया पुन्हा प्रयत्न करा.,
diff --git a/erpnext/translations/ms.csv b/erpnext/translations/ms.csv
index 4ee650b..5a3d986 100644
--- a/erpnext/translations/ms.csv
+++ b/erpnext/translations/ms.csv
@@ -3505,7 +3505,6 @@
 Reviews,Ulasan,
 Sender,Penghantar,
 Shop,Kedai,
-Sign Up,Daftar,
 Subsidiary,Anak Syarikat,
 There is some problem with the file url: {0},Terdapat beberapa masalah dengan url fail: {0},
 There were errors while sending email. Please try again.,Terdapat ralat semasa menghantar e-mel. Sila cuba sekali lagi.,