Merge pull request #40386 from rtdany10/gp-report-rate

fix: wrong buying amount if delivered and billed qty varies
diff --git a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
index 5258214..41af06f 100644
--- a/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/accounting_dimension.py
@@ -261,14 +261,16 @@
 
 
 def get_checks_for_pl_and_bs_accounts():
-	dimensions = frappe.db.sql(
-		"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
-		FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
-		WHERE p.name = c.parent""",
-		as_dict=1,
-	)
+	if frappe.flags.accounting_dimensions_details is None:
+		# nosemgrep
+		frappe.flags.accounting_dimensions_details = frappe.db.sql(
+			"""SELECT p.label, p.disabled, p.fieldname, c.default_dimension, c.company, c.mandatory_for_pl, c.mandatory_for_bs
+			FROM `tabAccounting Dimension`p ,`tabAccounting Dimension Detail` c
+			WHERE p.name = c.parent""",
+			as_dict=1,
+		)
 
-	return dimensions
+	return frappe.flags.accounting_dimensions_details
 
 
 def get_dimension_with_children(doctype, dimensions):
diff --git a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
index cb7f5f5..10dbe3b 100644
--- a/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
+++ b/erpnext/accounts/doctype/accounting_dimension/test_accounting_dimension.py
@@ -78,6 +78,8 @@
 
 	def tearDown(self):
 		disable_dimension()
+		frappe.flags.accounting_dimensions_details = None
+		frappe.flags.dimension_filter_map = None
 
 
 def create_dimension():
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
index 01f6e60..2179a4d 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/accounting_dimension_filter.py
@@ -66,37 +66,39 @@
 
 
 def get_dimension_filter_map():
-	filters = frappe.db.sql(
-		"""
-		SELECT
-			a.applicable_on_account, d.dimension_value, p.accounting_dimension,
-			p.allow_or_restrict, a.is_mandatory
-		FROM
-			`tabApplicable On Account` a,
-			`tabAccounting Dimension Filter` p
-		LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
-		WHERE
-			p.name = a.parent
-			AND p.disabled = 0
-	""",
-		as_dict=1,
-	)
-
-	dimension_filter_map = {}
-
-	for f in filters:
-		f.fieldname = scrub(f.accounting_dimension)
-
-		build_map(
-			dimension_filter_map,
-			f.fieldname,
-			f.applicable_on_account,
-			f.dimension_value,
-			f.allow_or_restrict,
-			f.is_mandatory,
+	if not frappe.flags.get("dimension_filter_map"):
+		filters = frappe.db.sql(
+			"""
+			SELECT
+				a.applicable_on_account, d.dimension_value, p.accounting_dimension,
+				p.allow_or_restrict, a.is_mandatory
+			FROM
+				`tabApplicable On Account` a,
+				`tabAccounting Dimension Filter` p
+			LEFT JOIN `tabAllowed Dimension` d ON d.parent = p.name
+			WHERE
+				p.name = a.parent
+				AND p.disabled = 0
+		""",
+			as_dict=1,
 		)
 
-	return dimension_filter_map
+		dimension_filter_map = {}
+
+		for f in filters:
+			f.fieldname = scrub(f.accounting_dimension)
+
+			build_map(
+				dimension_filter_map,
+				f.fieldname,
+				f.applicable_on_account,
+				f.dimension_value,
+				f.allow_or_restrict,
+				f.is_mandatory,
+			)
+		frappe.flags.dimension_filter_map = dimension_filter_map
+
+	return frappe.flags.dimension_filter_map
 
 
 def build_map(map_object, dimension, account, filter_value, allow_or_restrict, is_mandatory):
diff --git a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
index 6aba2ab..3a7bf80 100644
--- a/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
+++ b/erpnext/accounts/doctype/accounting_dimension_filter/test_accounting_dimension_filter.py
@@ -47,6 +47,8 @@
 	def tearDown(self):
 		disable_dimension_filter()
 		disable_dimension()
+		frappe.flags.accounting_dimensions_details = None
+		frappe.flags.dimension_filter_map = None
 
 		for si in self.invoice_list:
 			si.load_from_db()
diff --git a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
index 30e564c..6728fea 100644
--- a/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
+++ b/erpnext/accounts/doctype/bank_statement_import/bank_statement_import.py
@@ -149,6 +149,9 @@
 	import_file = ImportFile("Bank Transaction", file=file, import_type="Insert New Records")
 
 	data = parse_data_from_template(import_file.raw_data)
+	# Importer expects 'Data Import' class, which has 'payload_count' attribute
+	if not data_import.get("payload_count"):
+		data_import.payload_count = len(data) - 1
 
 	if import_file_path:
 		add_bank_account(data, bank_account)
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index 5e17881..4246ba5 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -56,17 +56,17 @@
 		Bank Transaction should be on the same currency as the Bank Account.
 		"""
 		if self.currency and self.bank_account:
-			account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
-			account_currency = frappe.get_cached_value("Account", account, "account_currency")
+			if account := frappe.get_cached_value("Bank Account", self.bank_account, "account"):
+				account_currency = frappe.get_cached_value("Account", account, "account_currency")
 
-			if self.currency != account_currency:
-				frappe.throw(
-					_(
-						"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
-					).format(
-						frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
+				if self.currency != account_currency:
+					frappe.throw(
+						_(
+							"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
+						).format(
+							frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
+						)
 					)
-				)
 
 	def set_status(self):
 		if self.docstatus == 2:
diff --git a/erpnext/accounts/doctype/budget/budget.py b/erpnext/accounts/doctype/budget/budget.py
index 2cf9d97..aa77af6 100644
--- a/erpnext/accounts/doctype/budget/budget.py
+++ b/erpnext/accounts/doctype/budget/budget.py
@@ -139,6 +139,8 @@
 
 def validate_expense_against_budget(args, expense_amount=0):
 	args = frappe._dict(args)
+	if not frappe.get_all("Budget", limit=1):
+		return
 
 	if args.get("company") and not args.fiscal_year:
 		args.fiscal_year = get_fiscal_year(args.get("posting_date"), company=args.get("company"))[0]
@@ -146,6 +148,11 @@
 			"Company", args.get("company"), "exception_budget_approver_role"
 		)
 
+	if not frappe.get_cached_value(
+		"Budget", {"fiscal_year": args.fiscal_year, "company": args.company}
+	):  # nosec
+		return
+
 	if not args.account:
 		args.account = args.get("expense_account")
 
@@ -172,13 +179,13 @@
 		if (
 			args.get(budget_against)
 			and args.account
-			and frappe.db.get_value("Account", {"name": args.account, "root_type": "Expense"})
+			and (frappe.get_cached_value("Account", args.account, "root_type") == "Expense")
 		):
 
 			doctype = dimension.get("document_type")
 
 			if frappe.get_cached_value("DocType", doctype, "is_tree"):
-				lft, rgt = frappe.db.get_value(doctype, args.get(budget_against), ["lft", "rgt"])
+				lft, rgt = frappe.get_cached_value(doctype, args.get(budget_against), ["lft", "rgt"])
 				condition = """and exists(select name from `tab%s`
 					where lft<=%s and rgt>=%s and name=b.%s)""" % (
 					doctype,
diff --git a/erpnext/accounts/doctype/budget_account/budget_account.json b/erpnext/accounts/doctype/budget_account/budget_account.json
index ead0761..c7d8726 100644
--- a/erpnext/accounts/doctype/budget_account/budget_account.json
+++ b/erpnext/accounts/doctype/budget_account/budget_account.json
@@ -1,94 +1,42 @@
 {
- "allow_copy": 0, 
- "allow_import": 0, 
- "allow_rename": 0, 
- "beta": 0, 
- "creation": "2016-05-16 11:54:09.286135", 
- "custom": 0, 
- "docstatus": 0, 
- "doctype": "DocType", 
- "document_type": "", 
- "editable_grid": 1, 
- "engine": "InnoDB", 
+ "actions": [],
+ "creation": "2016-05-16 11:54:09.286135",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "account",
+  "budget_amount"
+ ],
  "fields": [
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "account", 
-   "fieldtype": "Link", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Account", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Account", 
-   "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, 
-   "unique": 0
-  }, 
+   "fieldname": "account",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Account",
+   "options": "Account",
+   "reqd": 1,
+   "search_index": 1
+  },
   {
-   "allow_on_submit": 0, 
-   "bold": 0, 
-   "collapsible": 0, 
-   "columns": 0, 
-   "fieldname": "budget_amount", 
-   "fieldtype": "Currency", 
-   "hidden": 0, 
-   "ignore_user_permissions": 0, 
-   "ignore_xss_filter": 0, 
-   "in_filter": 0, 
-   "in_list_view": 1, 
-   "label": "Budget Amount", 
-   "length": 0, 
-   "no_copy": 0, 
-   "options": "Company:company:default_currency", 
-   "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, 
-   "unique": 0
+   "fieldname": "budget_amount",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Budget Amount",
+   "options": "Company:company:default_currency",
+   "reqd": 1
   }
- ], 
- "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": "2017-01-02 17:02:53.339420", 
- "modified_by": "Administrator", 
- "module": "Accounts", 
- "name": "Budget Account", 
- "name_case": "", 
- "owner": "Administrator", 
- "permissions": [], 
- "quick_entry": 1, 
- "read_only": 0, 
- "read_only_onload": 0, 
- "sort_field": "modified", 
- "sort_order": "DESC", 
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2024-03-04 15:43:27.016947",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Budget Account",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
index d931f62..ad68352 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.js
@@ -3,22 +3,36 @@
 
 frappe.ui.form.on("Currency Exchange Settings", {
 	service_provider: function (frm) {
-		if (frm.doc.service_provider == "exchangerate.host") {
-			let result = ["result"];
-			let params = {
-				date: "{transaction_date}",
-				from: "{from_currency}",
-				to: "{to_currency}",
-			};
-			add_param(frm, "https://api.exchangerate.host/convert", params, result);
-		} else if (frm.doc.service_provider == "frankfurter.app") {
-			let result = ["rates", "{to_currency}"];
-			let params = {
-				base: "{from_currency}",
-				symbols: "{to_currency}",
-			};
-			add_param(frm, "https://frankfurter.app/{transaction_date}", params, result);
-		}
+		frm.call({
+			method: "erpnext.accounts.doctype.currency_exchange_settings.currency_exchange_settings.get_api_endpoint",
+			args: {
+				service_provider: frm.doc.service_provider,
+				use_http: frm.doc.use_http,
+			},
+			callback: function (r) {
+				if (r && r.message) {
+					if (frm.doc.service_provider == "exchangerate.host") {
+						let result = ["result"];
+						let params = {
+							date: "{transaction_date}",
+							from: "{from_currency}",
+							to: "{to_currency}",
+						};
+						add_param(frm, r.message, params, result);
+					} else if (frm.doc.service_provider == "frankfurter.app") {
+						let result = ["rates", "{to_currency}"];
+						let params = {
+							base: "{from_currency}",
+							symbols: "{to_currency}",
+						};
+						add_param(frm, r.message, params, result);
+					}
+				}
+			},
+		});
+	},
+	use_http: function (frm) {
+		frm.trigger("service_provider");
 	},
 });
 
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
index df232a5..bd90b8a 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.json
@@ -9,6 +9,7 @@
   "disabled",
   "service_provider",
   "api_endpoint",
+  "use_http",
   "access_key",
   "url",
   "column_break_3",
@@ -91,12 +92,19 @@
    "fieldname": "access_key",
    "fieldtype": "Data",
    "label": "Access Key"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.service_provider != \"Custom\"",
+   "fieldname": "use_http",
+   "fieldtype": "Check",
+   "label": "Use HTTP Protocol"
   }
  ],
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-10-04 15:30:25.333860",
+ "modified": "2024-03-18 08:32:26.895076",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Currency Exchange Settings",
diff --git a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
index 3393d41..b8817c6 100644
--- a/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
+++ b/erpnext/accounts/doctype/currency_exchange_settings/currency_exchange_settings.py
@@ -31,6 +31,7 @@
 		result_key: DF.Table[CurrencyExchangeSettingsResult]
 		service_provider: DF.Literal["frankfurter.app", "exchangerate.host", "Custom"]
 		url: DF.Data | None
+		use_http: DF.Check
 	# end: auto-generated types
 
 	def validate(self):
@@ -53,7 +54,7 @@
 			self.set("result_key", [])
 			self.set("req_params", [])
 
-			self.api_endpoint = "https://api.exchangerate.host/convert"
+			self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
 			self.append("result_key", {"key": "result"})
 			self.append("req_params", {"key": "access_key", "value": self.access_key})
 			self.append("req_params", {"key": "amount", "value": "1"})
@@ -64,7 +65,7 @@
 			self.set("result_key", [])
 			self.set("req_params", [])
 
-			self.api_endpoint = "https://frankfurter.app/{transaction_date}"
+			self.api_endpoint = get_api_endpoint(self.service_provider, self.use_http)
 			self.append("result_key", {"key": "rates"})
 			self.append("result_key", {"key": "{to_currency}"})
 			self.append("req_params", {"key": "base", "value": "{from_currency}"})
@@ -103,3 +104,19 @@
 			frappe.throw(_("Returned exchange rate is neither integer not float."))
 
 		self.url = response.url
+
+
+@frappe.whitelist()
+def get_api_endpoint(service_provider: str = None, use_http: bool = False):
+	if service_provider and service_provider in ["exchangerate.host", "frankfurter.app"]:
+		if service_provider == "exchangerate.host":
+			api = "api.exchangerate.host/convert"
+		elif service_provider == "frankfurter.app":
+			api = "frankfurter.app/{transaction_date}"
+
+		protocol = "https://"
+		if use_http:
+			protocol = "http://"
+
+		return protocol + api
+	return None
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 8be09db..29732ef 100644
--- a/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
+++ b/erpnext/accounts/doctype/exchange_rate_revaluation/exchange_rate_revaluation.py
@@ -628,21 +628,21 @@
 	if account_balance and (
 		account_balance[0].balance or account_balance[0].balance_in_account_currency
 	):
-		account_with_new_balance = ExchangeRateRevaluation.calculate_new_account_balance(
+		if account_with_new_balance := ExchangeRateRevaluation.calculate_new_account_balance(
 			company, posting_date, account_balance
-		)
-		row = account_with_new_balance[0]
-		account_details.update(
-			{
-				"balance_in_base_currency": row["balance_in_base_currency"],
-				"balance_in_account_currency": row["balance_in_account_currency"],
-				"current_exchange_rate": row["current_exchange_rate"],
-				"new_exchange_rate": row["new_exchange_rate"],
-				"new_balance_in_base_currency": row["new_balance_in_base_currency"],
-				"new_balance_in_account_currency": row["new_balance_in_account_currency"],
-				"zero_balance": row["zero_balance"],
-				"gain_loss": row["gain_loss"],
-			}
-		)
+		):
+			row = account_with_new_balance[0]
+			account_details.update(
+				{
+					"balance_in_base_currency": row["balance_in_base_currency"],
+					"balance_in_account_currency": row["balance_in_account_currency"],
+					"current_exchange_rate": row["current_exchange_rate"],
+					"new_exchange_rate": row["new_exchange_rate"],
+					"new_balance_in_base_currency": row["new_balance_in_base_currency"],
+					"new_balance_in_account_currency": row["new_balance_in_account_currency"],
+					"zero_balance": row["zero_balance"],
+					"gain_loss": row["gain_loss"],
+				}
+			)
 
 	return account_details
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.json b/erpnext/accounts/doctype/gl_entry/gl_entry.json
index c071193..991a08b 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.json
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.json
@@ -179,7 +179,8 @@
    "fieldname": "voucher_detail_no",
    "fieldtype": "Data",
    "label": "Voucher Detail No",
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "fieldname": "project",
@@ -290,7 +291,7 @@
  "idx": 1,
  "in_create": 1,
  "links": [],
- "modified": "2023-09-26 12:03:23.031733",
+ "modified": "2024-03-19 18:43:42.235373",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "GL Entry",
@@ -325,4 +326,4 @@
  "sort_field": "modified",
  "sort_order": "DESC",
  "states": []
-}
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/gl_entry/gl_entry.py b/erpnext/accounts/doctype/gl_entry/gl_entry.py
index def2838..a6f6d4e 100644
--- a/erpnext/accounts/doctype/gl_entry/gl_entry.py
+++ b/erpnext/accounts/doctype/gl_entry/gl_entry.py
@@ -32,8 +32,6 @@
 		account: DF.Link | None
 		account_currency: DF.Link | None
 		against: DF.Text | None
-		against_link: DF.DynamicLink | None
-		against_type: DF.Link | None
 		against_voucher: DF.DynamicLink | None
 		against_voucher_type: DF.Link | None
 		company: DF.Link | None
@@ -323,7 +321,7 @@
 		party_condition = ""
 
 	if against_voucher_type == "Sales Invoice":
-		party_account = frappe.db.get_value(against_voucher_type, against_voucher, "debit_to")
+		party_account = frappe.get_cached_value(against_voucher_type, against_voucher, "debit_to")
 		account_condition = "and account in ({0}, {1})".format(
 			frappe.db.escape(account), frappe.db.escape(party_account)
 		)
@@ -391,8 +389,8 @@
 def validate_frozen_account(account, adv_adj=None):
 	frozen_account = frappe.get_cached_value("Account", account, "freeze_account")
 	if frozen_account == "Yes" and not adv_adj:
-		frozen_accounts_modifier = frappe.db.get_single_value(
-			"Accounts Settings", "frozen_accounts_modifier"
+		frozen_accounts_modifier = frappe.get_cached_value(
+			"Accounts Settings", None, "frozen_accounts_modifier"
 		)
 
 		if not frozen_accounts_modifier:
diff --git a/erpnext/accounts/doctype/journal_entry/journal_entry.js b/erpnext/accounts/doctype/journal_entry/journal_entry.js
index f6d35fe..3186d07 100644
--- a/erpnext/accounts/doctype/journal_entry/journal_entry.js
+++ b/erpnext/accounts/doctype/journal_entry/journal_entry.js
@@ -196,7 +196,7 @@
 			!(frm.doc.accounts || []).length ||
 			((frm.doc.accounts || []).length === 1 && !frm.doc.accounts[0].account)
 		) {
-			if (in_list(["Bank Entry", "Cash Entry"], frm.doc.voucher_type)) {
+			if (["Bank Entry", "Cash Entry"].includes(frm.doc.voucher_type)) {
 				return frappe.call({
 					type: "GET",
 					method: "erpnext.accounts.doctype.journal_entry.journal_entry.get_default_bank_cash_account",
@@ -308,7 +308,7 @@
 				filters: [[jvd.reference_type, "docstatus", "=", 1]],
 			};
 
-			if (in_list(["Sales Invoice", "Purchase Invoice"], jvd.reference_type)) {
+			if (["Sales Invoice", "Purchase Invoice"].includes(jvd.reference_type)) {
 				out.filters.push([jvd.reference_type, "outstanding_amount", "!=", 0]);
 				// Filter by cost center
 				if (jvd.cost_center) {
@@ -320,7 +320,7 @@
 				out.filters.push([jvd.reference_type, party_account_field, "=", jvd.account]);
 			}
 
-			if (in_list(["Sales Order", "Purchase Order"], jvd.reference_type)) {
+			if (["Sales Order", "Purchase Order"].includes(jvd.reference_type)) {
 				// party_type and party mandatory
 				frappe.model.validate_missing(jvd, "party_type");
 				frappe.model.validate_missing(jvd, "party");
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index ab50c38..0cb1a3d 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -32,7 +32,7 @@
 		frm.set_query("paid_from", function () {
 			frm.events.validate_company(frm);
 
-			var account_types = in_list(["Pay", "Internal Transfer"], frm.doc.payment_type)
+			var account_types = ["Pay", "Internal Transfer"].includes(frm.doc.payment_type)
 				? ["Bank", "Cash"]
 				: [frappe.boot.party_account_types[frm.doc.party_type]];
 			return {
@@ -87,7 +87,7 @@
 		frm.set_query("paid_to", function () {
 			frm.events.validate_company(frm);
 
-			var account_types = in_list(["Receive", "Internal Transfer"], frm.doc.payment_type)
+			var account_types = ["Receive", "Internal Transfer"].includes(frm.doc.payment_type)
 				? ["Bank", "Cash"]
 				: [frappe.boot.party_account_types[frm.doc.party_type]];
 			return {
@@ -134,7 +134,7 @@
 		frm.set_query("payment_term", "references", function (frm, cdt, cdn) {
 			const child = locals[cdt][cdn];
 			if (
-				in_list(["Purchase Invoice", "Sales Invoice"], child.reference_doctype) &&
+				["Purchase Invoice", "Sales Invoice"].includes(child.reference_doctype) &&
 				child.reference_name
 			) {
 				return {
@@ -395,10 +395,6 @@
 				return {
 					query: "erpnext.controllers.queries.employee_query",
 				};
-			} else if (frm.doc.party_type == "Customer") {
-				return {
-					query: "erpnext.controllers.queries.customer_query",
-				};
 			}
 		});
 
@@ -627,7 +623,7 @@
 		if (frm.doc.paid_from_account_currency == company_currency) {
 			frm.set_value("source_exchange_rate", 1);
 		} else if (frm.doc.paid_from) {
-			if (in_list(["Internal Transfer", "Pay"], frm.doc.payment_type)) {
+			if (["Internal Transfer", "Pay"].includes(frm.doc.payment_type)) {
 				let company_currency = frappe.get_doc(":Company", frm.doc.company).default_currency;
 				frappe.call({
 					method: "erpnext.setup.utils.get_exchange_rate",
@@ -1046,7 +1042,7 @@
 			}
 
 			var allocated_positive_outstanding = paid_amount + allocated_negative_outstanding;
-		} else if (in_list(["Customer", "Supplier"], frm.doc.party_type)) {
+		} else if (["Customer", "Supplier"].includes(frm.doc.party_type)) {
 			total_negative_outstanding = flt(total_negative_outstanding, precision("outstanding_amount"));
 			if (paid_amount > total_negative_outstanding) {
 				if (total_negative_outstanding == 0) {
@@ -1217,7 +1213,7 @@
 
 			if (
 				frm.doc.party_type == "Customer" &&
-				!in_list(["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"], row.reference_doctype)
+				!["Sales Order", "Sales Invoice", "Journal Entry", "Dunning"].includes(row.reference_doctype)
 			) {
 				frappe.model.set_value(row.doctype, row.name, "reference_doctype", null);
 				frappe.msgprint(
@@ -1231,7 +1227,7 @@
 
 			if (
 				frm.doc.party_type == "Supplier" &&
-				!in_list(["Purchase Order", "Purchase Invoice", "Journal Entry"], row.reference_doctype)
+				!["Purchase Order", "Purchase Invoice", "Journal Entry"].includes(row.reference_doctype)
 			) {
 				frappe.model.set_value(row.doctype, row.name, "against_voucher_type", null);
 				frappe.msgprint(
@@ -1327,7 +1323,7 @@
 
 	bank_account: function (frm) {
 		const field = frm.doc.payment_type == "Pay" ? "paid_from" : "paid_to";
-		if (frm.doc.bank_account && in_list(["Pay", "Receive"], frm.doc.payment_type)) {
+		if (frm.doc.bank_account && ["Pay", "Receive"].includes(frm.doc.payment_type)) {
 			frappe.call({
 				method: "erpnext.accounts.doctype.bank_account.bank_account.get_bank_account_details",
 				args: {
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.py b/erpnext/accounts/doctype/payment_entry/payment_entry.py
index 7970a3e..b4d1d39 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -30,7 +30,7 @@
 	make_reverse_gl_entries,
 	process_gl_map,
 )
-from erpnext.accounts.party import get_party_account
+from erpnext.accounts.party import get_party_account, set_contact_details
 from erpnext.accounts.utils import (
 	cancel_exchange_gain_loss_journal,
 	get_account_currency,
@@ -444,6 +444,8 @@
 				self.party_name = frappe.db.get_value(self.party_type, self.party, "name")
 
 		if self.party:
+			if not self.contact_person:
+				set_contact_details(self, party=frappe._dict({"name": self.party}), party_type=self.party_type)
 			if not self.party_balance:
 				self.party_balance = get_balance_on(
 					party_type=self.party_type, party=self.party, date=self.posting_date, company=self.company
@@ -487,7 +489,9 @@
 				ref_details = get_reference_details(
 					d.reference_doctype, d.reference_name, self.party_account_currency
 				)
-				if ref_exchange_rate:
+
+				# Only update exchange rate when the reference is Journal Entry
+				if ref_exchange_rate and d.reference_doctype == "Journal Entry":
 					ref_details.update({"exchange_rate": ref_exchange_rate})
 
 				for field, value in ref_details.items():
@@ -609,9 +613,9 @@
 
 	def get_valid_reference_doctypes(self):
 		if self.party_type == "Customer":
-			return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning")
+			return ("Sales Order", "Sales Invoice", "Journal Entry", "Dunning", "Payment Entry")
 		elif self.party_type == "Supplier":
-			return ("Purchase Order", "Purchase Invoice", "Journal Entry")
+			return ("Purchase Order", "Purchase Invoice", "Journal Entry", "Payment Entry")
 		elif self.party_type == "Shareholder":
 			return ("Journal Entry",)
 		elif self.party_type == "Employee":
@@ -1277,6 +1281,7 @@
 					"Journal Entry",
 					"Sales Order",
 					"Purchase Order",
+					"Payment Entry",
 				):
 					self.add_advance_gl_for_reference(gl_entries, ref)
 
@@ -1299,7 +1304,9 @@
 		if getdate(posting_date) < getdate(self.posting_date):
 			posting_date = self.posting_date
 
-		dr_or_cr = "credit" if invoice.reference_doctype in ["Sales Invoice", "Sales Order"] else "debit"
+		dr_or_cr = (
+			"credit" if invoice.reference_doctype in ["Sales Invoice", "Payment Entry"] else "debit"
+		)
 		args_dict["account"] = invoice.account
 		args_dict[dr_or_cr] = invoice.allocated_amount
 		args_dict[dr_or_cr + "_in_account_currency"] = invoice.allocated_amount
@@ -1749,7 +1756,7 @@
 		outstanding_invoices = get_outstanding_invoices(
 			args.get("party_type"),
 			args.get("party"),
-			party_account,
+			[party_account],
 			common_filter=common_filter,
 			posting_date=posting_and_due_date,
 			min_outstanding=args.get("outstanding_amt_greater_than"),
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 5a014b8..6323e4c 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -1514,6 +1514,168 @@
 			for field in ["account", "debit", "credit"]:
 				self.assertEqual(self.expected_gle[row][field], gl_entries[row][field])
 
+	def test_reverse_payment_reconciliation(self):
+		customer = create_customer(frappe.generate_hash(length=10), "INR")
+		pe = create_payment_entry(
+			party_type="Customer",
+			party=customer,
+			payment_type="Receive",
+			paid_from="Debtors - _TC",
+			paid_to="_Test Cash - _TC",
+		)
+		pe.submit()
+
+		reverse_pe = create_payment_entry(
+			party_type="Customer",
+			party=customer,
+			payment_type="Pay",
+			paid_from="_Test Cash - _TC",
+			paid_to="Debtors - _TC",
+		)
+		reverse_pe.submit()
+
+		pr = frappe.get_doc("Payment Reconciliation")
+		pr.company = "_Test Company"
+		pr.party_type = "Customer"
+		pr.party = customer
+		pr.receivable_payable_account = "Debtors - _TC"
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.invoices), 1)
+		self.assertEqual(len(pr.payments), 1)
+
+		self.assertEqual(reverse_pe.name, pr.invoices[0].invoice_number)
+		self.assertEqual(pe.name, pr.payments[0].reference_name)
+
+		invoices = [x.as_dict() for x in pr.invoices]
+		payments = [pr.payments[0].as_dict()]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+		self.assertEqual(len(pr.invoices), 0)
+		self.assertEqual(len(pr.payments), 0)
+
+	def test_advance_reverse_payment_reconciliation(self):
+		from erpnext.accounts.doctype.account.test_account import create_account
+
+		company = "_Test Company"
+		customer = create_customer(frappe.generate_hash(length=10), "INR")
+		advance_account = create_account(
+			parent_account="Current Assets - _TC",
+			account_name="Advances Received",
+			company=company,
+			account_type="Receivable",
+		)
+
+		frappe.db.set_value(
+			"Company",
+			company,
+			{
+				"book_advance_payments_in_separate_party_account": 1,
+				"default_advance_received_account": advance_account,
+			},
+		)
+		# Reverse Payment(essentially an Invoice)
+		reverse_pe = create_payment_entry(
+			party_type="Customer",
+			party=customer,
+			payment_type="Pay",
+			paid_from="_Test Cash - _TC",
+			paid_to=advance_account,
+		)
+		reverse_pe.save()  # use save() to trigger set_liability_account()
+		reverse_pe.submit()
+
+		# Advance Payment
+		pe = create_payment_entry(
+			party_type="Customer",
+			party=customer,
+			payment_type="Receive",
+			paid_from=advance_account,
+			paid_to="_Test Cash - _TC",
+		)
+		pe.save()  # use save() to trigger set_liability_account()
+		pe.submit()
+
+		# Partially reconcile advance against invoice
+		pr = frappe.get_doc("Payment Reconciliation")
+		pr.company = company
+		pr.party_type = "Customer"
+		pr.party = customer
+		pr.receivable_payable_account = "Debtors - _TC"
+		pr.default_advance_account = advance_account
+		pr.get_unreconciled_entries()
+
+		self.assertEqual(len(pr.invoices), 1)
+		self.assertEqual(len(pr.payments), 1)
+
+		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}))
+		pr.allocation[0].allocated_amount = 400
+		pr.reconcile()
+
+		# assert General and Payment Ledger entries post partial reconciliation
+		self.expected_gle = [
+			{"account": "Debtors - _TC", "debit": 0.0, "credit": 400.0},
+			{"account": advance_account, "debit": 400.0, "credit": 0.0},
+			{"account": advance_account, "debit": 0.0, "credit": 1000.0},
+			{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
+		]
+		self.expected_ple = [
+			{
+				"account": advance_account,
+				"voucher_no": pe.name,
+				"against_voucher_no": pe.name,
+				"amount": -1000.0,
+			},
+			{
+				"account": "Debtors - _TC",
+				"voucher_no": pe.name,
+				"against_voucher_no": reverse_pe.name,
+				"amount": -400.0,
+			},
+			{
+				"account": advance_account,
+				"voucher_no": pe.name,
+				"against_voucher_no": pe.name,
+				"amount": 400.0,
+			},
+		]
+		self.voucher_no = pe.name
+		self.check_gl_entries()
+		self.check_pl_entries()
+
+		# Unreconcile
+		unrecon = (
+			frappe.get_doc(
+				{
+					"doctype": "Unreconcile Payment",
+					"company": company,
+					"voucher_type": pe.doctype,
+					"voucher_no": pe.name,
+					"allocations": [{"reference_doctype": reverse_pe.doctype, "reference_name": reverse_pe.name}],
+				}
+			)
+			.save()
+			.submit()
+		)
+
+		# assert General and Payment Ledger entries post unreconciliation
+		self.expected_gle = [
+			{"account": advance_account, "debit": 0.0, "credit": 1000.0},
+			{"account": "_Test Cash - _TC", "debit": 1000.0, "credit": 0.0},
+		]
+		self.expected_ple = [
+			{
+				"account": advance_account,
+				"voucher_no": pe.name,
+				"against_voucher_no": pe.name,
+				"amount": -1000.0,
+			},
+		]
+		self.voucher_no = pe.name
+		self.check_gl_entries()
+		self.check_pl_entries()
+
 
 def create_payment_entry(**args):
 	payment_entry = frappe.new_doc("Payment Entry")
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
index e8dfda2..3fea325 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
+++ b/erpnext/accounts/doctype/payment_ledger_entry/payment_ledger_entry.py
@@ -161,11 +161,12 @@
 	def on_update(self):
 		adv_adj = self.flags.adv_adj
 		if not self.flags.from_repost:
-			self.validate_account_details()
-			self.validate_dimensions_for_pl_and_bs()
-			self.validate_allowed_dimensions()
-			validate_balance_type(self.account, adv_adj)
 			validate_frozen_account(self.account, adv_adj)
+			if not self.delinked:
+				self.validate_account_details()
+				self.validate_dimensions_for_pl_and_bs()
+				self.validate_allowed_dimensions()
+				validate_balance_type(self.account, adv_adj)
 
 		# update outstanding amount
 		if (
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 972ce26..dcb1a16 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -340,10 +340,15 @@
 
 		self.build_qb_filter_conditions(get_invoices=True)
 
+		accounts = [self.receivable_payable_account]
+
+		if self.default_advance_account:
+			accounts.append(self.default_advance_account)
+
 		non_reconciled_invoices = get_outstanding_invoices(
 			self.party_type,
 			self.party,
-			self.receivable_payable_account,
+			accounts,
 			common_filter=self.common_filter_conditions,
 			posting_date=self.ple_posting_date_filter,
 			min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
diff --git a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
index 301e6ef..1d20a5b 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/test_payment_reconciliation.py
@@ -1130,6 +1130,17 @@
 		self.assertEqual(pr.allocation[0].allocated_amount, 85)
 		self.assertEqual(pr.allocation[0].difference_amount, 0)
 
+		pr.reconcile()
+		si.reload()
+		self.assertEqual(si.outstanding_amount, 0)
+		# No Exchange Gain/Loss journal should be generated
+		exc_gain_loss_journals = frappe.db.get_all(
+			"Journal Entry Account",
+			filters={"reference_type": si.doctype, "reference_name": si.name, "docstatus": 1},
+			fields=["parent"],
+		)
+		self.assertEqual(exc_gain_loss_journals, [])
+
 	def test_reconciliation_purchase_invoice_against_return(self):
 		self.supplier = "_Test Supplier USD"
 		pi = self.create_purchase_invoice(qty=5, rate=50, do_not_submit=True)
diff --git a/erpnext/accounts/doctype/payment_request/payment_request.js b/erpnext/accounts/doctype/payment_request/payment_request.js
index d07f824..f12facf 100644
--- a/erpnext/accounts/doctype/payment_request/payment_request.js
+++ b/erpnext/accounts/doctype/payment_request/payment_request.js
@@ -32,7 +32,7 @@
 	if (
 		frm.doc.payment_request_type == "Inward" &&
 		frm.doc.payment_channel !== "Phone" &&
-		!in_list(["Initiated", "Paid"], frm.doc.status) &&
+		!["Initiated", "Paid"].includes(frm.doc.status) &&
 		!frm.doc.__islocal &&
 		frm.doc.docstatus == 1
 	) {
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 2a84d97..76c0a09 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -141,7 +141,8 @@
 		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}
+			"GL Entry",
+			{"posting_date": ("<=", last_year_closing), "company": self.company, "is_cancelled": 0},
 		):
 			return
 
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
index 955b66a..d7b1736 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.json
@@ -775,7 +775,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "label": "Taxes and Charges Calculation",
    "no_copy": 1,
    "oldfieldtype": "HTML",
@@ -1563,7 +1563,7 @@
  "icon": "fa fa-file-text",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-11-20 12:27:12.848149",
+ "modified": "2024-03-20 16:00:34.268756",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "POS Invoice",
diff --git a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
index 1a7eef6..5db3da1 100644
--- a/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
+++ b/erpnext/accounts/doctype/pos_invoice/pos_invoice.py
@@ -109,7 +109,7 @@
 		loyalty_redemption_cost_center: DF.Link | None
 		naming_series: DF.Literal["ACC-PSINV-.YYYY.-"]
 		net_total: DF.Currency
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		outstanding_amount: DF.Currency
 		packed_items: DF.Table[PackedItem]
 		paid_amount: DF.Currency
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 5307ccb..81ebf97 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
@@ -89,10 +89,11 @@
 	<table class="table table-bordered">
 		<thead>
 			<tr>
-				<th style="width: 25%">30 Days</th>
-				<th style="width: 25%">60 Days</th>
-				<th style="width: 25%">90 Days</th>
-				<th style="width: 25%">120 Days</th>
+				<th style="width: 20%">0 - 30 Days</th>
+				<th style="width: 20%">30 - 60 Days</th>
+				<th style="width: 20%">60 - 90 Days</th>
+				<th style="width: 20%">90 - 120 Days</th>
+				<th style="width: 20%">Above 120 Days</th>
 			</tr>
 		</thead>
 		<tbody>
@@ -101,6 +102,7 @@
 				<td>{{ frappe.utils.fmt_money(ageing.range2, currency=filters.presentation_currency) }}</td>
 				<td>{{ frappe.utils.fmt_money(ageing.range3, currency=filters.presentation_currency) }}</td>
 				<td>{{ frappe.utils.fmt_money(ageing.range4, currency=filters.presentation_currency) }}</td>
+				<td>{{ frappe.utils.fmt_money(ageing.range5, currency=filters.presentation_currency) }}</td>
 			</tr>
 		</tbody>
 	</table>
diff --git a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
index 0bb8d3a..a9c1900 100644
--- a/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/promotional_scheme.py
@@ -60,6 +60,8 @@
 	"free_item_rate",
 	"same_item",
 	"is_recursive",
+	"recurse_for",
+	"apply_recursion_over",
 	"apply_multiple_pricing_rules",
 ]
 
diff --git a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
index 9e576fb..9b40c98 100644
--- a/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
+++ b/erpnext/accounts/doctype/promotional_scheme/test_promotional_scheme.py
@@ -107,6 +107,28 @@
 		price_rules = frappe.get_all("Pricing Rule", filters={"promotional_scheme": ps.name})
 		self.assertEqual(price_rules, [])
 
+	def test_pricing_rule_for_product_discount_slabs(self):
+		ps = make_promotional_scheme()
+		ps.set("price_discount_slabs", [])
+		ps.set(
+			"product_discount_slabs",
+			[
+				{
+					"rule_description": "12+1",
+					"min_qty": 12,
+					"free_item": "_Test Item 2",
+					"free_qty": 1,
+					"is_recursive": 1,
+					"recurse_for": 12,
+				}
+			],
+		)
+		ps.save()
+		pr = frappe.get_doc("Pricing Rule", {"promotional_scheme_id": ps.product_discount_slabs[0].name})
+		self.assertSequenceEqual(
+			[pr.min_qty, pr.free_item, pr.free_qty, pr.recurse_for], [12, "_Test Item 2", 1, 12]
+		)
+
 
 def make_promotional_scheme(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
index 3eab515..4e61d04 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.json
@@ -27,7 +27,9 @@
   "threshold_percentage",
   "column_break_15",
   "priority",
-  "is_recursive"
+  "is_recursive",
+  "recurse_for",
+  "apply_recursion_over"
  ],
  "fields": [
   {
@@ -161,17 +163,36 @@
    "fieldname": "is_recursive",
    "fieldtype": "Check",
    "label": "Is Recursive"
+  },
+  {
+   "default": "0",
+   "depends_on": "is_recursive",
+   "description": "Give free item for every N quantity",
+   "fieldname": "recurse_for",
+   "fieldtype": "Float",
+   "label": "Recurse Every (As Per Transaction UOM)",
+   "mandatory_depends_on": "is_recursive"
+  },
+  {
+   "default": "0",
+   "depends_on": "is_recursive",
+   "description": "Qty for which recursion isn't applicable.",
+   "fieldname": "apply_recursion_over",
+   "fieldtype": "Float",
+   "label": "Apply Recursion Over (As Per Transaction UOM)",
+   "mandatory_depends_on": "is_recursive"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-03-06 21:58:18.162346",
+ "modified": "2024-03-12 12:53:58.199108",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Promotional Scheme Product Discount",
  "owner": "Administrator",
  "permissions": [],
  "sort_field": "modified",
- "sort_order": "DESC"
+ "sort_order": "DESC",
+ "states": []
 }
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
index 7dd5fea..1463a7b 100644
--- a/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
+++ b/erpnext/accounts/doctype/promotional_scheme_product_discount/promotional_scheme_product_discount.py
@@ -15,6 +15,7 @@
 		from frappe.types import DF
 
 		apply_multiple_pricing_rules: DF.Check
+		apply_recursion_over: DF.Float
 		disable: DF.Check
 		free_item: DF.Link | None
 		free_item_rate: DF.Currency
@@ -51,6 +52,7 @@
 			"19",
 			"20",
 		]
+		recurse_for: DF.Float
 		rule_description: DF.SmallText
 		same_item: DF.Check
 		threshold_percentage: DF.Percent
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index d6455b2..957611f 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -3,6 +3,8 @@
 
 frappe.provide("erpnext.accounts");
 
+cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
+
 erpnext.accounts.payment_triggers.setup("Purchase Invoice");
 erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
 erpnext.accounts.taxes.setup_tax_validations("Purchase Invoice");
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
index d12a43c..44d3d48 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.json
@@ -22,6 +22,7 @@
   "is_paid",
   "is_return",
   "return_against",
+  "update_outstanding_for_self",
   "update_billed_amount_in_purchase_order",
   "update_billed_amount_in_purchase_receipt",
   "apply_tds",
@@ -759,7 +760,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "label": "Taxes and Charges Calculation",
    "no_copy": 1,
    "oldfieldtype": "HTML",
@@ -1623,13 +1624,21 @@
    "fieldtype": "Link",
    "label": "Supplier Group",
    "options": "Supplier Group"
+  },
+  {
+   "default": "1",
+   "depends_on": "eval: doc.is_return && doc.return_against",
+   "description": "Debit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
+   "fieldname": "update_outstanding_for_self",
+   "fieldtype": "Check",
+   "label": "Update Outstanding for Self"
   }
  ],
  "icon": "fa fa-file-text",
  "idx": 204,
  "is_submittable": 1,
  "links": [],
- "modified": "2024-02-25 11:20:28.366808",
+ "modified": "2024-03-20 15:57:00.736868",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice",
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 8dfd69f..19b7092 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -3,7 +3,7 @@
 
 
 import frappe
-from frappe import _, throw
+from frappe import _, qb, throw
 from frappe.model.mapper import get_mapped_doc
 from frappe.query_builder.functions import Sum
 from frappe.utils import cint, cstr, flt, formatdate, get_link_to_form, getdate, nowdate
@@ -147,7 +147,7 @@
 		net_total: DF.Currency
 		on_hold: DF.Check
 		only_include_allocated_payments: DF.Check
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		outstanding_amount: DF.Currency
 		paid_amount: DF.Currency
 		party_account_currency: DF.Link | None
@@ -217,6 +217,7 @@
 		unrealized_profit_loss_account: DF.Link | None
 		update_billed_amount_in_purchase_order: DF.Check
 		update_billed_amount_in_purchase_receipt: DF.Check
+		update_outstanding_for_self: DF.Check
 		update_stock: DF.Check
 		use_company_roundoff_cost_center: DF.Check
 		use_transaction_date_exchange_rate: DF.Check
@@ -741,13 +742,12 @@
 				self.db_set("repost_required", self.needs_repost)
 
 	def make_gl_entries(self, gl_entries=None, from_repost=False):
-		if not gl_entries:
-			gl_entries = self.get_gl_entries()
+		update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
+		if self.docstatus == 1:
+			if not gl_entries:
+				gl_entries = self.get_gl_entries()
 
-		if gl_entries:
-			update_outstanding = "No" if (cint(self.is_paid) or self.write_off_account) else "Yes"
-
-			if self.docstatus == 1:
+			if gl_entries:
 				make_gl_entries(
 					gl_entries,
 					update_outstanding=update_outstanding,
@@ -755,29 +755,43 @@
 					from_repost=from_repost,
 				)
 				self.make_exchange_gain_loss_journal()
-			elif self.docstatus == 2:
-				provisional_entries = [a for a in gl_entries if a.voucher_type == "Purchase Receipt"]
-				make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
-				if provisional_entries:
-					for entry in provisional_entries:
-						frappe.db.set_value(
-							"GL Entry",
-							{"voucher_type": "Purchase Receipt", "voucher_detail_no": entry.voucher_detail_no},
-							"is_cancelled",
-							1,
-						)
-
-			if update_outstanding == "No":
-				update_outstanding_amt(
-					self.credit_to,
-					"Supplier",
-					self.supplier,
-					self.doctype,
-					self.return_against if cint(self.is_return) and self.return_against else self.name,
-				)
-
-		elif self.docstatus == 2 and cint(self.update_stock) and self.auto_accounting_for_stock:
+		elif self.docstatus == 2:
 			make_reverse_gl_entries(voucher_type=self.doctype, voucher_no=self.name)
+			self.cancel_provisional_entries()
+
+		self.update_supplier_outstanding(update_outstanding)
+
+	def cancel_provisional_entries(self):
+		rows = set()
+		purchase_receipts = set()
+		for d in self.items:
+			if d.purchase_receipt:
+				purchase_receipts.add(d.purchase_receipt)
+				rows.add(d.name)
+
+		if rows:
+			# cancel gl entries
+			gle = qb.DocType("GL Entry")
+			gle_update_query = (
+				qb.update(gle)
+				.set(gle.is_cancelled, 1)
+				.where(
+					(gle.voucher_type == "Purchase Receipt")
+					& (gle.voucher_no.isin(purchase_receipts))
+					& (gle.voucher_detail_no.isin(rows))
+				)
+			)
+			gle_update_query.run()
+
+	def update_supplier_outstanding(self, update_outstanding):
+		if update_outstanding == "No":
+			update_outstanding_amt(
+				self.credit_to,
+				"Supplier",
+				self.supplier,
+				self.doctype,
+				self.return_against if cint(self.is_return) and self.return_against else self.name,
+			)
 
 	def get_gl_entries(self, warehouse_account=None):
 		self.auto_accounting_for_stock = erpnext.is_perpetual_inventory_enabled(self.company)
@@ -829,6 +843,10 @@
 		)
 
 		if grand_total and not self.is_internal_transfer():
+			against_voucher = self.name
+			if self.is_return and self.return_against and not self.update_outstanding_for_self:
+				against_voucher = self.return_against
+
 			# Did not use base_grand_total to book rounding loss gle
 			gl_entries.append(
 				self.get_gl_dict(
@@ -842,7 +860,7 @@
 						"credit_in_account_currency": base_grand_total
 						if self.party_account_currency == self.company_currency
 						else grand_total,
-						"against_voucher": self.name,
+						"against_voucher": against_voucher,
 						"against_voucher_type": self.doctype,
 						"project": self.project,
 						"cost_center": self.cost_center,
@@ -886,8 +904,8 @@
 				"Company", self.company, "enable_provisional_accounting_for_non_stock_items"
 			)
 		)
-
-		purchase_receipt_doc_map = {}
+		if provisional_accounting_for_non_stock_items:
+			self.get_provisional_accounts()
 
 		for item in self.get("items"):
 			if flt(item.base_net_amount):
@@ -1024,44 +1042,7 @@
 					dummy, amount = self.get_amount_and_base_amount(item, None)
 
 					if provisional_accounting_for_non_stock_items:
-						if item.purchase_receipt:
-							provisional_account, pr_qty, pr_base_rate = frappe.get_cached_value(
-								"Purchase Receipt Item",
-								item.pr_detail,
-								["provisional_expense_account", "qty", "base_rate"],
-							)
-							provisional_account = provisional_account or self.get_company_default(
-								"default_provisional_account"
-							)
-							purchase_receipt_doc = purchase_receipt_doc_map.get(item.purchase_receipt)
-
-							if not purchase_receipt_doc:
-								purchase_receipt_doc = frappe.get_doc("Purchase Receipt", item.purchase_receipt)
-								purchase_receipt_doc_map[item.purchase_receipt] = purchase_receipt_doc
-
-							# Post reverse entry for Stock-Received-But-Not-Billed if it is booked in Purchase Receipt
-							expense_booked_in_pr = frappe.db.get_value(
-								"GL Entry",
-								{
-									"is_cancelled": 0,
-									"voucher_type": "Purchase Receipt",
-									"voucher_no": item.purchase_receipt,
-									"voucher_detail_no": item.pr_detail,
-									"account": provisional_account,
-								},
-								"name",
-							)
-
-							if expense_booked_in_pr:
-								# Intentionally passing purchase invoice item to handle partial billing
-								purchase_receipt_doc.add_provisional_gl_entry(
-									item,
-									gl_entries,
-									self.posting_date,
-									provisional_account,
-									reverse=1,
-									item_amount=(min(item.qty, pr_qty) * pr_base_rate),
-								)
+						self.make_provisional_gl_entry(gl_entries, item)
 
 					if not self.is_internal_transfer():
 						gl_entries.append(
@@ -1158,6 +1139,57 @@
 			if item.is_fixed_asset and item.landed_cost_voucher_amount:
 				self.update_gross_purchase_amount_for_linked_assets(item)
 
+	def get_provisional_accounts(self):
+		self.provisional_accounts = frappe._dict()
+		linked_purchase_receipts = set([d.purchase_receipt for d in self.items if d.purchase_receipt])
+		pr_items = frappe.get_all(
+			"Purchase Receipt Item",
+			filters={"parent": ("in", linked_purchase_receipts)},
+			fields=["name", "provisional_expense_account", "qty", "base_rate"],
+		)
+		default_provisional_account = self.get_company_default("default_provisional_account")
+		provisional_accounts = set(
+			[
+				d.provisional_expense_account if d.provisional_expense_account else default_provisional_account
+				for d in pr_items
+			]
+		)
+
+		provisional_gl_entries = frappe.get_all(
+			"GL Entry",
+			filters={
+				"voucher_type": "Purchase Receipt",
+				"voucher_no": ("in", linked_purchase_receipts),
+				"account": ("in", provisional_accounts),
+				"is_cancelled": 0,
+			},
+			fields=["voucher_detail_no"],
+		)
+		rows_with_provisional_entries = [d.voucher_detail_no for d in provisional_gl_entries]
+		for item in pr_items:
+			self.provisional_accounts[item.name] = {
+				"provisional_account": item.provisional_expense_account or default_provisional_account,
+				"qty": item.qty,
+				"base_rate": item.base_rate,
+				"has_provisional_entry": item.name in rows_with_provisional_entries,
+			}
+
+	def make_provisional_gl_entry(self, gl_entries, item):
+		if item.purchase_receipt:
+			pr_item = self.provisional_accounts.get(item.pr_detail, {})
+			if pr_item.get("has_provisional_entry"):
+				purchase_receipt_doc = frappe.get_cached_doc("Purchase Receipt", item.purchase_receipt)
+
+				# Intentionally passing purchase invoice item to handle partial billing
+				purchase_receipt_doc.add_provisional_gl_entry(
+					item,
+					gl_entries,
+					self.posting_date,
+					pr_item.get("provisional_account"),
+					reverse=1,
+					item_amount=(min(item.qty, pr_item.get("qty")) * pr_item.get("base_rate")),
+				)
+
 	def update_gross_purchase_amount_for_linked_assets(self, item):
 		assets = frappe.db.get_all(
 			"Asset",
diff --git a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
index 3ee4214..66df76a 100644
--- a/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
+++ b/erpnext/accounts/doctype/purchase_invoice_item/purchase_invoice_item.json
@@ -745,6 +745,7 @@
    "fieldtype": "Currency",
    "label": "Landed Cost Voucher Amount",
    "no_copy": 1,
+   "options": "Company:company:default_currency",
    "print_hide": 1,
    "read_only": 1
   },
@@ -938,7 +939,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2024-02-04 14:11:52.742228",
+ "modified": "2024-03-19 19:09:47.210965",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Purchase Invoice Item",
diff --git a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
index 66a9cbe..4c94503 100644
--- a/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/purchase_taxes_and_charges_template/purchase_taxes_and_charges_template.js
@@ -1,6 +1,7 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
+cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
 erpnext.accounts.taxes.setup_tax_validations("Purchase Taxes and Charges Template");
 erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
 
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index 17101cd..c7505ce 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -3,6 +3,8 @@
 
 frappe.provide("erpnext.accounts");
 
+cur_frm.cscript.tax_table = "Sales Taxes and Charges";
+
 erpnext.accounts.taxes.setup_tax_validations("Sales Invoice");
 erpnext.accounts.payment_triggers.setup("Sales Invoice");
 erpnext.accounts.pos.setup("Sales Invoice");
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
index 88b28ad..1d8983e 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.json
@@ -25,6 +25,7 @@
   "is_consolidated",
   "is_return",
   "return_against",
+  "update_outstanding_for_self",
   "update_billed_amount_in_sales_order",
   "update_billed_amount_in_delivery_note",
   "is_debit_note",
@@ -945,7 +946,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "hide_days": 1,
    "hide_seconds": 1,
    "label": "Taxes and Charges Calculation",
@@ -2171,6 +2172,15 @@
    "fieldtype": "Check",
    "label": "Don't Create Loyalty Points",
    "no_copy": 1
+  },
+  {
+   "default": "1",
+   "depends_on": "eval: doc.is_return && doc.return_against",
+   "description": "Credit Note will update it's own outstanding amount, even if \"Return Against\" is specified.",
+   "fieldname": "update_outstanding_for_self",
+   "fieldtype": "Check",
+   "label": "Update Outstanding for Self",
+   "no_copy": 1
   }
  ],
  "icon": "fa fa-file-text",
@@ -2183,7 +2193,7 @@
    "link_fieldname": "consolidated_invoice"
   }
  ],
- "modified": "2024-03-01 09:21:54.201289",
+ "modified": "2024-03-20 16:02:52.237732",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Sales Invoice",
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index e2cbf5e..1228bbb 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -146,7 +146,7 @@
 		naming_series: DF.Literal["ACC-SINV-.YYYY.-", "ACC-SINV-RET-.YYYY.-"]
 		net_total: DF.Currency
 		only_include_allocated_payments: DF.Check
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		outstanding_amount: DF.Currency
 		packed_items: DF.Table[PackedItem]
 		paid_amount: DF.Currency
@@ -220,6 +220,7 @@
 		unrealized_profit_loss_account: DF.Link | None
 		update_billed_amount_in_delivery_note: DF.Check
 		update_billed_amount_in_sales_order: DF.Check
+		update_outstanding_for_self: DF.Check
 		update_stock: DF.Check
 		use_company_roundoff_cost_center: DF.Check
 		write_off_account: DF.Link | None
@@ -1219,6 +1220,10 @@
 		)
 
 		if grand_total and not self.is_internal_transfer():
+			against_voucher = self.name
+			if self.is_return and self.return_against and not self.update_outstanding_for_self:
+				against_voucher = self.return_against
+
 			# Did not use base_grand_total to book rounding loss gle
 			gl_entries.append(
 				self.get_gl_dict(
@@ -1232,7 +1237,7 @@
 						"debit_in_account_currency": base_grand_total
 						if self.party_account_currency == self.company_currency
 						else grand_total,
-						"against_voucher": self.name,
+						"against_voucher": against_voucher,
 						"against_voucher_type": self.doctype,
 						"cost_center": self.cost_center,
 						"project": self.project,
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index c8a35eb..7e3eec5 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1588,6 +1588,12 @@
 		self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
 		self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
 
+	def test_zero_qty_return_invoice_with_stock_effect(self):
+		cr_note = create_sales_invoice(qty=-1, rate=300, is_return=1, do_not_submit=True)
+		cr_note.update_stock = True
+		cr_note.items[0].qty = 0
+		self.assertRaises(frappe.ValidationError, cr_note.save)
+
 	def test_return_invoice_with_account_mismatch(self):
 		debtors2 = create_account(
 			parent_account="Accounts Receivable - _TC",
@@ -3945,7 +3951,6 @@
 		)
 
 		supplier.append("companies", {"company": allowed_to_interact_with})
-
 		supplier.insert()
 		supplier_name = supplier.name
 	else:
diff --git a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
index 91d4d04..c42623a 100644
--- a/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
+++ b/erpnext/accounts/doctype/sales_taxes_and_charges_template/sales_taxes_and_charges_template.js
@@ -1,5 +1,6 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
+cur_frm.cscript.tax_table = "Sales Taxes and Charges";
 erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template");
 erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
diff --git a/erpnext/accounts/doctype/subscription/subscription.py b/erpnext/accounts/doctype/subscription/subscription.py
index 9f19366..1a79103 100644
--- a/erpnext/accounts/doctype/subscription/subscription.py
+++ b/erpnext/accounts/doctype/subscription/subscription.py
@@ -408,11 +408,11 @@
 		# Earlier subscription didn't had any company field
 		company = self.get("company") or get_default_company()
 		if not company:
-			# fmt: off
 			frappe.throw(
-				_("Company is mandatory was generating invoice. Please set default company in Global Defaults.")
+				_(
+					"Company is mandatory for generating an invoice. Please set a default company in Global Defaults."
+				)
 			)
-			# fmt: on
 
 		invoice = frappe.new_doc(self.invoice_document_type)
 		invoice.company = company
diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py b/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/accounts/doctype/transaction_deletion_record_details/__init__.py
diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json
new file mode 100644
index 0000000..fe4b085
--- /dev/null
+++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.json
@@ -0,0 +1,58 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2024-02-04 10:53:32.307930",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "doctype_name",
+  "docfield_name",
+  "no_of_docs",
+  "done"
+ ],
+ "fields": [
+  {
+   "fieldname": "doctype_name",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "DocType",
+   "options": "DocType",
+   "read_only": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "docfield_name",
+   "fieldtype": "Data",
+   "label": "DocField",
+   "read_only": 1
+  },
+  {
+   "fieldname": "no_of_docs",
+   "fieldtype": "Int",
+   "in_list_view": 1,
+   "label": "No of Docs",
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "done",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "label": "Done",
+   "read_only": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2024-02-05 17:35:09.556054",
+ "modified_by": "Administrator",
+ "module": "Accounts",
+ "name": "Transaction Deletion Record Details",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py
new file mode 100644
index 0000000..bc5b5c4
--- /dev/null
+++ b/erpnext/accounts/doctype/transaction_deletion_record_details/transaction_deletion_record_details.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2024, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class TransactionDeletionRecordDetails(Document):
+	# begin: auto-generated types
+	# This code is auto-generated. Do not modify anything in this block.
+
+	from typing import TYPE_CHECKING
+
+	if TYPE_CHECKING:
+		from frappe.types import DF
+
+		docfield_name: DF.Data | None
+		doctype_name: DF.Link
+		done: DF.Check
+		no_of_docs: DF.Int
+		parent: DF.Data
+		parentfield: DF.Data
+		parenttype: DF.Data
+	# end: auto-generated types
+
+	pass
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 2e82886..825a01e 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -7,7 +7,7 @@
 import frappe
 from frappe import _
 from frappe.model.meta import get_field_precision
-from frappe.utils import cint, cstr, flt, formatdate, getdate, now
+from frappe.utils import cint, flt, formatdate, getdate, now
 
 import erpnext
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -234,11 +234,13 @@
 def merge_similar_entries(gl_map, precision=None):
 	merged_gl_map = []
 	accounting_dimensions = get_accounting_dimensions()
+	merge_properties = get_merge_properties(accounting_dimensions)
 
 	for entry in gl_map:
+		entry.merge_key = get_merge_key(entry, merge_properties)
 		# if there is already an entry in this account then just add it
 		# to that entry
-		same_head = check_if_in_list(entry, merged_gl_map, accounting_dimensions)
+		same_head = check_if_in_list(entry, merged_gl_map)
 		if same_head:
 			same_head.debit = flt(same_head.debit) + flt(entry.debit)
 			same_head.debit_in_account_currency = flt(same_head.debit_in_account_currency) + flt(
@@ -273,34 +275,35 @@
 	return merged_gl_map
 
 
-def check_if_in_list(gle, gl_map, dimensions=None):
-	account_head_fieldnames = [
-		"voucher_detail_no",
-		"party",
-		"against_voucher",
+def get_merge_properties(dimensions=None):
+	merge_properties = [
+		"account",
 		"cost_center",
-		"against_voucher_type",
+		"party",
 		"party_type",
+		"voucher_detail_no",
+		"against_voucher",
+		"against_voucher_type",
 		"project",
 		"finance_book",
 		"voucher_no",
 	]
-
 	if dimensions:
-		account_head_fieldnames = account_head_fieldnames + dimensions
+		merge_properties.extend(dimensions)
+	return merge_properties
 
+
+def get_merge_key(entry, merge_properties):
+	merge_key = []
+	for fieldname in merge_properties:
+		merge_key.append(entry.get(fieldname, ""))
+
+	return tuple(merge_key)
+
+
+def check_if_in_list(gle, gl_map):
 	for e in gl_map:
-		same_head = True
-		if e.account != gle.account:
-			same_head = False
-			continue
-
-		for fieldname in account_head_fieldnames:
-			if cstr(e.get(fieldname)) != cstr(gle.get(fieldname)):
-				same_head = False
-				break
-
-		if same_head:
+		if e.merge_key == gle.merge_key:
 			return e
 
 
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 6d77ef5..38723e9 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -690,7 +690,12 @@
 
 	def get_return_entries(self):
 		doctype = "Sales Invoice" if self.account_type == "Receivable" else "Purchase Invoice"
-		filters = {"is_return": 1, "docstatus": 1, "company": self.filters.company}
+		filters = {
+			"is_return": 1,
+			"docstatus": 1,
+			"company": self.filters.company,
+			"update_outstanding_for_self": 0,
+		}
 		or_filters = {}
 		for party_type in self.party_type:
 			party_field = scrub(party_type)
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index 6ff81be..de49139 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -62,7 +62,7 @@
 		pe.insert()
 		pe.submit()
 
-	def create_credit_note(self, docname):
+	def create_credit_note(self, docname, do_not_submit=False):
 		credit_note = create_sales_invoice(
 			company=self.company,
 			customer=self.customer,
@@ -72,6 +72,7 @@
 			cost_center=self.cost_center,
 			is_return=1,
 			return_against=docname,
+			do_not_submit=do_not_submit,
 		)
 
 		return credit_note
@@ -149,7 +150,9 @@
 			)
 
 		# check invoice grand total, invoiced, paid and outstanding column's value after credit note
-		self.create_credit_note(si.name)
+		cr_note = self.create_credit_note(si.name, do_not_submit=True)
+		cr_note.update_outstanding_for_self = False
+		cr_note.save().submit()
 		report = execute(filters)
 
 		expected_data_after_credit_note = [100, 0, 0, 40, -40, self.debit_to]
@@ -167,6 +170,82 @@
 			],
 		)
 
+	def test_cr_note_flag_to_update_self(self):
+		filters = {
+			"company": self.company,
+			"report_date": today(),
+			"range1": 30,
+			"range2": 60,
+			"range3": 90,
+			"range4": 120,
+			"show_remarks": True,
+		}
+
+		# check invoice grand total and invoiced column's value for 3 payment terms
+		si = self.create_sales_invoice(no_payment_schedule=True, do_not_submit=True)
+		si.set_posting_time = True
+		si.posting_date = add_days(today(), -1)
+		si.save().submit()
+
+		report = execute(filters)
+
+		expected_data = [100, 100, "No Remarks"]
+
+		self.assertEqual(len(report[1]), 1)
+		row = report[1][0]
+		self.assertEqual(expected_data, [row.invoice_grand_total, row.invoiced, row.remarks])
+
+		# check invoice grand total, invoiced, paid and outstanding column's value after payment
+		self.create_payment_entry(si.name)
+		report = execute(filters)
+
+		expected_data_after_payment = [100, 100, 40, 60]
+		self.assertEqual(len(report[1]), 1)
+		row = report[1][0]
+		self.assertEqual(
+			expected_data_after_payment,
+			[row.invoice_grand_total, row.invoiced, row.paid, row.outstanding],
+		)
+
+		# check invoice grand total, invoiced, paid and outstanding column's value after credit note
+		cr_note = self.create_credit_note(si.name, do_not_submit=True)
+		cr_note.update_outstanding_for_self = True
+		cr_note.save().submit()
+		report = execute(filters)
+
+		expected_data_after_credit_note = [
+			[100.0, 100.0, 40.0, 0.0, 60.0, si.name],
+			[0, 0, 100.0, 0.0, -100.0, cr_note.name],
+		]
+		self.assertEqual(len(report[1]), 2)
+		si_row = [
+			[
+				row.invoice_grand_total,
+				row.invoiced,
+				row.paid,
+				row.credit_note,
+				row.outstanding,
+				row.voucher_no,
+			]
+			for row in report[1]
+			if row.voucher_no == si.name
+		][0]
+
+		cr_note_row = [
+			[
+				row.invoice_grand_total,
+				row.invoiced,
+				row.paid,
+				row.credit_note,
+				row.outstanding,
+				row.voucher_no,
+			]
+			for row in report[1]
+			if row.voucher_no == cr_note.name
+		][0]
+		self.assertEqual(expected_data_after_credit_note[0], si_row)
+		self.assertEqual(expected_data_after_credit_note[1], cr_note_row)
+
 	def test_payment_againt_po_in_receivable_report(self):
 		"""
 		Payments made against Purchase Order will show up as outstanding amount
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 0755f2e..02012ad 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -1027,7 +1027,7 @@
 
 	if account:
 		root_type, account_type = frappe.get_cached_value(
-			"Account", account, ["root_type", "account_type"]
+			"Account", account[0], ["root_type", "account_type"]
 		)
 		party_account_type = "Receivable" if root_type == "Asset" else "Payable"
 		party_account_type = account_type or party_account_type
@@ -1038,7 +1038,7 @@
 
 	common_filter = common_filter or []
 	common_filter.append(ple.account_type == party_account_type)
-	common_filter.append(ple.account == account)
+	common_filter.append(ple.account.isin(account))
 	common_filter.append(ple.party_type == party_type)
 	common_filter.append(ple.party == party)
 
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 6dbb53a..0f71e5d 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -79,7 +79,7 @@
 		frm.toggle_display("next_depreciation_date", frm.doc.docstatus < 1);
 
 		if (frm.doc.docstatus == 1) {
-			if (in_list(["Submitted", "Partially Depreciated", "Fully Depreciated"], frm.doc.status)) {
+			if (["Submitted", "Partially Depreciated", "Fully Depreciated"].includes(frm.doc.status)) {
 				frm.add_custom_button(
 					__("Transfer Asset"),
 					function () {
@@ -365,7 +365,7 @@
 				if (v.journal_entry) {
 					asset_values.push(asset_value);
 				} else {
-					if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
+					if (["Scrapped", "Sold"].includes(frm.doc.status)) {
 						asset_values.push(null);
 					} else {
 						asset_values.push(asset_value);
@@ -400,7 +400,7 @@
 			});
 		}
 
-		if (in_list(["Scrapped", "Sold"], frm.doc.status)) {
+		if (["Scrapped", "Sold"].includes(frm.doc.status)) {
 			x_intervals.push(frappe.format(frm.doc.disposal_date, { fieldtype: "Date" }));
 			asset_values.push(0);
 		}
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 166e8c4..385797f 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -152,6 +152,7 @@
 	def on_submit(self):
 		self.validate_in_use_date()
 		self.make_asset_movement()
+		self.reload()
 		if not self.booked_fixed_asset and self.validate_make_gl_entry():
 			self.make_gl_entries()
 		if self.calculate_depreciation and not self.split_from:
@@ -163,6 +164,7 @@
 		self.validate_cancellation()
 		self.cancel_movement_entries()
 		self.cancel_capitalization()
+		self.reload()
 		self.delete_depreciation_entries()
 		cancel_asset_depr_schedules(self)
 		self.set_status()
@@ -698,7 +700,9 @@
 		fixed_asset_account, cwip_account = self.get_fixed_asset_account(), self.get_cwip_account()
 
 		if (
-			purchase_document and self.purchase_receipt_amount and self.available_for_use_date <= nowdate()
+			purchase_document
+			and self.purchase_receipt_amount
+			and getdate(self.available_for_use_date) <= getdate()
 		):
 
 			gl_entries.append(
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index 191675c..205f4b9 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -242,9 +242,7 @@
 				debit_account,
 				accounting_dimensions,
 			)
-			frappe.db.commit()
 		except Exception as e:
-			frappe.db.rollback()
 			depreciation_posting_error = e
 
 	asset.set_status()
@@ -523,6 +521,7 @@
 
 	make_depreciation_entry_for_all_asset_depr_schedules(asset_doc, date)
 
+	asset_doc.reload()
 	cancel_depreciation_entries(asset_doc, date)
 
 
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 77469df..6e16508 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -327,7 +327,7 @@
 					schedule_date = get_last_day(schedule_date)
 
 			# if asset is being sold or scrapped
-			if date_of_disposal:
+			if date_of_disposal and getdate(schedule_date) >= getdate(date_of_disposal):
 				from_date = add_months(
 					getdate(asset_doc.available_for_use_date),
 					(asset_doc.number_of_depreciations_booked * row.frequency_of_depreciation),
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 7875646..cf38302 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -4,6 +4,8 @@
 frappe.provide("erpnext.buying");
 frappe.provide("erpnext.accounts.dimensions");
 
+cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
+
 erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
 erpnext.accounts.taxes.setup_tax_validations("Purchase Order");
 erpnext.buying.setup_buying_controller();
@@ -289,7 +291,7 @@
 				this.frm.fields_dict.items_section.wrapper.removeClass("hide-border");
 			}
 
-			if (!in_list(["Closed", "Delivered"], doc.status)) {
+			if (!["Closed", "Delivered"].includes(doc.status)) {
 				if (
 					this.frm.doc.status !== "Closed" &&
 					flt(this.frm.doc.per_received, 2) < 100 &&
@@ -334,7 +336,7 @@
 
 					this.frm.page.set_inner_btn_group_as_primary(__("Status"));
 				}
-			} else if (in_list(["Closed", "Delivered"], doc.status)) {
+			} else if (["Closed", "Delivered"].includes(doc.status)) {
 				if (this.frm.has_perm("submit")) {
 					this.frm.add_custom_button(
 						__("Re-open"),
@@ -507,7 +509,6 @@
 					target: me.frm,
 					setters: {
 						schedule_date: undefined,
-						status: undefined,
 					},
 					get_query_filters: {
 						material_request_type: "Purchase",
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.json b/erpnext/buying/doctype/purchase_order/purchase_order.json
index 9da49a7..0916ff5 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.json
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.json
@@ -642,7 +642,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "label": "Taxes and Charges Calculation",
    "no_copy": 1,
    "oldfieldtype": "HTML",
@@ -1288,7 +1288,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-10-10 13:37:40.158761",
+ "modified": "2024-03-20 16:03:31.611808",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order",
@@ -1343,4 +1343,4 @@
  "timeline_field": "supplier",
  "title_field": "supplier_name",
  "track_changes": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 4d94868..4f24ec2 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -57,6 +57,7 @@
 		additional_discount_percentage: DF.Float
 		address_display: DF.SmallText | None
 		advance_paid: DF.Currency
+		advance_payment_status: DF.Literal["Not Initiated", "Initiated", "Partially Paid", "Fully Paid"]
 		amended_from: DF.Link | None
 		apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
 		apply_tds: DF.Check
@@ -109,7 +110,7 @@
 		net_total: DF.Currency
 		order_confirmation_date: DF.Date | None
 		order_confirmation_no: DF.Data | None
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		party_account_currency: DF.Link | None
 		payment_schedule: DF.Table[PaymentSchedule]
 		payment_terms_template: DF.Link | None
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
index 272d077..6b10df8 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.js
@@ -518,16 +518,15 @@
 						callback: load_suppliers,
 					});
 				} else if (args.supplier_group) {
-					return frappe.call({
-						method: "frappe.client.get_list",
-						args: {
-							doctype: "Supplier",
+					frappe.db
+						.get_list("Supplier", {
+							filters: { supplier_group: args.supplier_group },
+							limit: 100,
 							order_by: "name",
-							fields: ["name"],
-							filters: [["Supplier", "supplier_group", "=", args.supplier_group]],
-						},
-						callback: load_suppliers,
-					});
+						})
+						.then((r) => {
+							load_suppliers({ message: r });
+						});
 				}
 			},
 		});
diff --git a/erpnext/buying/doctype/supplier/supplier.json b/erpnext/buying/doctype/supplier/supplier.json
index 60dd54c..3dae044 100644
--- a/erpnext/buying/doctype/supplier/supplier.json
+++ b/erpnext/buying/doctype/supplier/supplier.json
@@ -485,7 +485,7 @@
    "link_fieldname": "party"
   }
  ],
- "modified": "2023-10-19 16:55:15.148325",
+ "modified": "2024-03-13 11:14:06.516519",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Supplier",
@@ -544,7 +544,7 @@
   }
  ],
  "quick_entry": 1,
- "search_fields": "supplier_name, supplier_group",
+ "search_fields": "supplier_group",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "ASC",
diff --git a/erpnext/buying/doctype/supplier/test_supplier.py b/erpnext/buying/doctype/supplier/test_supplier.py
index 350a25f..55974ea 100644
--- a/erpnext/buying/doctype/supplier/test_supplier.py
+++ b/erpnext/buying/doctype/supplier/test_supplier.py
@@ -154,44 +154,6 @@
 		# Rollback
 		address.delete()
 
-	def test_serach_fields_for_supplier(self):
-		from erpnext.controllers.queries import supplier_query
-
-		frappe.db.set_single_value("Buying Settings", "supp_master_name", "Naming Series")
-
-		supplier_name = create_supplier(supplier_name="Test Supplier 1").name
-
-		make_property_setter(
-			"Supplier", None, "search_fields", "supplier_group", "Data", for_doctype="Doctype"
-		)
-
-		data = supplier_query(
-			"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
-		)
-
-		self.assertEqual(data[0].name, supplier_name)
-		self.assertEqual(data[0].supplier_group, "Services")
-		self.assertTrue("supplier_type" not in data[0])
-
-		make_property_setter(
-			"Supplier",
-			None,
-			"search_fields",
-			"supplier_group, supplier_type",
-			"Data",
-			for_doctype="Doctype",
-		)
-		data = supplier_query(
-			"Supplier", supplier_name, "name", 0, 20, filters={"name": supplier_name}, as_dict=True
-		)
-
-		self.assertEqual(data[0].name, supplier_name)
-		self.assertEqual(data[0].supplier_group, "Services")
-		self.assertEqual(data[0].supplier_type, "Company")
-		self.assertTrue("supplier_type" in data[0])
-
-		frappe.db.set_single_value("Buying Settings", "supp_master_name", "Supplier Name")
-
 
 def create_supplier(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
index 1891261..09be247 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.json
@@ -462,7 +462,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Markdown Editor",
    "label": "Taxes and Charges Calculation",
    "no_copy": 1,
    "oldfieldtype": "HTML",
@@ -928,7 +928,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-11-17 12:34:30.083077",
+ "modified": "2024-03-20 16:03:59.069145",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Supplier Quotation",
diff --git a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
index e2b737b..b716f7f 100644
--- a/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
+++ b/erpnext/buying/doctype/supplier_quotation/supplier_quotation.py
@@ -71,7 +71,7 @@
 		naming_series: DF.Literal["PUR-SQTN-.YYYY.-"]
 		net_total: DF.Currency
 		opportunity: DF.Link | None
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.MarkdownEditor | None
 		plc_conversion_rate: DF.Float
 		price_list_currency: DF.Link | None
 		pricing_rules: DF.Table[PricingRuleDetail]
diff --git a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
index c109abd..f7d0d94 100644
--- a/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
+++ b/erpnext/buying/report/supplier_quotation_comparison/supplier_quotation_comparison.js
@@ -77,7 +77,10 @@
 			fieldname: "group_by",
 			label: __("Group by"),
 			fieldtype: "Select",
-			options: [__("Group by Supplier"), __("Group by Item")],
+			options: [
+				{ label: __("Group by Supplier"), value: "Group by Supplier" },
+				{ label: __("Group by Item"), value: "Group by Item" },
+			],
 			default: __("Group by Supplier"),
 		},
 		{
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index c543dfc..8bdee22 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -89,6 +89,7 @@
 	"weight_per_unit",
 	"weight_uom",
 	"total_weight",
+	"valuation_rate",
 )
 
 
@@ -168,6 +169,13 @@
 		if not self.get("is_return") and not self.get("is_debit_note"):
 			self.validate_qty_is_not_zero()
 
+		if (
+			self.doctype in ["Sales Invoice", "Purchase Invoice"]
+			and self.get("is_return")
+			and self.get("update_stock")
+		):
+			self.validate_zero_qty_for_return_invoices_with_stock()
+
 		if self.get("_action") and self._action != "update_after_submit":
 			self.set_missing_values(for_validate=True)
 
@@ -218,17 +226,18 @@
 				)
 
 			if self.get("is_return") and self.get("return_against") and not self.get("is_pos"):
-				# if self.get("is_return") and self.get("return_against"):
-				document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
-				frappe.msgprint(
-					_(
-						"{0} will be treated as a standalone {0}. Post creation use {1} tool to reconcile against {2}."
-					).format(
-						document_type,
-						get_link_to_form("Payment Reconciliation"),
-						get_link_to_form(self.doctype, self.get("return_against")),
+				if self.get("update_outstanding_for_self"):
+					document_type = "Credit Note" if self.doctype == "Sales Invoice" else "Debit Note"
+					frappe.msgprint(
+						_(
+							"We can see {0} is made against {1}. If you want {1}'s outstanding to be updated, uncheck '{2}' checkbox. <br><br> Or you can use {3} tool to reconcile against {1} later."
+						).format(
+							frappe.bold(document_type),
+							get_link_to_form(self.doctype, self.get("return_against")),
+							frappe.bold("Update Outstanding for Self"),
+							get_link_to_form("Payment Reconciliation"),
+						)
 					)
-				)
 
 			pos_check_field = "is_pos" if self.doctype == "Sales Invoice" else "is_paid"
 			if cint(self.allocate_advances_automatically) and not cint(self.get(pos_check_field)):
@@ -601,23 +610,31 @@
 				)
 
 	def validate_due_date(self):
-		if self.get("is_pos"):
+		if self.get("is_pos") or self.doctype not in ["Sales Invoice", "Purchase Invoice"]:
 			return
 
 		from erpnext.accounts.party import validate_due_date
 
-		if self.doctype == "Sales Invoice":
+		posting_date = (
+			self.posting_date if self.doctype == "Sales Invoice" else (self.bill_date or self.posting_date)
+		)
+
+		# skip due date validation for records via Data Import
+		if frappe.flags.in_import and getdate(self.due_date) < getdate(posting_date):
+			self.due_date = posting_date
+
+		elif self.doctype == "Sales Invoice":
 			if not self.due_date:
 				frappe.throw(_("Due Date is mandatory"))
 
 			validate_due_date(
-				self.posting_date,
+				posting_date,
 				self.due_date,
 				self.payment_terms_template,
 			)
 		elif self.doctype == "Purchase Invoice":
 			validate_due_date(
-				self.bill_date or self.posting_date,
+				posting_date,
 				self.due_date,
 				self.bill_date,
 				self.payment_terms_template,
@@ -1043,6 +1060,18 @@
 		else:
 			return flt(args.get(field, 0) / self.get("conversion_rate", 1))
 
+	def validate_zero_qty_for_return_invoices_with_stock(self):
+		rows = []
+		for item in self.items:
+			if not flt(item.qty):
+				rows.append(item)
+		if rows:
+			frappe.throw(
+				_(
+					"For Return Invoices with Stock effect, '0' qty Items are not allowed. Following rows are affected: {0}"
+				).format(frappe.bold(comma_and(["#" + str(x.idx) for x in rows])))
+			)
+
 	def validate_qty_is_not_zero(self):
 		for item in self.items:
 			if self.doctype == "Purchase Receipt" and item.rejected_qty:
@@ -1710,8 +1739,8 @@
 		item_allowance = {}
 		global_qty_allowance, global_amount_allowance = None, None
 
-		role_allowed_to_over_bill = frappe.db.get_single_value(
-			"Accounts Settings", "role_allowed_to_over_bill"
+		role_allowed_to_over_bill = frappe.get_cached_value(
+			"Accounts Settings", None, "role_allowed_to_over_bill"
 		)
 		user_roles = frappe.get_roles()
 
@@ -2707,14 +2736,20 @@
 	else:
 		q = q.where(journal_acc.debit_in_account_currency > 0)
 
+	reference_or_condition = []
+
 	if include_unallocated:
-		q = q.where((journal_acc.reference_name.isnull()) | (journal_acc.reference_name == ""))
+		reference_or_condition.append(journal_acc.reference_name.isnull())
+		reference_or_condition.append(journal_acc.reference_name == "")
 
 	if order_list:
-		q = q.where(
+		reference_or_condition.append(
 			(journal_acc.reference_type == order_doctype) & ((journal_acc.reference_name).isin(order_list))
 		)
 
+	if reference_or_condition:
+		q = q.where(Criterion.any(reference_or_condition))
+
 	q = q.orderby(journal_entry.posting_date)
 
 	journal_entries = q.run(as_dict=True)
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 8211857..c530727 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -513,6 +513,14 @@
 						(not cint(self.is_return) and self.docstatus == 1)
 						or (cint(self.is_return) and self.docstatus == 2)
 					):
+						serial_and_batch_bundle = d.get("serial_and_batch_bundle")
+						if self.is_internal_transfer() and self.is_return and self.docstatus == 2:
+							serial_and_batch_bundle = frappe.db.get_value(
+								"Stock Ledger Entry",
+								{"voucher_detail_no": d.name, "warehouse": d.from_warehouse},
+								"serial_and_batch_bundle",
+							)
+
 						from_warehouse_sle = self.get_sl_entries(
 							d,
 							{
@@ -521,19 +529,24 @@
 								"outgoing_rate": d.rate,
 								"recalculate_rate": 1,
 								"dependant_sle_voucher_detail_no": d.name,
+								"serial_and_batch_bundle": serial_and_batch_bundle,
 							},
 						)
 
 						sl_entries.append(from_warehouse_sle)
 
+					type_of_transaction = "Inward"
+					if self.docstatus == 2:
+						type_of_transaction = "Outward"
+
 					sle = self.get_sl_entries(
 						d,
 						{
 							"actual_qty": flt(pr_qty),
 							"serial_and_batch_bundle": (
 								d.serial_and_batch_bundle
-								if not self.is_internal_transfer()
-								else self.get_package_for_target_warehouse(d)
+								if not self.is_internal_transfer() or self.is_return
+								else self.get_package_for_target_warehouse(d, type_of_transaction=type_of_transaction)
 							),
 						},
 					)
@@ -570,7 +583,17 @@
 						or (cint(self.is_return) and self.docstatus == 1)
 					):
 						from_warehouse_sle = self.get_sl_entries(
-							d, {"actual_qty": -1 * pr_qty, "warehouse": d.from_warehouse, "recalculate_rate": 1}
+							d,
+							{
+								"actual_qty": -1 * pr_qty,
+								"warehouse": d.from_warehouse,
+								"recalculate_rate": 1,
+								"serial_and_batch_bundle": (
+									self.get_package_for_target_warehouse(d, d.from_warehouse, "Inward")
+									if self.is_internal_transfer() and self.is_return
+									else None
+								),
+							},
 						)
 
 						sl_entries.append(from_warehouse_sle)
@@ -597,13 +620,15 @@
 			via_landed_cost_voucher=via_landed_cost_voucher,
 		)
 
-	def get_package_for_target_warehouse(self, item) -> str:
+	def get_package_for_target_warehouse(self, item, warehouse=None, type_of_transaction=None) -> str:
 		if not item.serial_and_batch_bundle:
 			return ""
 
+		if not warehouse:
+			warehouse = item.warehouse
+
 		return self.make_package_for_transfer(
-			item.serial_and_batch_bundle,
-			item.warehouse,
+			item.serial_and_batch_bundle, warehouse, type_of_transaction=type_of_transaction
 		)
 
 	def update_ordered_and_reserved_qty(self):
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 0d64188..0de75d4 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -85,79 +85,6 @@
 		{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
 	)
 
-	# searches for customer
-
-
-@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def customer_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
-	doctype = "Customer"
-	conditions = []
-	cust_master_name = frappe.defaults.get_user_default("cust_master_name")
-
-	fields = ["name"]
-	if cust_master_name != "Customer Name":
-		fields.append("customer_name")
-
-	fields = get_fields(doctype, fields)
-	searchfields = frappe.get_meta(doctype).get_search_fields()
-	searchfields = " or ".join(field + " like %(txt)s" for field in searchfields)
-
-	return frappe.db.sql(
-		"""select {fields} from `tabCustomer`
-		where docstatus < 2
-			and ({scond}) and disabled=0
-			{fcond} {mcond}
-		order by
-			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
-			(case when locate(%(_txt)s, customer_name) > 0 then locate(%(_txt)s, customer_name) else 99999 end),
-			idx desc,
-			name, customer_name
-		limit %(page_len)s offset %(start)s""".format(
-			**{
-				"fields": ", ".join(fields),
-				"scond": searchfields,
-				"mcond": get_match_cond(doctype),
-				"fcond": get_filters_cond(doctype, filters, conditions).replace("%", "%%"),
-			}
-		),
-		{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
-		as_dict=as_dict,
-	)
-
-
-# searches for supplier
-@frappe.whitelist()
-@frappe.validate_and_sanitize_search_inputs
-def supplier_query(doctype, txt, searchfield, start, page_len, filters, as_dict=False):
-	doctype = "Supplier"
-	supp_master_name = frappe.defaults.get_user_default("supp_master_name")
-
-	fields = ["name"]
-	if supp_master_name != "Supplier Name":
-		fields.append("supplier_name")
-
-	fields = get_fields(doctype, fields)
-
-	return frappe.db.sql(
-		"""select {field} from `tabSupplier`
-		where docstatus < 2
-			and ({key} like %(txt)s
-			or supplier_name like %(txt)s) and disabled=0
-			and (on_hold = 0 or (on_hold = 1 and CURRENT_DATE > release_date))
-			{mcond}
-		order by
-			(case when locate(%(_txt)s, name) > 0 then locate(%(_txt)s, name) else 99999 end),
-			(case when locate(%(_txt)s, supplier_name) > 0 then locate(%(_txt)s, supplier_name) else 99999 end),
-			idx desc,
-			name, supplier_name
-		limit %(page_len)s offset %(start)s""".format(
-			**{"field": ", ".join(fields), "key": searchfield, "mcond": get_match_cond(doctype)}
-		),
-		{"txt": "%%%s%%" % txt, "_txt": txt.replace("%", ""), "start": start, "page_len": page_len},
-		as_dict=as_dict,
-	)
-
 
 @frappe.whitelist()
 @frappe.validate_and_sanitize_search_inputs
@@ -670,7 +597,7 @@
 	searchfields = frappe.get_meta(doctype).get_search_fields()
 
 	meta = frappe.get_meta(doctype)
-	if meta.is_tree:
+	if meta.is_tree and meta.has_field("is_group"):
 		query_filters.append(["is_group", "=", 0])
 
 	if meta.has_field("disabled"):
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 1ddcaa7..5594816 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -423,6 +423,15 @@
 			]:
 				type_of_transaction = "Outward"
 
+			warehouse = source_doc.warehouse if qty_field == "stock_qty" else source_doc.rejected_warehouse
+			if source_parent.doctype in [
+				"Sales Invoice",
+				"POS Invoice",
+				"Delivery Note",
+			] and source_parent.get("is_internal_customer"):
+				type_of_transaction = "Outward"
+				warehouse = source_doc.target_warehouse
+
 			cls_obj = SerialBatchCreation(
 				{
 					"type_of_transaction": type_of_transaction,
@@ -432,7 +441,7 @@
 					"returned_serial_nos": returned_serial_nos,
 					"voucher_type": source_parent.doctype,
 					"do_not_submit": True,
-					"warehouse": source_doc.warehouse,
+					"warehouse": warehouse,
 					"has_serial_no": item_details.has_serial_no,
 					"has_batch_no": item_details.has_batch_no,
 				}
@@ -575,11 +584,14 @@
 			if not item_details.has_batch_no and not item_details.has_serial_no:
 				return
 
-			for qty_field in ["stock_qty", "rejected_qty"]:
-				if target_doc.get(qty_field) and not target_doc.get("use_serial_batch_fields"):
+			if not target_doc.get("use_serial_batch_fields"):
+				for qty_field in ["stock_qty", "rejected_qty"]:
+					if not target_doc.get(qty_field):
+						continue
+
 					update_serial_batch_no(source_doc, target_doc, source_parent, item_details, qty_field)
-				elif target_doc.get(qty_field) and target_doc.get("use_serial_batch_fields"):
-					update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
+			elif target_doc.get("use_serial_batch_fields"):
+				update_non_bundled_serial_nos(source_doc, target_doc, source_parent)
 
 	def update_non_bundled_serial_nos(source_doc, target_doc, source_parent):
 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 359d721..9d86cb2 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -439,8 +439,10 @@
 				# Get incoming rate based on original item cost based on valuation method
 				qty = flt(d.get("stock_qty") or d.get("actual_qty"))
 
-				if not d.incoming_rate or (
-					get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return")
+				if (
+					not d.incoming_rate
+					or self.is_internal_transfer()
+					or (get_valuation_method(d.item_code) == "Moving Average" and self.get("is_return"))
 				):
 					d.incoming_rate = get_incoming_rate(
 						{
@@ -455,6 +457,8 @@
 							"voucher_no": self.name,
 							"voucher_detail_no": d.name,
 							"allow_zero_valuation": d.get("allow_zero_valuation"),
+							"batch_no": d.batch_no,
+							"serial_no": d.serial_no,
 						},
 						raise_error_if_no_rate=False,
 					)
@@ -527,13 +531,26 @@
 		self.make_sl_entries(sl_entries)
 
 	def get_sle_for_source_warehouse(self, item_row):
+		serial_and_batch_bundle = item_row.serial_and_batch_bundle
+		if serial_and_batch_bundle and self.is_internal_transfer() and self.is_return:
+			if self.docstatus == 1:
+				serial_and_batch_bundle = self.make_package_for_transfer(
+					serial_and_batch_bundle, item_row.warehouse, type_of_transaction="Inward"
+				)
+			else:
+				serial_and_batch_bundle = frappe.db.get_value(
+					"Stock Ledger Entry",
+					{"voucher_detail_no": item_row.name, "warehouse": item_row.warehouse},
+					"serial_and_batch_bundle",
+				)
+
 		sle = self.get_sl_entries(
 			item_row,
 			{
 				"actual_qty": -1 * flt(item_row.qty),
 				"incoming_rate": item_row.incoming_rate,
 				"recalculate_rate": cint(self.is_return),
-				"serial_and_batch_bundle": item_row.serial_and_batch_bundle,
+				"serial_and_batch_bundle": serial_and_batch_bundle,
 			},
 		)
 		if item_row.target_warehouse and not cint(self.is_return):
@@ -554,9 +571,15 @@
 				if item_row.warehouse:
 					sle.dependant_sle_voucher_detail_no = item_row.name
 
-			if item_row.serial_and_batch_bundle:
+			if item_row.serial_and_batch_bundle and not cint(self.is_return):
+				type_of_transaction = "Inward"
+				if cint(self.is_return):
+					type_of_transaction = "Outward"
+
 				sle["serial_and_batch_bundle"] = self.make_package_for_transfer(
-					item_row.serial_and_batch_bundle, item_row.target_warehouse
+					item_row.serial_and_batch_bundle,
+					item_row.target_warehouse,
+					type_of_transaction=type_of_transaction,
 				)
 
 		return sle
diff --git a/erpnext/controllers/status_updater.py b/erpnext/controllers/status_updater.py
index e5f341f..fcbec22 100644
--- a/erpnext/controllers/status_updater.py
+++ b/erpnext/controllers/status_updater.py
@@ -577,6 +577,7 @@
 			ref_doc.set_status(update=True)
 
 
+@frappe.request_cache
 def get_allowance_for(
 	item_code,
 	item_allowance=None,
@@ -606,20 +607,20 @@
 				global_amount_allowance,
 			)
 
-	qty_allowance, over_billing_allowance = frappe.db.get_value(
+	qty_allowance, over_billing_allowance = frappe.get_cached_value(
 		"Item", item_code, ["over_delivery_receipt_allowance", "over_billing_allowance"]
 	)
 
 	if qty_or_amount == "qty" and not qty_allowance:
 		if global_qty_allowance == None:
 			global_qty_allowance = flt(
-				frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
+				frappe.get_cached_value("Stock Settings", None, "over_delivery_receipt_allowance")
 			)
 		qty_allowance = global_qty_allowance
 	elif qty_or_amount == "amount" and not over_billing_allowance:
 		if global_amount_allowance == None:
 			global_amount_allowance = flt(
-				frappe.db.get_single_value("Accounts Settings", "over_billing_allowance")
+				frappe.get_cached_value("Accounts Settings", None, "over_billing_allowance")
 			)
 		over_billing_allowance = global_amount_allowance
 
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index a3fbdda..15eeff5 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -236,6 +236,14 @@
 			qty = row.get("rejected_qty")
 			warehouse = row.get("rejected_warehouse")
 
+		if (
+			self.is_internal_transfer()
+			and self.doctype in ["Sales Invoice", "Delivery Note"]
+			and self.is_return
+		):
+			warehouse = row.get("target_warehouse") or row.get("warehouse")
+			type_of_transaction = "Outward"
+
 		bundle_details.update(
 			{
 				"qty": qty,
@@ -579,7 +587,7 @@
 		bundle_doc.warehouse = warehouse
 		bundle_doc.type_of_transaction = type_of_transaction
 		bundle_doc.voucher_type = self.doctype
-		bundle_doc.voucher_no = self.name
+		bundle_doc.voucher_no = "" if self.is_new() or self.docstatus == 2 else self.name
 		bundle_doc.is_cancelled = 0
 
 		for row in bundle_doc.entries:
@@ -595,6 +603,7 @@
 
 		bundle_doc.calculate_qty_and_amount()
 		bundle_doc.flags.ignore_permissions = True
+		bundle_doc.flags.ignore_validate = True
 		bundle_doc.save(ignore_permissions=True)
 
 		return bundle_doc.name
diff --git a/erpnext/controllers/tests/test_queries.py b/erpnext/controllers/tests/test_queries.py
index 3a3bc1c..c536d1c 100644
--- a/erpnext/controllers/tests/test_queries.py
+++ b/erpnext/controllers/tests/test_queries.py
@@ -31,18 +31,6 @@
 		self.assertGreaterEqual(len(query(txt="_Test Lead")), 4)
 		self.assertEqual(len(query(txt="_Test Lead 4")), 1)
 
-	def test_customer_query(self):
-		query = add_default_params(queries.customer_query, "Customer")
-
-		self.assertGreaterEqual(len(query(txt="_Test Customer")), 7)
-		self.assertGreaterEqual(len(query(txt="_Test Customer USD")), 1)
-
-	def test_supplier_query(self):
-		query = add_default_params(queries.supplier_query, "Supplier")
-
-		self.assertGreaterEqual(len(query(txt="_Test Supplier")), 7)
-		self.assertGreaterEqual(len(query(txt="_Test Supplier USD")), 1)
-
 	def test_item_query(self):
 		query = add_default_params(queries.item_query, "Item")
 
diff --git a/erpnext/crm/doctype/lead/lead.js b/erpnext/crm/doctype/lead/lead.js
index 0b6cdf2..609eab7 100644
--- a/erpnext/crm/doctype/lead/lead.js
+++ b/erpnext/crm/doctype/lead/lead.js
@@ -17,10 +17,6 @@
 	}
 
 	onload() {
-		this.frm.set_query("customer", function (doc, cdt, cdn) {
-			return { query: "erpnext.controllers.queries.customer_query" };
-		});
-
 		this.frm.set_query("lead_owner", function (doc, cdt, cdn) {
 			return { query: "frappe.core.doctype.user.user.user_query" };
 		});
diff --git a/erpnext/hooks.py b/erpnext/hooks.py
index 308e6ca..0d70476 100644
--- a/erpnext/hooks.py
+++ b/erpnext/hooks.py
@@ -282,9 +282,6 @@
 
 before_tests = "erpnext.setup.utils.before_tests"
 
-standard_queries = {
-	"Customer": "erpnext.controllers.queries.customer_query",
-}
 
 period_closing_doctypes = [
 	"Sales Invoice",
@@ -309,7 +306,10 @@
 
 doc_events = {
 	"*": {
-		"validate": "erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
+		"validate": [
+			"erpnext.support.doctype.service_level_agreement.service_level_agreement.apply",
+			"erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.check_for_running_deletion_job",
+		],
 	},
 	tuple(period_closing_doctypes): {
 		"validate": "erpnext.accounts.doctype.accounting_period.accounting_period.validate_accounting_period_on_doc_save",
diff --git a/erpnext/manufacturing/doctype/bom/bom.js b/erpnext/manufacturing/doctype/bom/bom.js
index 2ac28ea..6267ee4 100644
--- a/erpnext/manufacturing/doctype/bom/bom.js
+++ b/erpnext/manufacturing/doctype/bom/bom.js
@@ -400,7 +400,7 @@
 	},
 
 	rm_cost_as_per(frm) {
-		if (in_list(["Valuation Rate", "Last Purchase Rate"], frm.doc.rm_cost_as_per)) {
+		if (["Valuation Rate", "Last Purchase Rate"].includes(frm.doc.rm_cost_as_per)) {
 			frm.set_value("plc_conversion_rate", 1.0);
 		}
 	},
diff --git a/erpnext/manufacturing/doctype/bom/test_bom.py b/erpnext/manufacturing/doctype/bom/test_bom.py
index 4cd0530..2debf91 100644
--- a/erpnext/manufacturing/doctype/bom/test_bom.py
+++ b/erpnext/manufacturing/doctype/bom/test_bom.py
@@ -28,28 +28,6 @@
 
 class TestBOM(FrappeTestCase):
 	@timeout
-	def test_bom_qty(self):
-		from erpnext.stock.doctype.item.test_item import make_item
-
-		# No error.
-		bom = frappe.new_doc("BOM")
-		item = make_item(properties={"is_stock_item": 1})
-		bom.item = fg_item.item_code
-		bom.quantity = 1
-		bom.append(
-			"items",
-			{
-				"item_code": bom_item.item_code,
-				"qty": 0,
-				"uom": bom_item.stock_uom,
-				"stock_uom": bom_item.stock_uom,
-				"rate": 100.0,
-			},
-		)
-		bom.save()
-		self.assertEqual(bom.items[0].qty, 0)
-
-	@timeout
 	def test_get_items(self):
 		from erpnext.manufacturing.doctype.bom.bom import get_bom_items_as_dict
 
diff --git a/erpnext/manufacturing/doctype/job_card/job_card.py b/erpnext/manufacturing/doctype/job_card/job_card.py
index 35aebb9..e889c5d 100644
--- a/erpnext/manufacturing/doctype/job_card/job_card.py
+++ b/erpnext/manufacturing/doctype/job_card/job_card.py
@@ -264,7 +264,7 @@
 		if not self.has_overlap(production_capacity, time_logs):
 			return {}
 
-		if self.workstation_type and time_logs:
+		if not self.workstation and self.workstation_type and time_logs:
 			if workstation_time := self.get_workstation_based_on_available_slot(time_logs):
 				self.workstation = workstation_time.get("workstation")
 				return workstation_time
@@ -420,7 +420,7 @@
 		if not workstation_doc.working_hours or cint(
 			frappe.db.get_single_value("Manufacturing Settings", "allow_overtime")
 		):
-			if get_datetime(row.planned_end_time) < get_datetime(row.planned_start_time):
+			if get_datetime(row.planned_end_time) <= get_datetime(row.planned_start_time):
 				row.planned_end_time = add_to_date(row.planned_start_time, minutes=row.time_in_mins)
 				row.remaining_time_in_mins = 0.0
 			else:
diff --git a/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
index 8824c98..69c8f44 100644
--- a/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
+++ b/erpnext/manufacturing/doctype/plant_floor/stock_summary_template.html
@@ -52,10 +52,10 @@
 		</span>
 	</div>
 	<div class="col-sm-1">
-		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">Add</button>
+		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-add" data-item-code="{{ escape(row.item_code) }}">{{ __("Add") }}</button>
 	</div>
 	<div class="col-sm-1">
-		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">Move</button>
+		<button style="margin-left: 7px;" class="btn btn-default btn-xs btn-move" data-item-code="{{ escape(row.item_code) }}">{{ __("Move") }}</button>
 	</div>
 </div>
-{% }); %}
\ No newline at end of file
+{% }); %}
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.js b/erpnext/manufacturing/doctype/production_plan/production_plan.js
index 54d1414..6db901c 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.js
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.js
@@ -129,7 +129,7 @@
 				if (
 					frm.doc.mr_items &&
 					frm.doc.mr_items.length &&
-					!in_list(["Material Requested", "Closed"], frm.doc.status)
+					!["Material Requested", "Closed"].includes(frm.doc.status)
 				) {
 					frm.add_custom_button(
 						__("Material Request"),
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.js b/erpnext/manufacturing/doctype/work_order/work_order.js
index 42f6943..1da33f0 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.js
+++ b/erpnext/manufacturing/doctype/work_order/work_order.js
@@ -9,6 +9,8 @@
 			"Job Card": "Create Job Card",
 		};
 
+		frm.ignore_doctypes_on_cancel_all = ["Serial and Batch Bundle"];
+
 		// Set query for warehouses
 		frm.set_query("wip_warehouse", function () {
 			return {
@@ -194,7 +196,7 @@
 	},
 
 	add_custom_button_to_return_components: function (frm) {
-		if (frm.doc.docstatus === 1 && in_list(["Closed", "Completed"], frm.doc.status)) {
+		if (frm.doc.docstatus === 1 && ["Closed", "Completed"].includes(frm.doc.status)) {
 			let non_consumed_items = frm.doc.required_items.filter((d) => {
 				return flt(d.consumed_qty) < flt(d.transferred_qty - d.returned_qty);
 			});
@@ -594,7 +596,7 @@
 			);
 		}
 
-		if (doc.docstatus === 1 && !in_list(["Closed", "Completed"], doc.status)) {
+		if (doc.docstatus === 1 && !["Closed", "Completed"].includes(doc.status)) {
 			if (doc.status != "Stopped" && doc.status != "Completed") {
 				frm.add_custom_button(
 					__("Stop"),
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 5e22707..233ca19 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -330,6 +330,13 @@
 		else:
 			status = "Cancelled"
 
+		if (
+			self.skip_transfer
+			and self.produced_qty
+			and self.qty > (flt(self.produced_qty) + flt(self.process_loss_qty))
+		):
+			status = "In Process"
+
 		return status
 
 	def update_work_order_qty(self):
@@ -784,7 +791,7 @@
 				)
 
 	def update_completed_qty_in_material_request(self):
-		if self.material_request:
+		if self.material_request and self.material_request_item:
 			frappe.get_doc("Material Request", self.material_request).update_completed_qty(
 				[self.material_request_item]
 			)
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 8033289..15dfc36 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -356,6 +356,7 @@
 erpnext.patches.v15_0.create_advance_payment_status
 erpnext.patches.v15_0.allow_on_submit_dimensions_for_repostable_doctypes
 erpnext.patches.v14_0.create_accounting_dimensions_in_reconciliation_tool
+erpnext.patches.v14_0.update_flag_for_return_invoices
 # below migration patch should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
 erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
diff --git a/erpnext/patches/v14_0/update_flag_for_return_invoices.py b/erpnext/patches/v14_0/update_flag_for_return_invoices.py
new file mode 100644
index 0000000..feb43be
--- /dev/null
+++ b/erpnext/patches/v14_0/update_flag_for_return_invoices.py
@@ -0,0 +1,62 @@
+from frappe import qb
+
+
+def execute():
+	# Set "update_outstanding_for_self" flag in Credit/Debit Notes
+	# Fetch Credit/Debit notes that does have 'return_against' but still post ledger entries against themselves.
+
+	gle = qb.DocType("GL Entry")
+
+	# Use hardcoded 'creation' date to isolate Credit/Debit notes created post v14 backport
+	# https://github.com/frappe/erpnext/pull/39497
+	creation_date = "2024-01-25"
+
+	si = qb.DocType("Sales Invoice")
+	if cr_notes := (
+		qb.from_(si)
+		.select(si.name)
+		.where(
+			(si.creation.gte(creation_date))
+			& (si.docstatus == 1)
+			& (si.is_return == True)
+			& (si.return_against.notnull())
+		)
+		.run()
+	):
+		cr_notes = [x[0] for x in cr_notes]
+		if docs_that_require_update := (
+			qb.from_(gle)
+			.select(gle.voucher_no)
+			.distinct()
+			.where((gle.voucher_no.isin(cr_notes)) & (gle.voucher_no == gle.against_voucher))
+			.run()
+		):
+			docs_that_require_update = [x[0] for x in docs_that_require_update]
+			qb.update(si).set(si.update_outstanding_for_self, True).where(
+				si.name.isin(docs_that_require_update)
+			).run()
+
+	pi = qb.DocType("Purchase Invoice")
+	if dr_notes := (
+		qb.from_(pi)
+		.select(pi.name)
+		.where(
+			(pi.creation.gte(creation_date))
+			& (pi.docstatus == 1)
+			& (pi.is_return == True)
+			& (pi.return_against.notnull())
+		)
+		.run()
+	):
+		dr_notes = [x[0] for x in dr_notes]
+		if docs_that_require_update := (
+			qb.from_(gle)
+			.select(gle.voucher_no)
+			.distinct()
+			.where((gle.voucher_no.isin(dr_notes)) & (gle.voucher_no == gle.against_voucher))
+			.run()
+		):
+			docs_that_require_update = [x[0] for x in docs_that_require_update]
+			qb.update(pi).set(pi.update_outstanding_for_self, True).where(
+				pi.name.isin(docs_that_require_update)
+			).run()
diff --git a/erpnext/projects/doctype/project/project.js b/erpnext/projects/doctype/project/project.js
index 16ac8db..49e8d84 100644
--- a/erpnext/projects/doctype/project/project.js
+++ b/erpnext/projects/doctype/project/project.js
@@ -27,8 +27,6 @@
 			};
 		};
 
-		frm.set_query("customer", "erpnext.controllers.queries.customer_query");
-
 		frm.set_query("user", "users", function () {
 			return {
 				query: "erpnext.projects.doctype.project.project.get_users_for_project",
diff --git a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js
index 454a2a4..5061be9 100644
--- a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js
+++ b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js
@@ -107,7 +107,7 @@
 				this.frm?.doc.docstatus === 0
 					? [
 							{
-								label: __(frappe.utils.icon("edit", "sm") + " Qty"),
+								label: `${frappe.utils.icon("edit", "sm")} ${__("Qty")}`,
 								click: function (node) {
 									let view = frappe.views.trees["BOM Configurator"];
 									view.events.edit_qty(node, view);
@@ -115,7 +115,7 @@
 								btnClass: "hidden-xs",
 							},
 							{
-								label: __(frappe.utils.icon("add", "sm") + " Raw Material"),
+								label: `${frappe.utils.icon("add", "sm")} ${__("Raw Material")}`,
 								click: function (node) {
 									let view = frappe.views.trees["BOM Configurator"];
 									view.events.add_item(node, view);
@@ -126,7 +126,7 @@
 								btnClass: "hidden-xs",
 							},
 							{
-								label: __(frappe.utils.icon("add", "sm") + " Sub Assembly"),
+								label: `${frappe.utils.icon("add", "sm")} ${__("Sub Assembly")}`,
 								click: function (node) {
 									let view = frappe.views.trees["BOM Configurator"];
 									view.events.add_sub_assembly(node, view);
@@ -156,7 +156,7 @@
 								btnClass: "hidden-xs expand-all-btn",
 							},
 							{
-								label: __(frappe.utils.icon("move", "sm") + " Sub Assembly"),
+								label: `${frappe.utils.icon("move", "sm")} ${__("Sub Assembly")}`,
 								click: function (node) {
 									let view = frappe.views.trees["BOM Configurator"];
 									view.events.convert_to_sub_assembly(node, view);
@@ -167,7 +167,7 @@
 								btnClass: "hidden-xs",
 							},
 							{
-								label: __(frappe.utils.icon("delete", "sm") + __(" Item")),
+								label: `${frappe.utils.icon("delete", "sm")} ${__("Item")}`,
 								click: function (node) {
 									let view = frappe.views.trees["BOM Configurator"];
 									view.events.delete_node(node, view);
diff --git a/erpnext/public/js/communication.js b/erpnext/public/js/communication.js
index d9187f8b..c8905e1 100644
--- a/erpnext/public/js/communication.js
+++ b/erpnext/public/js/communication.js
@@ -20,7 +20,7 @@
 			);
 		}
 
-		if (!in_list(["Lead", "Opportunity"], frm.doc.reference_doctype)) {
+		if (!["Lead", "Opportunity"].includes(frm.doc.reference_doctype)) {
 			frm.add_custom_button(
 				__("Lead"),
 				() => {
diff --git a/erpnext/public/js/controllers/accounts.js b/erpnext/public/js/controllers/accounts.js
index f0d8cbb..964a175 100644
--- a/erpnext/public/js/controllers/accounts.js
+++ b/erpnext/public/js/controllers/accounts.js
@@ -11,7 +11,7 @@
 			setup: function(frm) {
 				// set conditional display for rate column in taxes
 				$(frm.wrapper).on('grid-row-render', function(e, grid_row) {
-					if(in_list(['Sales Taxes and Charges', 'Purchase Taxes and Charges'], grid_row.doc.doctype)) {
+					if(['Sales Taxes and Charges', 'Purchase Taxes and Charges'].includes(grid_row.doc.doctype)) {
 						me.set_conditional_mandatory_rate_or_amount(grid_row);
 					}
 				});
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index 1d0d47e..1e94c00 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -74,11 +74,6 @@
 				me.frm.set_query('billing_address', erpnext.queries.company_address_query);
 				erpnext.accounts.dimensions.setup_dimension_filters(me.frm, me.frm.doctype);
 
-				if(this.frm.fields_dict.supplier) {
-					this.frm.set_query("supplier", function() {
-						return{	query: "erpnext.controllers.queries.supplier_query" }});
-				}
-
 				this.frm.set_query("item_code", "items", function() {
 					if (me.frm.doc.is_subcontracted) {
 						var filters = {'supplier': me.frm.doc.supplier};
@@ -134,7 +129,7 @@
 			}
 
 			toggle_subcontracting_fields() {
-				if (in_list(['Purchase Receipt', 'Purchase Invoice'], this.frm.doc.doctype)) {
+				if (['Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doc.doctype)) {
 					this.frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty',
 						'read_only', this.frm.doc.__onload && this.frm.doc.__onload.backflush_based_on === 'BOM');
 
diff --git a/erpnext/public/js/controllers/taxes_and_totals.js b/erpnext/public/js/controllers/taxes_and_totals.js
index 3099184..0d272b8 100644
--- a/erpnext/public/js/controllers/taxes_and_totals.js
+++ b/erpnext/public/js/controllers/taxes_and_totals.js
@@ -9,7 +9,7 @@
 	apply_pricing_rule_on_item(item) {
 		let effective_item_rate = item.price_list_rate;
 		let item_rate = item.rate;
-		if (in_list(["Sales Order", "Quotation"], item.parenttype) && item.blanket_order_rate) {
+		if (["Sales Order", "Quotation"].includes(item.parenttype) && item.blanket_order_rate) {
 			effective_item_rate = item.blanket_order_rate;
 		}
 		if (item.margin_type == "Percentage") {
@@ -26,7 +26,7 @@
 			item.discount_amount = flt(item.rate_with_margin) * flt(item.discount_percentage) / 100;
 		}
 
-		if (item.discount_amount) {
+		if (item.discount_amount > 0) {
 			item_rate = flt((item.rate_with_margin) - (item.discount_amount), precision('rate', item));
 			item.discount_percentage = 100 * flt(item.discount_amount) / flt(item.rate_with_margin);
 		}
@@ -52,7 +52,7 @@
 
 		// Advance calculation applicable to Sales/Purchase Invoice
 		if (
-			in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)
+			["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)
 			&& this.frm.doc.docstatus < 2
 			&& !this.frm.doc.is_return
 		) {
@@ -60,7 +60,7 @@
 		}
 
 		if (
-			in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
+			["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
 			&& this.frm.doc.is_pos
 			&& this.frm.doc.is_return
 		) {
@@ -69,7 +69,7 @@
 		}
 
 		// Sales person's commission
-		if (in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"], this.frm.doc.doctype)) {
+		if (["Quotation", "Sales Order", "Delivery Note", "Sales Invoice"].includes(this.frm.doc.doctype)) {
 			this.calculate_commission();
 			this.calculate_contribution();
 		}
@@ -575,7 +575,7 @@
 			? this.frm.doc["taxes"][tax_count - 1].total + flt(this.frm.doc.grand_total_diff)
 			: this.frm.doc.net_total);
 
-		if(in_list(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
+		if(["Quotation", "Sales Order", "Delivery Note", "Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
 			this.frm.doc.base_grand_total = (this.frm.doc.total_taxes_and_charges) ?
 				flt(this.frm.doc.grand_total * this.frm.doc.conversion_rate) : this.frm.doc.base_net_total;
 		} else {
@@ -583,7 +583,7 @@
 			this.frm.doc.taxes_and_charges_added = this.frm.doc.taxes_and_charges_deducted = 0.0;
 			if(tax_count) {
 				$.each(this.frm.doc["taxes"] || [], function(i, tax) {
-					if (in_list(["Valuation and Total", "Total"], tax.category)) {
+					if (["Valuation and Total", "Total"].includes(tax.category)) {
 						if(tax.add_deduct_tax == "Add") {
 							me.frm.doc.taxes_and_charges_added += flt(tax.tax_amount_after_discount_amount);
 						} else {
@@ -729,7 +729,7 @@
 			var actual_taxes_dict = {};
 
 			$.each(this.frm.doc["taxes"] || [], function(i, tax) {
-				if (in_list(["Actual", "On Item Quantity"], tax.charge_type)) {
+				if (["Actual", "On Item Quantity"].includes(tax.charge_type)) {
 					var tax_amount = (tax.category == "Valuation") ? 0.0 : tax.tax_amount;
 					tax_amount *= (tax.add_deduct_tax == "Deduct") ? -1.0 : 1.0;
 					actual_taxes_dict[tax.idx] = tax_amount;
@@ -774,7 +774,7 @@
 		// NOTE:
 		// paid_amount and write_off_amount is only for POS/Loyalty Point Redemption Invoice
 		// total_advance is only for non POS Invoice
-		if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype) && this.frm.doc.is_return){
+		if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype) && this.frm.doc.is_return){
 			this.calculate_paid_amount();
 		}
 
@@ -782,7 +782,7 @@
 
 		frappe.model.round_floats_in(this.frm.doc, ["grand_total", "total_advance", "write_off_amount"]);
 
-		if(in_list(["Sales Invoice", "POS Invoice", "Purchase Invoice"], this.frm.doc.doctype)) {
+		if(["Sales Invoice", "POS Invoice", "Purchase Invoice"].includes(this.frm.doc.doctype)) {
 			let grand_total = this.frm.doc.rounded_total || this.frm.doc.grand_total;
 			let base_grand_total = this.frm.doc.base_rounded_total || this.frm.doc.base_grand_total;
 
@@ -805,7 +805,7 @@
 				this.frm.refresh_field("base_paid_amount");
 			}
 
-			if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)) {
+			if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)) {
 				let total_amount_for_payment = (this.frm.doc.redeem_loyalty_points && this.frm.doc.loyalty_amount)
 					? flt(total_amount_to_pay - this.frm.doc.loyalty_amount, precision("base_grand_total"))
 					: total_amount_to_pay;
@@ -909,7 +909,7 @@
 	calculate_change_amount(){
 		this.frm.doc.change_amount = 0.0;
 		this.frm.doc.base_change_amount = 0.0;
-		if(in_list(["Sales Invoice", "POS Invoice"], this.frm.doc.doctype)
+		if(["Sales Invoice", "POS Invoice"].includes(this.frm.doc.doctype)
 			&& this.frm.doc.paid_amount > this.frm.doc.grand_total && !this.frm.doc.is_return) {
 
 			var payment_types = $.map(this.frm.doc.payments, function(d) { return d.type; });
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 3a4615d..664fb91 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -315,7 +315,7 @@
 	}
 
 	setup_quality_inspection() {
-		if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)) {
+		if(!["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)) {
 			return;
 		}
 
@@ -327,7 +327,7 @@
 			this.frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
-		const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)
+		const inspection_type = ["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"].includes(this.frm.doc.doctype)
 			? "Incoming" : "Outgoing";
 
 		let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
@@ -359,7 +359,7 @@
 
 	make_payment_request() {
 		let me = this;
-		const payment_request_type = (in_list(['Sales Order', 'Sales Invoice'], this.frm.doc.doctype))
+		const payment_request_type = (['Sales Order', 'Sales Invoice'].includes(this.frm.doc.doctype))
 			? "Inward" : "Outward";
 
 		frappe.call({
@@ -474,7 +474,7 @@
 	setup_sms() {
 		var me = this;
 		let blacklist = ['Purchase Invoice', 'BOM'];
-		if(this.frm.doc.docstatus===1 && !in_list(["Lost", "Stopped", "Closed"], this.frm.doc.status)
+		if(this.frm.doc.docstatus===1 && !["Lost", "Stopped", "Closed"].includes(this.frm.doc.status)
 			&& !blacklist.includes(this.frm.doctype)) {
 			this.frm.page.add_menu_item(__('Send SMS'), function() { me.send_sms(); });
 		}
@@ -760,7 +760,7 @@
 	}
 
 	on_submit() {
-		if (in_list(["Purchase Invoice", "Sales Invoice"], this.frm.doc.doctype)
+		if (["Purchase Invoice", "Sales Invoice"].includes(this.frm.doc.doctype)
 			&& !this.frm.doc.update_stock) {
 			return;
 		}
@@ -864,7 +864,7 @@
 		}
 
 		var set_party_account = function(set_pricing) {
-			if (in_list(["Sales Invoice", "Purchase Invoice"], me.frm.doc.doctype)) {
+			if (["Sales Invoice", "Purchase Invoice"].includes(me.frm.doc.doctype)) {
 				if(me.frm.doc.doctype=="Sales Invoice") {
 					var party_type = "Customer";
 					var party_account_field = 'debit_to';
@@ -899,7 +899,7 @@
 		}
 
 		if (frappe.meta.get_docfield(this.frm.doctype, "shipping_address") &&
-			in_list(['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'], this.frm.doctype)) {
+			['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'].includes(this.frm.doctype)) {
 			erpnext.utils.get_shipping_address(this.frm, function() {
 				set_party_account(set_pricing);
 			});
@@ -1276,8 +1276,11 @@
 
 	calculate_stock_uom_rate(doc, cdt, cdn) {
 		let item = frappe.get_doc(cdt, cdn);
-		item.stock_uom_rate = flt(item.rate)/flt(item.conversion_factor);
-		refresh_field("stock_uom_rate", item.name, item.parentfield);
+
+		if (item?.rate) {
+			item.stock_uom_rate = flt(item.rate) / flt(item.conversion_factor);
+			refresh_field("stock_uom_rate", item.name, item.parentfield);
+		}
 	}
 	service_stop_date(frm, cdt, cdn) {
 		var child = locals[cdt][cdn];
@@ -1620,7 +1623,7 @@
 			"doctype": me.frm.doc.doctype,
 			"name": me.frm.doc.name,
 			"is_return": cint(me.frm.doc.is_return),
-			"update_stock": in_list(['Sales Invoice', 'Purchase Invoice'], me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
+			"update_stock": ['Sales Invoice', 'Purchase Invoice'].includes(me.frm.doc.doctype) ? cint(me.frm.doc.update_stock) : 0,
 			"conversion_factor": me.frm.doc.conversion_factor,
 			"pos_profile": me.frm.doc.doctype == 'Sales Invoice' ? me.frm.doc.pos_profile : '',
 			"coupon_code": me.frm.doc.coupon_code
@@ -1826,8 +1829,8 @@
 			let items = [];
 
 			me.frm.doc.items.forEach(d => {
-				// if same item was added a free item through a different pricing rule, keep it
-				if(d.item_code != item.remove_free_item || !d.is_free_item || removed_pricing_rule?.includes(d.pricing_rules)) {
+				// if same item was added as free item through a different pricing rule, keep it
+				if(d.item_code != item.remove_free_item || !d.is_free_item || !removed_pricing_rule?.includes(d.pricing_rules)) {
 					items.push(d);
 				}
 			});
@@ -2229,7 +2232,7 @@
 		});
 
 		this.frm.doc.items.forEach(item => {
-			if (!item.quality_inspection) {
+			if (this.has_inspection_required(item)) {
 				let dialog_items = dialog.fields_dict.items;
 				dialog_items.df.data.push({
 					"docname": item.name,
@@ -2253,10 +2256,20 @@
 		}
 	}
 
+	has_inspection_required(item) {
+		if (this.frm.doc.doctype === "Stock Entry" && this.frm.doc.purpose == "Manufacture" ) {
+			if (item.is_finished_item && !item.quality_inspection) {
+				return true;
+			}
+		} else if (!item.quality_inspection) {
+			return true;
+		}
+	}
+
 	get_method_for_payment() {
 		var method = "erpnext.accounts.doctype.payment_entry.payment_entry.get_payment_entry";
 		if(cur_frm.doc.__onload && cur_frm.doc.__onload.make_payment_via_journal_entry){
-			if(in_list(['Sales Invoice', 'Purchase Invoice'],  cur_frm.doc.doctype)){
+			if(['Sales Invoice', 'Purchase Invoice'].includes( cur_frm.doc.doctype)){
 				method = "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_invoice";
 			}else {
 				method= "erpnext.accounts.doctype.journal_entry.journal_entry.get_payment_entry_against_order";
@@ -2496,7 +2509,7 @@
 	}
 
 	frappe.require("assets/erpnext/js/utils/serial_no_batch_selector.js", function() {
-		if (in_list(["Sales Invoice", "Delivery Note"], frm.doc.doctype)) {
+		if (["Sales Invoice", "Delivery Note"].includes(frm.doc.doctype)) {
 			item_row.type_of_transaction = frm.doc.is_return ? "Inward" : "Outward";
 		} else {
 			item_row.type_of_transaction = frm.doc.is_return ? "Outward" : "Inward";
diff --git a/erpnext/public/js/payment/payments.js b/erpnext/public/js/payment/payments.js
index 0e58420..c91bb04 100644
--- a/erpnext/public/js/payment/payments.js
+++ b/erpnext/public/js/payment/payments.js
@@ -218,7 +218,7 @@
 
 	update_paid_amount(update_write_off) {
 		var me = this;
-		if (in_list(["change_amount", "write_off_amount"], this.idx)) {
+		if (["change_amount", "write_off_amount"].includes(this.idx)) {
 			var value = me.selected_mode.val();
 			if (me.idx == "change_amount") {
 				me.change_amount(value);
diff --git a/erpnext/public/js/queries.js b/erpnext/public/js/queries.js
index b7e685c..d7edac3 100644
--- a/erpnext/public/js/queries.js
+++ b/erpnext/public/js/queries.js
@@ -12,14 +12,6 @@
 		return { query: "erpnext.controllers.queries.lead_query" };
 	},
 
-	customer: function () {
-		return { query: "erpnext.controllers.queries.customer_query" };
-	},
-
-	supplier: function () {
-		return { query: "erpnext.controllers.queries.supplier_query" };
-	},
-
 	item: function (filters) {
 		var args = { query: "erpnext.controllers.queries.item_query" };
 		if (filters) args["filters"] = filters;
diff --git a/erpnext/public/js/sms_manager.js b/erpnext/public/js/sms_manager.js
index d3147bb..63833da 100644
--- a/erpnext/public/js/sms_manager.js
+++ b/erpnext/public/js/sms_manager.js
@@ -28,11 +28,11 @@
 			"Purchase Receipt": "Items has been received against purchase receipt: " + doc.name,
 		};
 
-		if (in_list(["Sales Order", "Delivery Note", "Sales Invoice"], doc.doctype))
+		if (["Sales Order", "Delivery Note", "Sales Invoice"].includes(doc.doctype))
 			this.show(doc.contact_person, "Customer", doc.customer, "", default_msg[doc.doctype]);
 		else if (doc.doctype === "Quotation")
 			this.show(doc.contact_person, "Customer", doc.party_name, "", default_msg[doc.doctype]);
-		else if (in_list(["Purchase Order", "Purchase Receipt"], doc.doctype))
+		else if (["Purchase Order", "Purchase Receipt"].includes(doc.doctype))
 			this.show(doc.contact_person, "Supplier", doc.supplier, "", default_msg[doc.doctype]);
 		else if (doc.doctype == "Lead") this.show("", "", "", doc.mobile_no, default_msg[doc.doctype]);
 		else if (doc.doctype == "Opportunity")
diff --git a/erpnext/public/js/utils/party.js b/erpnext/public/js/utils/party.js
index 801376b..6239417 100644
--- a/erpnext/public/js/utils/party.js
+++ b/erpnext/public/js/utils/party.js
@@ -14,10 +14,10 @@
 	if (!args) {
 		if (
 			(frm.doctype != "Purchase Order" && frm.doc.customer) ||
-			(frm.doc.party_name && in_list(["Quotation", "Opportunity"], frm.doc.doctype))
+			(frm.doc.party_name && ["Quotation", "Opportunity"].includes(frm.doc.doctype))
 		) {
 			let party_type = "Customer";
-			if (frm.doc.quotation_to && in_list(["Lead", "Prospect"], frm.doc.quotation_to)) {
+			if (frm.doc.quotation_to && ["Lead", "Prospect"].includes(frm.doc.quotation_to)) {
 				party_type = frm.doc.quotation_to;
 			}
 
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index f2b7331..00df1c5 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -303,7 +303,7 @@
 				if ((doc.packed_items || []).length) {
 					$(this.frm.fields_dict.packing_list.row.wrapper).toggle(true);
 
-					if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) {
+					if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) {
 						var help_msg =
 							"<div class='alert alert-warning'>" +
 							__(
@@ -315,7 +315,7 @@
 					}
 				} else {
 					$(this.frm.fields_dict.packing_list.row.wrapper).toggle(false);
-					if (in_list(["Delivery Note", "Sales Invoice"], doc.doctype)) {
+					if (["Delivery Note", "Sales Invoice"].includes(doc.doctype)) {
 						frappe.meta.get_docfield(doc.doctype, "product_bundle_help", doc.name).options = "";
 					}
 				}
@@ -416,7 +416,7 @@
 
 			project() {
 				let me = this;
-				if (in_list(["Delivery Note", "Sales Invoice", "Sales Order"], this.frm.doc.doctype)) {
+				if (["Delivery Note", "Sales Invoice", "Sales Order"].includes(this.frm.doc.doctype)) {
 					if (this.frm.doc.project) {
 						frappe.call({
 							method: "erpnext.projects.doctype.project.project.get_cost_center_name",
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 24133b8..42d37bf4 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -542,6 +542,10 @@
 			frappe.throw(__("Please add atleast one Serial No / Batch No"));
 		}
 
+		if (!warehouse) {
+			frappe.throw(__("Please select a Warehouse"));
+		}
+
 		frappe
 			.call({
 				method: "erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle.add_serial_batch_ledgers",
diff --git a/erpnext/public/scss/erpnext.scss b/erpnext/public/scss/erpnext.scss
index e45ae50..03dd311 100644
--- a/erpnext/public/scss/erpnext.scss
+++ b/erpnext/public/scss/erpnext.scss
@@ -548,3 +548,7 @@
 	align-items: center;
 	justify-content: center;
 }
+
+.frappe-control[data-fieldname="other_charges_calculation"] .ql-editor {
+	white-space: normal;
+}
diff --git a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js
index 5fbb5cb..7aa8012 100644
--- a/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js
+++ b/erpnext/regional/doctype/import_supplier_invoice/import_supplier_invoice.js
@@ -34,7 +34,7 @@
 	},
 
 	toggle_read_only_fields: function (frm) {
-		if (in_list(["File Import Completed", "Processing File Data"], frm.doc.status)) {
+		if (["File Import Completed", "Processing File Data"].includes(frm.doc.status)) {
 			cur_frm.set_read_only();
 			cur_frm.refresh_fields();
 			frm.set_df_property("import_invoices", "hidden", 1);
diff --git a/erpnext/selling/doctype/customer/customer.json b/erpnext/selling/doctype/customer/customer.json
index db712d9..41c6311 100644
--- a/erpnext/selling/doctype/customer/customer.json
+++ b/erpnext/selling/doctype/customer/customer.json
@@ -583,7 +583,7 @@
    "link_fieldname": "party"
   }
  ],
- "modified": "2023-12-28 13:15:36.298369",
+ "modified": "2024-03-16 19:41:47.971815",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Customer",
@@ -661,7 +661,7 @@
   }
  ],
  "quick_entry": 1,
- "search_fields": "customer_name,customer_group,territory, mobile_no,primary_address",
+ "search_fields": "customer_group,territory, mobile_no,primary_address",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
diff --git a/erpnext/selling/doctype/customer/test_customer.py b/erpnext/selling/doctype/customer/test_customer.py
index a8ebccd..7e6d6de 100644
--- a/erpnext/selling/doctype/customer/test_customer.py
+++ b/erpnext/selling/doctype/customer/test_customer.py
@@ -370,37 +370,6 @@
 		due_date = get_due_date("2017-01-22", "Customer", "_Test Customer")
 		self.assertEqual(due_date, "2017-01-22")
 
-	def test_serach_fields_for_customer(self):
-		from erpnext.controllers.queries import customer_query
-
-		frappe.db.set_single_value("Selling Settings", "cust_master_name", "Naming Series")
-
-		make_property_setter(
-			"Customer", None, "search_fields", "customer_group", "Data", for_doctype="Doctype"
-		)
-
-		data = customer_query(
-			"Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True
-		)
-
-		self.assertEqual(data[0].name, "_Test Customer")
-		self.assertEqual(data[0].customer_group, "_Test Customer Group")
-		self.assertTrue("territory" not in data[0])
-
-		make_property_setter(
-			"Customer", None, "search_fields", "customer_group, territory", "Data", for_doctype="Doctype"
-		)
-		data = customer_query(
-			"Customer", "_Test Customer", "", 0, 20, filters={"name": "_Test Customer"}, as_dict=True
-		)
-
-		self.assertEqual(data[0].name, "_Test Customer")
-		self.assertEqual(data[0].customer_group, "_Test Customer Group")
-		self.assertEqual(data[0].territory, "_Test Territory")
-		self.assertTrue("territory" in data[0])
-
-		frappe.db.set_single_value("Selling Settings", "cust_master_name", "Customer Name")
-
 	def test_parse_full_name(self):
 		first, middle, last = parse_full_name("John")
 		self.assertEqual(first, "John")
diff --git a/erpnext/selling/doctype/quotation/quotation.js b/erpnext/selling/doctype/quotation/quotation.js
index 6e2b726..95cbfd0 100644
--- a/erpnext/selling/doctype/quotation/quotation.js
+++ b/erpnext/selling/doctype/quotation/quotation.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
+cur_frm.cscript.tax_table = "Sales Taxes and Charges";
+
 erpnext.accounts.taxes.setup_tax_validations("Sales Taxes and Charges Template");
 erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
 erpnext.pre_sales.set_as_lost("Quotation");
diff --git a/erpnext/selling/doctype/quotation/quotation.json b/erpnext/selling/doctype/quotation/quotation.json
index 8c816cf..982e732 100644
--- a/erpnext/selling/doctype/quotation/quotation.json
+++ b/erpnext/selling/doctype/quotation/quotation.json
@@ -557,7 +557,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "label": "Taxes and Charges Calculation",
    "no_copy": 1,
    "oldfieldtype": "HTML",
@@ -1073,7 +1073,7 @@
  "idx": 82,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-06-03 16:21:04.980033",
+ "modified": "2024-03-20 16:04:21.567847",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Quotation",
diff --git a/erpnext/selling/doctype/quotation/quotation.py b/erpnext/selling/doctype/quotation/quotation.py
index 13d17d6..633e5f5 100644
--- a/erpnext/selling/doctype/quotation/quotation.py
+++ b/erpnext/selling/doctype/quotation/quotation.py
@@ -78,7 +78,7 @@
 		opportunity: DF.Link | None
 		order_lost_reason: DF.SmallText | None
 		order_type: DF.Literal["", "Sales", "Maintenance", "Shopping Cart"]
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		packed_items: DF.Table[PackedItem]
 		party_name: DF.DynamicLink | None
 		payment_schedule: DF.Table[PaymentSchedule]
diff --git a/erpnext/selling/doctype/quotation/test_quotation.py b/erpnext/selling/doctype/quotation/test_quotation.py
index 0b0d6e7..a525942 100644
--- a/erpnext/selling/doctype/quotation/test_quotation.py
+++ b/erpnext/selling/doctype/quotation/test_quotation.py
@@ -42,6 +42,39 @@
 
 		self.assertTrue(sales_order.get("payment_schedule"))
 
+	def test_gross_profit(self):
+		from erpnext.stock.doctype.item.test_item import make_item
+		from erpnext.stock.doctype.stock_entry.stock_entry_utils import make_stock_entry
+		from erpnext.stock.get_item_details import insert_item_price
+
+		item_doc = make_item("_Test Item for Gross Profit", {"is_stock_item": 1})
+		item_code = item_doc.name
+		make_stock_entry(item_code=item_code, qty=10, rate=100, target="_Test Warehouse - _TC")
+
+		selling_price_list = frappe.get_all("Price List", filters={"selling": 1}, limit=1)[0].name
+		frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1)
+		insert_item_price(
+			frappe._dict(
+				{
+					"item_code": item_code,
+					"price_list": selling_price_list,
+					"price_list_rate": 300,
+					"rate": 300,
+					"conversion_factor": 1,
+					"discount_amount": 0.0,
+					"currency": frappe.db.get_value("Price List", selling_price_list, "currency"),
+					"uom": item_doc.stock_uom,
+				}
+			)
+		)
+
+		quotation = make_quotation(
+			item_code=item_code, qty=1, rate=300, selling_price_list=selling_price_list
+		)
+		self.assertEqual(quotation.items[0].valuation_rate, 100)
+		self.assertEqual(quotation.items[0].gross_profit, 200)
+		frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
+
 	def test_maintain_rate_in_sales_cycle_is_enforced(self):
 		from erpnext.selling.doctype.quotation.quotation import make_sales_order
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 28f6edf..715d4d1 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -1,6 +1,8 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 
+cur_frm.cscript.tax_table = "Sales Taxes and Charges";
+
 erpnext.accounts.taxes.setup_tax_filters("Sales Taxes and Charges");
 erpnext.accounts.taxes.setup_tax_validations("Sales Order");
 erpnext.sales_common.setup_selling_controller();
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index 3c516d0..1fb1ae0 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -777,7 +777,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "hide_days": 1,
    "hide_seconds": 1,
    "label": "Taxes and Charges Calculation",
@@ -1657,7 +1657,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-10-18 12:41:54.813462",
+ "modified": "2024-03-20 16:04:43.627183",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order",
@@ -1735,4 +1735,4 @@
  "title_field": "customer_name",
  "track_changes": 1,
  "track_seen": 1
-}
+}
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 4956f29..79df4d3 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -66,6 +66,7 @@
 		additional_discount_percentage: DF.Float
 		address_display: DF.SmallText | None
 		advance_paid: DF.Currency
+		advance_payment_status: DF.Literal["Not Requested", "Requested", "Partially Paid", "Fully Paid"]
 		amended_from: DF.Link | None
 		amount_eligible_for_commission: DF.Currency
 		apply_discount_on: DF.Literal["", "Grand Total", "Net Total"]
@@ -122,7 +123,7 @@
 		naming_series: DF.Literal["SAL-ORD-.YYYY.-"]
 		net_total: DF.Currency
 		order_type: DF.Literal["", "Sales", "Maintenance", "Shopping Cart"]
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		packed_items: DF.Table[PackedItem]
 		party_account_currency: DF.Link | None
 		payment_schedule: DF.Table[PaymentSchedule]
@@ -155,6 +156,7 @@
 			"",
 			"Draft",
 			"On Hold",
+			"To Pay",
 			"To Deliver and Bill",
 			"To Bill",
 			"To Deliver",
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index b5189b8..7327fde 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2101,6 +2101,80 @@
 			self.assertFalse(row.warehouse == rejected_warehouse)
 			self.assertTrue(row.warehouse == warehouse)
 
+	def test_pick_list_for_batch(self):
+		from erpnext.stock.doctype.pick_list.pick_list import create_delivery_note
+
+		batch_item = make_item(
+			"_Test Batch Item for Pick LIST",
+			properties={
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "BATCH-SDDTBIFRM-.#####",
+			},
+		).name
+
+		warehouse = "_Test Warehouse - _TC"
+		se = make_stock_entry(item_code=batch_item, qty=10, target=warehouse, use_serial_batch_fields=1)
+		so = make_sales_order(item_code=batch_item, qty=10, warehouse=warehouse)
+		pick_list = create_pick_list(so.name)
+
+		pick_list.save()
+		batch_no = frappe.get_all(
+			"Serial and Batch Entry",
+			filters={"parent": se.items[0].serial_and_batch_bundle},
+			fields=["batch_no"],
+		)[0].batch_no
+
+		for row in pick_list.locations:
+			self.assertEqual(row.qty, 10.0)
+			self.assertTrue(row.warehouse == warehouse)
+			self.assertTrue(row.batch_no == batch_no)
+
+		pick_list.submit()
+
+		dn = create_delivery_note(pick_list.name)
+		for row in dn.items:
+			self.assertEqual(row.qty, 10.0)
+			self.assertTrue(row.warehouse == warehouse)
+			self.assertTrue(row.batch_no == batch_no)
+
+		dn.submit()
+		dn.reload()
+
+	def test_auto_update_price_list(self):
+		item = make_item(
+			"_Test Auto Update Price List Item",
+		)
+
+		frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 1)
+		so = make_sales_order(
+			item_code=item.name, currency="USD", qty=1, rate=100, price_list_rate=100, do_not_submit=True
+		)
+		so.save()
+
+		item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate")
+		self.assertEqual(item_price, 100)
+
+		so = make_sales_order(
+			item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=100, do_not_submit=True
+		)
+		so.save()
+
+		item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate")
+		self.assertEqual(item_price, 100)
+
+		frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 1)
+		so = make_sales_order(
+			item_code=item.name, currency="USD", qty=1, rate=200, price_list_rate=200, do_not_submit=True
+		)
+		so.save()
+
+		item_price = frappe.db.get_value("Item Price", {"item_code": item.name}, "price_list_rate")
+		self.assertEqual(item_price, 200)
+
+		frappe.db.set_single_value("Stock Settings", "update_existing_price_list_rate", 0)
+		frappe.db.set_single_value("Stock Settings", "auto_insert_price_list_rate_if_missing", 0)
+
 
 def automatically_fetch_payment_terms(enable=1):
 	accounts_settings = frappe.get_doc("Accounts Settings")
@@ -2166,13 +2240,14 @@
 	return so
 
 
-def create_dn_against_so(so, delivered_qty=0):
+def create_dn_against_so(so, delivered_qty=0, do_not_submit=False):
 	frappe.db.set_single_value("Stock Settings", "allow_negative_stock", 1)
 
 	dn = make_delivery_note(so)
 	dn.get("items")[0].qty = delivered_qty or 5
 	dn.insert()
-	dn.submit()
+	if not do_not_submit:
+		dn.submit()
 	return dn
 
 
diff --git a/erpnext/selling/page/point_of_sale/pos_item_cart.js b/erpnext/selling/page/point_of_sale/pos_item_cart.js
index d95ef58..fbee9c1 100644
--- a/erpnext/selling/page/point_of_sale/pos_item_cart.js
+++ b/erpnext/selling/page/point_of_sale/pos_item_cart.js
@@ -295,10 +295,10 @@
 			<div class="customer-field"></div>
 		`);
 		const me = this;
-		const query = { query: "erpnext.controllers.queries.customer_query" };
 		const allowed_customer_group = this.allowed_customer_groups || [];
+		let filters = {};
 		if (allowed_customer_group.length) {
-			query.filters = {
+			filters = {
 				customer_group: ["in", allowed_customer_group],
 			};
 		}
@@ -308,7 +308,11 @@
 				fieldtype: "Link",
 				options: "Customer",
 				placeholder: __("Search by customer name, phone, email."),
-				get_query: () => query,
+				get_query: function () {
+					return {
+						filters: filters,
+					};
+				},
 				onchange: function () {
 					if (this.value) {
 						const frm = me.events.get_frm();
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index 448dbca..c399005 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -73,7 +73,7 @@
 		const { status } = doc;
 		let indicator_color = "";
 
-		in_list(["Paid", "Consolidated"], status) && (indicator_color = "green");
+		["Paid", "Consolidated"].includes(status) && (indicator_color = "green");
 		status === "Draft" && (indicator_color = "red");
 		status === "Return" && (indicator_color = "grey");
 
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 f2f1e4c..42bdf57 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
@@ -197,6 +197,8 @@
 				):
 					details[p_key] += r.get(qty_or_amount_field, 0)
 					details[variance_key] = details.get(p_key) - details.get(target_key)
+				else:
+					details[variance_key] = details.get(p_key) - details.get(target_key)
 
 			details["total_achieved"] += details.get(p_key)
 			details["total_variance"] = details.get("total_achieved") - details.get("total_target")
@@ -209,31 +211,32 @@
 
 	parent_doc = frappe.qb.DocType(filters.get("doctype"))
 	child_doc = frappe.qb.DocType(filters.get("doctype") + " Item")
-	sales_team = frappe.qb.DocType("Sales Team")
 
-	query = (
-		frappe.qb.from_(parent_doc)
-		.inner_join(child_doc)
-		.on(child_doc.parent == parent_doc.name)
-		.inner_join(sales_team)
-		.on(sales_team.parent == parent_doc.name)
-		.select(
-			child_doc.item_group,
-			(child_doc.stock_qty * sales_team.allocated_percentage / 100).as_("stock_qty"),
-			(child_doc.base_net_amount * sales_team.allocated_percentage / 100).as_("base_net_amount"),
-			sales_team.sales_person,
-			parent_doc[date_field],
-		)
-		.where(
-			(parent_doc.docstatus == 1)
-			& (parent_doc[date_field].between(fiscal_year.year_start_date, fiscal_year.year_end_date))
-		)
-	)
+	query = frappe.qb.from_(parent_doc).inner_join(child_doc).on(child_doc.parent == parent_doc.name)
 
 	if sales_field == "sales_person":
-		query = query.where(sales_team.sales_person.isin(sales_users_or_territory_data))
+		sales_team = frappe.qb.DocType("Sales Team")
+		stock_qty = child_doc.stock_qty * sales_team.allocated_percentage / 100
+		net_amount = child_doc.base_net_amount * sales_team.allocated_percentage / 100
+		sales_field_col = sales_team[sales_field]
+
+		query = query.inner_join(sales_team).on(sales_team.parent == parent_doc.name)
 	else:
-		query = query.where(parent_doc[sales_field].isin(sales_users_or_territory_data))
+		stock_qty = child_doc.stock_qty
+		net_amount = child_doc.base_net_amount
+		sales_field_col = parent_doc[sales_field]
+
+	query = query.select(
+		child_doc.item_group,
+		parent_doc[date_field],
+		(stock_qty).as_("stock_qty"),
+		(net_amount).as_("base_net_amount"),
+		sales_field_col,
+	).where(
+		(parent_doc.docstatus == 1)
+		& (parent_doc[date_field].between(fiscal_year.year_start_date, fiscal_year.year_end_date))
+		& (sales_field_col.isin(sales_users_or_territory_data))
+	)
 
 	return query.run(as_dict=True)
 
diff --git a/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py
new file mode 100644
index 0000000..1718668
--- /dev/null
+++ b/erpnext/selling/report/sales_partner_target_variance_based_on_item_group/test_sales_partner_target_variance_based_on_item_group.py
@@ -0,0 +1,57 @@
+import frappe
+from frappe.tests.utils import FrappeTestCase
+from frappe.utils import flt, nowdate
+
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.utils import get_fiscal_year
+from erpnext.selling.report.sales_partner_target_variance_based_on_item_group.sales_partner_target_variance_based_on_item_group import (
+	execute,
+)
+from erpnext.selling.report.sales_person_target_variance_based_on_item_group.test_sales_person_target_variance_based_on_item_group import (
+	create_sales_target_doc,
+	create_target_distribution,
+)
+
+
+class TestSalesPartnerTargetVarianceBasedOnItemGroup(FrappeTestCase):
+	def setUp(self):
+		self.fiscal_year = get_fiscal_year(nowdate())[0]
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def test_achieved_target_and_variance_for_partner(self):
+		# Create a Target Distribution
+		distribution = create_target_distribution(self.fiscal_year)
+
+		# Create Sales Partner with targets for the current fiscal year
+		sales_partner = create_sales_target_doc(
+			"Sales Partner", "partner_name", "Sales Partner 1", self.fiscal_year, distribution.name
+		)
+
+		# Create a Sales Invoice for the Partner
+		si = create_sales_invoice(
+			rate=1000,
+			qty=20,
+			do_not_submit=True,
+		)
+		si.sales_partner = sales_partner
+		si.commission_rate = 5
+		si.submit()
+
+		# Check Achieved Target and Variance for the Sales Partner
+		result = execute(
+			frappe._dict(
+				{
+					"fiscal_year": self.fiscal_year,
+					"doctype": "Sales Invoice",
+					"period": "Yearly",
+					"target_on": "Quantity",
+				}
+			)
+		)[1]
+		row = frappe._dict(result[0])
+		self.assertSequenceEqual(
+			[flt(value, 2) for value in (row.total_target, row.total_achieved, row.total_variance)],
+			[50, 20, -30],
+		)
diff --git a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py
index 4ae5d2b..73ae6d0 100644
--- a/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py
+++ b/erpnext/selling/report/sales_person_target_variance_based_on_item_group/test_sales_person_target_variance_based_on_item_group.py
@@ -18,17 +18,17 @@
 
 	def test_achieved_target_and_variance(self):
 		# Create a Target Distribution
-		distribution = frappe.new_doc("Monthly Distribution")
-		distribution.distribution_id = "Target Report Distribution"
-		distribution.fiscal_year = self.fiscal_year
-		distribution.get_months()
-		distribution.insert()
+		distribution = create_target_distribution(self.fiscal_year)
 
-		# Create sales people with targets
-		person_1 = create_sales_person_with_target("Sales Person 1", self.fiscal_year, distribution.name)
-		person_2 = create_sales_person_with_target("Sales Person 2", self.fiscal_year, distribution.name)
+		# Create sales people with targets for the current fiscal year
+		person_1 = create_sales_target_doc(
+			"Sales Person", "sales_person_name", "Sales Person 1", self.fiscal_year, distribution.name
+		)
+		person_2 = create_sales_target_doc(
+			"Sales Person", "sales_person_name", "Sales Person 2", self.fiscal_year, distribution.name
+		)
 
-		# Create a Sales Order with 50-50 contribution
+		# Create a Sales Order with 50-50 contribution between both Sales people
 		so = make_sales_order(
 			rate=1000,
 			qty=20,
@@ -69,10 +69,20 @@
 		)
 
 
-def create_sales_person_with_target(sales_person_name, fiscal_year, distribution_id):
-	sales_person = frappe.new_doc("Sales Person")
-	sales_person.sales_person_name = sales_person_name
-	sales_person.append(
+def create_target_distribution(fiscal_year):
+	distribution = frappe.new_doc("Monthly Distribution")
+	distribution.distribution_id = "Target Report Distribution"
+	distribution.fiscal_year = fiscal_year
+	distribution.get_months()
+	return distribution.insert()
+
+
+def create_sales_target_doc(
+	sales_field_dt, sales_field_name, sales_field_value, fiscal_year, distribution_id
+):
+	sales_target_doc = frappe.new_doc(sales_field_dt)
+	sales_target_doc.set(sales_field_name, sales_field_value)
+	sales_target_doc.append(
 		"targets",
 		{
 			"fiscal_year": fiscal_year,
@@ -81,4 +91,6 @@
 			"distribution_id": distribution_id,
 		},
 	)
-	return sales_person.insert()
+	if sales_field_dt == "Sales Partner":
+		sales_target_doc.commission_rate = 5
+	return sales_target_doc.insert()
diff --git a/erpnext/setup/demo.py b/erpnext/setup/demo.py
index 688d45a..f48402e 100644
--- a/erpnext/setup/demo.py
+++ b/erpnext/setup/demo.py
@@ -181,8 +181,10 @@
 def create_transaction_deletion_record(company):
 	transaction_deletion_record = frappe.new_doc("Transaction Deletion Record")
 	transaction_deletion_record.company = company
+	transaction_deletion_record.process_in_single_transaction = True
 	transaction_deletion_record.save(ignore_permissions=True)
 	transaction_deletion_record.submit()
+	transaction_deletion_record.start_deletion_tasks()
 
 
 def clear_masters():
diff --git a/erpnext/setup/demo_data/item.json b/erpnext/setup/demo_data/item.json
index 330e114..1702434 100644
--- a/erpnext/setup/demo_data/item.json
+++ b/erpnext/setup/demo_data/item.json
@@ -4,6 +4,7 @@
         "item_group": "Demo Item Group",
         "item_code": "SKU001",
         "item_name": "T-shirt",
+        "valuation_rate": 400.0,
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/1484808/pexels-photo-1484808.jpeg"
     },
@@ -11,6 +12,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU002",
+        "valuation_rate": 300.0,
         "item_name": "Laptop",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/3999538/pexels-photo-3999538.jpeg"
@@ -19,6 +21,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU003",
+        "valuation_rate": 523.0,
         "item_name": "Book",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/2422178/pexels-photo-2422178.jpeg"
@@ -27,6 +30,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU004",
+        "valuation_rate": 725.0,
         "item_name": "Smartphone",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/1647976/pexels-photo-1647976.jpeg"
@@ -35,6 +39,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU005",
+        "valuation_rate": 222.0,
         "item_name": "Sneakers",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/1598505/pexels-photo-1598505.jpeg"
@@ -43,6 +48,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU006",
+        "valuation_rate": 420.0,
         "item_name": "Coffee Mug",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/585753/pexels-photo-585753.jpeg"
@@ -51,6 +57,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU007",
+        "valuation_rate": 375.0,
         "item_name": "Television",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/8059376/pexels-photo-8059376.jpeg"
@@ -59,6 +66,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU008",
+        "valuation_rate": 333.0,
         "item_name": "Backpack",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/3731256/pexels-photo-3731256.jpeg"
@@ -67,6 +75,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU009",
+        "valuation_rate": 700.0,
         "item_name": "Headphones",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/3587478/pexels-photo-3587478.jpeg"
@@ -75,6 +84,7 @@
         "doctype": "Item",
         "item_group": "Demo Item Group",
         "item_code": "SKU010",
+        "valuation_rate": 500.0,
         "item_name": "Camera",
         "gst_hsn_code": "999512",
         "image": "https://images.pexels.com/photos/51383/photo-camera-subject-photographer-51383.jpeg"
diff --git a/erpnext/setup/doctype/company/company.js b/erpnext/setup/doctype/company/company.js
index 3917005..9b1a41a 100644
--- a/erpnext/setup/doctype/company/company.js
+++ b/erpnext/setup/doctype/company/company.js
@@ -168,7 +168,7 @@
 
 	delete_company_transactions: function (frm) {
 		frappe.call({
-			method: "erpnext.setup.doctype.company.company.is_deletion_job_running",
+			method: "erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record.is_deletion_doc_running",
 			args: {
 				company: frm.doc.name,
 			},
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index 876b6a4..3ca14e6 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -12,7 +12,6 @@
 from frappe.custom.doctype.property_setter.property_setter import make_property_setter
 from frappe.desk.page.setup_wizard.setup_wizard import make_records
 from frappe.utils import cint, formatdate, get_link_to_form, get_timestamp, today
-from frappe.utils.background_jobs import get_job, is_job_enqueued
 from frappe.utils.nestedset import NestedSet, rebuild_tree
 
 from erpnext.accounts.doctype.account.account import get_account_currency
@@ -901,37 +900,21 @@
 		return None
 
 
-def generate_id_for_deletion_job(company):
-	return "delete_company_transactions_" + company
-
-
-@frappe.whitelist()
-def is_deletion_job_running(company):
-	job_id = generate_id_for_deletion_job(company)
-	if is_job_enqueued(job_id):
-		job_name = get_job(job_id).get_id()  # job name will have site prefix
-		frappe.throw(
-			_("A Transaction Deletion Job: {0} is already running for {1}").format(
-				frappe.bold(get_link_to_form("RQ Job", job_name)), frappe.bold(company)
-			)
-		)
-
-
 @frappe.whitelist()
 def create_transaction_deletion_request(company):
-	is_deletion_job_running(company)
-	job_id = generate_id_for_deletion_job(company)
+	from erpnext.setup.doctype.transaction_deletion_record.transaction_deletion_record import (
+		is_deletion_doc_running,
+	)
+
+	is_deletion_doc_running(company)
 
 	tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
-	tdr.insert()
+	tdr.submit()
+	tdr.start_deletion_tasks()
 
-	frappe.enqueue(
-		"frappe.utils.background_jobs.run_doc_method",
-		doctype=tdr.doctype,
-		name=tdr.name,
-		doc_method="submit",
-		job_id=job_id,
-		queue="long",
-		enqueue_after_commit=True,
+	frappe.msgprint(
+		_("A Transaction Deletion Document: {0} is triggered for {0}").format(
+			get_link_to_form("Transaction Deletion Record", tdr.name)
+		),
+		frappe.bold(company),
 	)
-	frappe.msgprint(_("A Transaction Deletion Job is triggered for {0}").format(frappe.bold(company)))
diff --git a/erpnext/setup/doctype/sales_partner/sales_partner.py b/erpnext/setup/doctype/sales_partner/sales_partner.py
index 1047360..a8a965d 100644
--- a/erpnext/setup/doctype/sales_partner/sales_partner.py
+++ b/erpnext/setup/doctype/sales_partner/sales_partner.py
@@ -54,25 +54,30 @@
 			self.partner_website = "http://" + self.partner_website
 
 	def get_context(self, context):
-		address = frappe.db.get_value(
-			"Address", {"sales_partner": self.name, "is_primary_address": 1}, "*", as_dict=True
+		address_names = frappe.db.get_all(
+			"Dynamic Link",
+			filters={"link_doctype": "Sales Partner", "link_name": self.name, "parenttype": "Address"},
+			pluck=["parent"],
 		)
-		if address:
-			city_state = ", ".join(filter(None, [address.city, address.state]))
-			address_rows = [
-				address.address_line1,
-				address.address_line2,
-				city_state,
-				address.pincode,
-				address.country,
-			]
 
-			context.update(
+		addresses = []
+		for address_name in address_names:
+			address_doc = frappe.get_doc("Address", address_name)
+			city_state = ", ".join([item for item in [address_doc.city, address_doc.state] if item])
+			address_rows = [
+				address_doc.address_line1,
+				address_doc.address_line2,
+				city_state,
+				address_doc.pincode,
+				address_doc.country,
+			]
+			addresses.append(
 				{
-					"email": address.email_id,
+					"email": address_doc.email_id,
 					"partner_address": filter_strip_join(address_rows, "\n<br>"),
-					"phone": filter_strip_join(cstr(address.phone).split(","), "\n<br>"),
+					"phone": filter_strip_join(cstr(address_doc.phone).split(","), "\n<br>"),
 				}
 			)
 
+		context["addresses"] = addresses
 		return context
diff --git a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
index 844e786..432438b 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/test_transaction_deletion_record.py
@@ -29,6 +29,7 @@
 		for i in range(5):
 			create_task("Dunder Mifflin Paper Co")
 		tdr = create_transaction_deletion_doc("Dunder Mifflin Paper Co")
+		tdr.reload()
 		for doctype in tdr.doctypes:
 			if doctype.doctype_name == "Task":
 				self.assertEqual(doctype.no_of_docs, 5)
@@ -60,7 +61,9 @@
 def create_transaction_deletion_doc(company):
 	tdr = frappe.get_doc({"doctype": "Transaction Deletion Record", "company": company})
 	tdr.insert()
+	tdr.process_in_single_transaction = True
 	tdr.submit()
+	tdr.start_deletion_tasks()
 	return tdr
 
 
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js
index 527c753..9aa0278 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.js
@@ -10,20 +10,24 @@
 				callback: function (r) {
 					doctypes_to_be_ignored_array = r.message;
 					populate_doctypes_to_be_ignored(doctypes_to_be_ignored_array, frm);
-					frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false);
 					frm.refresh_field("doctypes_to_be_ignored");
 				},
 			});
 		}
-
-		frm.get_field("doctypes_to_be_ignored").grid.cannot_add_rows = true;
-		frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false);
-		frm.refresh_field("doctypes_to_be_ignored");
 	},
 
 	refresh: function (frm) {
-		frm.fields_dict["doctypes_to_be_ignored"].grid.set_column_disp("no_of_docs", false);
-		frm.refresh_field("doctypes_to_be_ignored");
+		if (frm.doc.docstatus == 1 && ["Queued", "Failed"].find((x) => x == frm.doc.status)) {
+			let execute_btn = frm.doc.status == "Queued" ? __("Start Deletion") : __("Retry");
+
+			frm.add_custom_button(execute_btn, () => {
+				// Entry point for chain of events
+				frm.call({
+					method: "start_deletion_tasks",
+					doc: frm.doc,
+				});
+			});
+		}
 	},
 });
 
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
index 23e5947..b9f911d 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.json
@@ -7,10 +7,21 @@
  "engine": "InnoDB",
  "field_order": [
   "company",
+  "section_break_qpwb",
+  "status",
+  "error_log",
+  "tasks_section",
+  "delete_bin_data",
+  "delete_leads_and_addresses",
+  "reset_company_default_values",
+  "clear_notifications",
+  "initialize_doctypes_table",
+  "delete_transactions",
+  "section_break_tbej",
   "doctypes",
   "doctypes_to_be_ignored",
   "amended_from",
-  "status"
+  "process_in_single_transaction"
  ],
  "fields": [
   {
@@ -25,14 +36,16 @@
    "fieldname": "doctypes",
    "fieldtype": "Table",
    "label": "Summary",
-   "options": "Transaction Deletion Record Item",
+   "no_copy": 1,
+   "options": "Transaction Deletion Record Details",
    "read_only": 1
   },
   {
    "fieldname": "doctypes_to_be_ignored",
    "fieldtype": "Table",
    "label": "Excluded DocTypes",
-   "options": "Transaction Deletion Record Item"
+   "options": "Transaction Deletion Record Item",
+   "read_only": 1
   },
   {
    "fieldname": "amended_from",
@@ -46,18 +59,96 @@
   {
    "fieldname": "status",
    "fieldtype": "Select",
-   "hidden": 1,
    "label": "Status",
-   "options": "Draft\nCompleted"
+   "no_copy": 1,
+   "options": "Queued\nRunning\nFailed\nCompleted\nCancelled",
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_tbej",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "tasks_section",
+   "fieldtype": "Section Break",
+   "label": "Tasks"
+  },
+  {
+   "default": "0",
+   "fieldname": "delete_bin_data",
+   "fieldtype": "Check",
+   "label": "Delete Bins",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "delete_leads_and_addresses",
+   "fieldtype": "Check",
+   "label": "Delete Leads and Addresses",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "clear_notifications",
+   "fieldtype": "Check",
+   "label": "Clear Notifications",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "reset_company_default_values",
+   "fieldtype": "Check",
+   "label": "Reset Company Default Values",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "delete_transactions",
+   "fieldtype": "Check",
+   "label": "Delete Transactions",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "initialize_doctypes_table",
+   "fieldtype": "Check",
+   "label": "Initialize Summary Table",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "depends_on": "eval: doc.error_log",
+   "fieldname": "error_log",
+   "fieldtype": "Long Text",
+   "label": "Error Log"
+  },
+  {
+   "fieldname": "section_break_qpwb",
+   "fieldtype": "Section Break"
+  },
+  {
+   "default": "0",
+   "fieldname": "process_in_single_transaction",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Process in Single Transaction",
+   "no_copy": 1,
+   "read_only": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2021-08-04 20:15:59.071493",
+ "modified": "2024-03-21 10:29:19.456413",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Transaction Deletion Record",
+ "naming_rule": "Expression (old style)",
  "owner": "Administrator",
  "permissions": [
   {
@@ -76,5 +167,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
index 88c4b07..00fad5f 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record.py
@@ -1,12 +1,14 @@
 # Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
+from collections import OrderedDict
 
 import frappe
 from frappe import _, qb
 from frappe.desk.notifications import clear_notifications
 from frappe.model.document import Document
-from frappe.utils import cint, create_batch
+from frappe.utils import cint, comma_and, create_batch, get_link_to_form
+from frappe.utils.background_jobs import get_job, is_job_enqueued
 
 
 class TransactionDeletionRecord(Document):
@@ -18,20 +20,42 @@
 	if TYPE_CHECKING:
 		from frappe.types import DF
 
+		from erpnext.accounts.doctype.transaction_deletion_record_details.transaction_deletion_record_details import (
+			TransactionDeletionRecordDetails,
+		)
 		from erpnext.setup.doctype.transaction_deletion_record_item.transaction_deletion_record_item import (
 			TransactionDeletionRecordItem,
 		)
 
 		amended_from: DF.Link | None
+		clear_notifications: DF.Check
 		company: DF.Link
-		doctypes: DF.Table[TransactionDeletionRecordItem]
+		delete_bin_data: DF.Check
+		delete_leads_and_addresses: DF.Check
+		delete_transactions: DF.Check
+		doctypes: DF.Table[TransactionDeletionRecordDetails]
 		doctypes_to_be_ignored: DF.Table[TransactionDeletionRecordItem]
-		status: DF.Literal["Draft", "Completed"]
+		error_log: DF.LongText | None
+		initialize_doctypes_table: DF.Check
+		process_in_single_transaction: DF.Check
+		reset_company_default_values: DF.Check
+		status: DF.Literal["Queued", "Running", "Failed", "Completed", "Cancelled"]
 	# end: auto-generated types
 
 	def __init__(self, *args, **kwargs):
 		super(TransactionDeletionRecord, self).__init__(*args, **kwargs)
 		self.batch_size = 5000
+		# Tasks are listed by their execution order
+		self.task_to_internal_method_map = OrderedDict(
+			{
+				"Delete Bins": "delete_bins",
+				"Delete Leads and Addresses": "delete_lead_addresses",
+				"Reset Company Values": "reset_company_values",
+				"Clear Notifications": "delete_notifications",
+				"Initialize Summary Table": "initialize_doctypes_to_be_deleted_table",
+				"Delete Transactions": "delete_company_transactions",
+			}
+		)
 
 	def validate(self):
 		frappe.only_for("System Manager")
@@ -48,104 +72,266 @@
 					title=_("Not Allowed"),
 				)
 
+	def generate_job_name_for_task(self, task=None):
+		method = self.task_to_internal_method_map[task]
+		return f"{self.name}_{method}"
+
+	def generate_job_name_for_next_tasks(self, task=None):
+		job_names = []
+		current_task_idx = list(self.task_to_internal_method_map).index(task)
+		for idx, task in enumerate(self.task_to_internal_method_map.keys(), 0):
+			# generate job_name for next tasks
+			if idx > current_task_idx:
+				job_names.append(self.generate_job_name_for_task(task))
+		return job_names
+
+	def generate_job_name_for_all_tasks(self):
+		job_names = []
+		for task in self.task_to_internal_method_map.keys():
+			job_names.append(self.generate_job_name_for_task(task))
+		return job_names
+
 	def before_submit(self):
+		if queued_docs := frappe.db.get_all(
+			"Transaction Deletion Record",
+			filters={"company": self.company, "status": ("in", ["Running", "Queued"]), "docstatus": 1},
+			pluck="name",
+		):
+			frappe.throw(
+				_(
+					"Cannot enqueue multi docs for one company. {0} is already queued/running for company: {1}"
+				).format(
+					comma_and([get_link_to_form("Transaction Deletion Record", x) for x in queued_docs]),
+					frappe.bold(self.company),
+				)
+			)
+
 		if not self.doctypes_to_be_ignored:
 			self.populate_doctypes_to_be_ignored_table()
 
-		self.delete_bins()
-		self.delete_lead_addresses()
-		self.reset_company_values()
-		clear_notifications()
-		self.delete_company_transactions()
+	def reset_task_flags(self):
+		self.clear_notifications = 0
+		self.delete_bin_data = 0
+		self.delete_leads_and_addresses = 0
+		self.delete_transactions = 0
+		self.initialize_doctypes_table = 0
+		self.reset_company_default_values = 0
+
+	def before_save(self):
+		self.status = ""
+		self.doctypes.clear()
+		self.reset_task_flags()
+
+	def on_submit(self):
+		self.db_set("status", "Queued")
+
+	def on_cancel(self):
+		self.db_set("status", "Cancelled")
+
+	def enqueue_task(self, task: str | None = None):
+		if task and task in self.task_to_internal_method_map:
+			# make sure that none of next tasks are already running
+			job_names = self.generate_job_name_for_next_tasks(task=task)
+			self.validate_running_task_for_doc(job_names=job_names)
+
+			# Generate Job Id to uniquely identify each task for this document
+			job_id = self.generate_job_name_for_task(task)
+
+			if self.process_in_single_transaction:
+				self.execute_task(task_to_execute=task)
+			else:
+				frappe.enqueue(
+					"frappe.utils.background_jobs.run_doc_method",
+					doctype=self.doctype,
+					name=self.name,
+					doc_method="execute_task",
+					job_id=job_id,
+					queue="long",
+					enqueue_after_commit=True,
+					task_to_execute=task,
+				)
+
+	def execute_task(self, task_to_execute: str | None = None):
+		if task_to_execute:
+			method = self.task_to_internal_method_map[task_to_execute]
+			if task := getattr(self, method, None):
+				try:
+					task()
+				except Exception as err:
+					frappe.db.rollback()
+					traceback = frappe.get_traceback(with_context=True)
+					if traceback:
+						message = "Traceback: <br>" + traceback
+						frappe.db.set_value(self.doctype, self.name, "error_log", message)
+					frappe.db.set_value(self.doctype, self.name, "status", "Failed")
+
+	def delete_notifications(self):
+		self.validate_doc_status()
+		if not self.clear_notifications:
+			clear_notifications()
+			self.db_set("clear_notifications", 1)
+		self.enqueue_task(task="Initialize Summary Table")
 
 	def populate_doctypes_to_be_ignored_table(self):
 		doctypes_to_be_ignored_list = get_doctypes_to_be_ignored()
 		for doctype in doctypes_to_be_ignored_list:
 			self.append("doctypes_to_be_ignored", {"doctype_name": doctype})
 
-	def delete_bins(self):
-		frappe.db.sql(
-			"""delete from `tabBin` where warehouse in
-				(select name from tabWarehouse where company=%s)""",
-			self.company,
-		)
+	def validate_running_task_for_doc(self, job_names: list = None):
+		# at most only one task should be runnning
+		running_tasks = []
+		for x in job_names:
+			if is_job_enqueued(x):
+				running_tasks.append(get_job(x).get_id())
 
-	def delete_lead_addresses(self):
-		"""Delete addresses to which leads are linked"""
-		leads = frappe.get_all("Lead", filters={"company": self.company})
-		leads = ["'%s'" % row.get("name") for row in leads]
-		addresses = []
-		if leads:
-			addresses = frappe.db.sql_list(
-				"""select parent from `tabDynamic Link` where link_name
-				in ({leads})""".format(
-					leads=",".join(leads)
+		if running_tasks:
+			frappe.throw(
+				_("{0} is already running for {1}").format(
+					comma_and([get_link_to_form("RQ Job", x) for x in running_tasks]), self.name
 				)
 			)
 
-			if addresses:
-				addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
-
-				frappe.db.sql(
-					"""delete from `tabAddress` where name in ({addresses}) and
-					name not in (select distinct dl1.parent from `tabDynamic Link` dl1
-					inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
-					and dl1.link_doctype<>dl2.link_doctype)""".format(
-						addresses=",".join(addresses)
-					)
+	def validate_doc_status(self):
+		if self.status != "Running":
+			frappe.throw(
+				_("{0} is not running. Cannot trigger events for this Document").format(
+					get_link_to_form("Transaction Deletion Record", self.name)
 				)
+			)
 
-				frappe.db.sql(
-					"""delete from `tabDynamic Link` where link_doctype='Lead'
-					and parenttype='Address' and link_name in ({leads})""".format(
+	@frappe.whitelist()
+	def start_deletion_tasks(self):
+		# This method is the entry point for the chain of events that follow
+		self.db_set("status", "Running")
+		self.enqueue_task(task="Delete Bins")
+
+	def delete_bins(self):
+		self.validate_doc_status()
+		if not self.delete_bin_data:
+			frappe.db.sql(
+				"""delete from `tabBin` where warehouse in
+					(select name from tabWarehouse where company=%s)""",
+				self.company,
+			)
+			self.db_set("delete_bin_data", 1)
+		self.enqueue_task(task="Delete Leads and Addresses")
+
+	def delete_lead_addresses(self):
+		"""Delete addresses to which leads are linked"""
+		self.validate_doc_status()
+		if not self.delete_leads_and_addresses:
+			leads = frappe.get_all("Lead", filters={"company": self.company})
+			leads = ["'%s'" % row.get("name") for row in leads]
+			addresses = []
+			if leads:
+				addresses = frappe.db.sql_list(
+					"""select parent from `tabDynamic Link` where link_name
+					in ({leads})""".format(
 						leads=",".join(leads)
 					)
 				)
 
-			frappe.db.sql(
-				"""update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format(
-					leads=",".join(leads)
+				if addresses:
+					addresses = ["%s" % frappe.db.escape(addr) for addr in addresses]
+
+					frappe.db.sql(
+						"""delete from `tabAddress` where name in ({addresses}) and
+						name not in (select distinct dl1.parent from `tabDynamic Link` dl1
+						inner join `tabDynamic Link` dl2 on dl1.parent=dl2.parent
+						and dl1.link_doctype<>dl2.link_doctype)""".format(
+							addresses=",".join(addresses)
+						)
+					)
+
+					frappe.db.sql(
+						"""delete from `tabDynamic Link` where link_doctype='Lead'
+						and parenttype='Address' and link_name in ({leads})""".format(
+							leads=",".join(leads)
+						)
+					)
+
+				frappe.db.sql(
+					"""update `tabCustomer` set lead_name=NULL where lead_name in ({leads})""".format(
+						leads=",".join(leads)
+					)
 				)
-			)
+			self.db_set("delete_leads_and_addresses", 1)
+		self.enqueue_task(task="Reset Company Values")
 
 	def reset_company_values(self):
-		company_obj = frappe.get_doc("Company", self.company)
-		company_obj.total_monthly_sales = 0
-		company_obj.sales_monthly_history = None
-		company_obj.save()
+		self.validate_doc_status()
+		if not self.reset_company_default_values:
+			company_obj = frappe.get_doc("Company", self.company)
+			company_obj.total_monthly_sales = 0
+			company_obj.sales_monthly_history = None
+			company_obj.save()
+			self.db_set("reset_company_default_values", 1)
+		self.enqueue_task(task="Clear Notifications")
+
+	def initialize_doctypes_to_be_deleted_table(self):
+		self.validate_doc_status()
+		if not self.initialize_doctypes_table:
+			doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
+			docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
+			tables = self.get_all_child_doctypes()
+			for docfield in docfields:
+				if docfield["parent"] != self.doctype:
+					no_of_docs = self.get_number_of_docs_linked_with_specified_company(
+						docfield["parent"], docfield["fieldname"]
+					)
+					if no_of_docs > 0:
+						# Initialize
+						self.populate_doctypes_table(tables, docfield["parent"], docfield["fieldname"], 0)
+			self.db_set("initialize_doctypes_table", 1)
+		self.enqueue_task(task="Delete Transactions")
 
 	def delete_company_transactions(self):
-		doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
-		docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
+		self.validate_doc_status()
+		if not self.delete_transactions:
+			doctypes_to_be_ignored_list = self.get_doctypes_to_be_ignored_list()
+			docfields = self.get_doctypes_with_company_field(doctypes_to_be_ignored_list)
 
-		tables = self.get_all_child_doctypes()
-		for docfield in docfields:
-			if docfield["parent"] != self.doctype:
-				no_of_docs = self.get_number_of_docs_linked_with_specified_company(
-					docfield["parent"], docfield["fieldname"]
-				)
-
-				if no_of_docs > 0:
-					self.delete_version_log(docfield["parent"], docfield["fieldname"])
-
-					reference_docs = frappe.get_all(
-						docfield["parent"], filters={docfield["fieldname"]: self.company}
+			tables = self.get_all_child_doctypes()
+			for docfield in self.doctypes:
+				if docfield.doctype_name != self.doctype and not docfield.done:
+					no_of_docs = self.get_number_of_docs_linked_with_specified_company(
+						docfield.doctype_name, docfield.docfield_name
 					)
-					reference_doc_names = [r.name for r in reference_docs]
+					if no_of_docs > 0:
+						reference_docs = frappe.get_all(
+							docfield.doctype_name, filters={docfield.docfield_name: self.company}, limit=self.batch_size
+						)
+						reference_doc_names = [r.name for r in reference_docs]
 
-					self.delete_communications(docfield["parent"], reference_doc_names)
-					self.delete_comments(docfield["parent"], reference_doc_names)
-					self.unlink_attachments(docfield["parent"], reference_doc_names)
+						self.delete_version_log(docfield.doctype_name, reference_doc_names)
+						self.delete_communications(docfield.doctype_name, reference_doc_names)
+						self.delete_comments(docfield.doctype_name, reference_doc_names)
+						self.unlink_attachments(docfield.doctype_name, reference_doc_names)
+						self.delete_child_tables(docfield.doctype_name, reference_doc_names)
+						self.delete_docs_linked_with_specified_company(docfield.doctype_name, reference_doc_names)
+						processed = int(docfield.no_of_docs) + len(reference_doc_names)
+						frappe.db.set_value(docfield.doctype, docfield.name, "no_of_docs", processed)
+					else:
+						# reset naming series
+						naming_series = frappe.db.get_value("DocType", docfield.doctype_name, "autoname")
+						if naming_series:
+							if "#" in naming_series:
+								self.update_naming_series(naming_series, docfield.doctype_name)
+						frappe.db.set_value(docfield.doctype, docfield.name, "done", 1)
 
-					self.populate_doctypes_table(tables, docfield["parent"], no_of_docs)
-
-					self.delete_child_tables(docfield["parent"], docfield["fieldname"])
-					self.delete_docs_linked_with_specified_company(docfield["parent"], docfield["fieldname"])
-
-					naming_series = frappe.db.get_value("DocType", docfield["parent"], "autoname")
-					if naming_series:
-						if "#" in naming_series:
-							self.update_naming_series(naming_series, docfield["parent"])
+			pending_doctypes = frappe.db.get_all(
+				"Transaction Deletion Record Details",
+				filters={"parent": self.name, "done": 0},
+				pluck="doctype_name",
+			)
+			if pending_doctypes:
+				# as method is enqueued after commit, calling itself will not make validate_doc_status to throw
+				# recursively call this task to delete all transactions
+				self.enqueue_task(task="Delete Transactions")
+			else:
+				self.db_set("status", "Completed")
+				self.db_set("delete_transactions", 1)
+				self.db_set("error_log", None)
 
 	def get_doctypes_to_be_ignored_list(self):
 		singles = frappe.get_all("DocType", filters={"issingle": 1}, pluck="name")
@@ -174,25 +360,24 @@
 	def get_number_of_docs_linked_with_specified_company(self, doctype, company_fieldname):
 		return frappe.db.count(doctype, {company_fieldname: self.company})
 
-	def populate_doctypes_table(self, tables, doctype, no_of_docs):
+	def populate_doctypes_table(self, tables, doctype, fieldname, no_of_docs):
+		self.flags.ignore_validate_update_after_submit = True
 		if doctype not in tables:
-			self.append("doctypes", {"doctype_name": doctype, "no_of_docs": no_of_docs})
+			self.append(
+				"doctypes", {"doctype_name": doctype, "docfield_name": fieldname, "no_of_docs": no_of_docs}
+			)
+		self.save(ignore_permissions=True)
 
-	def delete_child_tables(self, doctype, company_fieldname):
-		parent_docs_to_be_deleted = frappe.get_all(
-			doctype, {company_fieldname: self.company}, pluck="name"
-		)
-
+	def delete_child_tables(self, doctype, reference_doc_names):
 		child_tables = frappe.get_all(
 			"DocField", filters={"fieldtype": "Table", "parent": doctype}, pluck="options"
 		)
 
-		for batch in create_batch(parent_docs_to_be_deleted, self.batch_size):
-			for table in child_tables:
-				frappe.db.delete(table, {"parent": ["in", batch]})
+		for table in child_tables:
+			frappe.db.delete(table, {"parent": ["in", reference_doc_names]})
 
-	def delete_docs_linked_with_specified_company(self, doctype, company_fieldname):
-		frappe.db.delete(doctype, {company_fieldname: self.company})
+	def delete_docs_linked_with_specified_company(self, doctype, reference_doc_names):
+		frappe.db.delete(doctype, {"name": ("in", reference_doc_names)})
 
 	def update_naming_series(self, naming_series, doctype_name):
 		if "." in naming_series:
@@ -213,17 +398,11 @@
 
 		frappe.db.sql("""update `tabSeries` set current = %s where name=%s""", (last, prefix))
 
-	def delete_version_log(self, doctype, company_fieldname):
-		dt = qb.DocType(doctype)
-		names = qb.from_(dt).select(dt.name).where(dt[company_fieldname] == self.company).run(as_list=1)
-		names = [x[0] for x in names]
-
-		if names:
-			versions = qb.DocType("Version")
-			for batch in create_batch(names, self.batch_size):
-				qb.from_(versions).delete().where(
-					(versions.ref_doctype == doctype) & (versions.docname.isin(batch))
-				).run()
+	def delete_version_log(self, doctype, docnames):
+		versions = qb.DocType("Version")
+		qb.from_(versions).delete().where(
+			(versions.ref_doctype == doctype) & (versions.docname.isin(docnames))
+		).run()
 
 	def delete_communications(self, doctype, reference_doc_names):
 		communications = frappe.get_all(
@@ -295,3 +474,34 @@
 	doctypes_to_be_ignored.extend(frappe.get_hooks("company_data_to_be_ignored") or [])
 
 	return doctypes_to_be_ignored
+
+
+@frappe.whitelist()
+def is_deletion_doc_running(company: str | None = None, err_msg: str | None = None):
+	if company:
+		if running_deletion_jobs := frappe.db.get_all(
+			"Transaction Deletion Record",
+			filters={"docstatus": 1, "company": company, "status": "Running"},
+		):
+			if not err_msg:
+				err_msg = ""
+			frappe.throw(
+				title=_("Deletion in Progress!"),
+				msg=_("Transaction Deletion Document: {0} is running for this Company. {1}").format(
+					get_link_to_form("Transaction Deletion Record", running_deletion_jobs[0].name), err_msg
+				),
+			)
+
+
+def check_for_running_deletion_job(doc, method=None):
+	# Check if DocType has 'company' field
+	df = qb.DocType("DocField")
+	if (
+		not_allowed := qb.from_(df)
+		.select(df.parent)
+		.where((df.fieldname == "company") & (df.parent == doc.doctype))
+		.run()
+	):
+		is_deletion_doc_running(
+			doc.company, _("Cannot make any transactions until the deletion job is completed")
+		)
diff --git a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js
index 08a35df..285cb6d 100644
--- a/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js
+++ b/erpnext/setup/doctype/transaction_deletion_record/transaction_deletion_record_list.js
@@ -2,11 +2,15 @@
 // License: GNU General Public License v3. See license.txt
 
 frappe.listview_settings["Transaction Deletion Record"] = {
+	add_fields: ["status"],
 	get_indicator: function (doc) {
-		if (doc.docstatus == 0) {
-			return [__("Draft"), "red"];
-		} else {
-			return [__("Completed"), "green"];
-		}
+		let colors = {
+			Queued: "orange",
+			Completed: "green",
+			Running: "blue",
+			Failed: "red",
+		};
+		let status = doc.status;
+		return [__(status), colors[status], "status,=," + status];
 	},
 };
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
index be0be94..89db636 100644
--- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.json
@@ -5,8 +5,7 @@
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
-  "doctype_name",
-  "no_of_docs"
+  "doctype_name"
  ],
  "fields": [
   {
@@ -16,18 +15,12 @@
    "label": "DocType",
    "options": "DocType",
    "reqd": 1
-  },
-  {
-   "fieldname": "no_of_docs",
-   "fieldtype": "Data",
-   "in_list_view": 1,
-   "label": "Number of Docs"
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-05-08 23:10:46.166744",
+ "modified": "2024-02-04 10:56:27.413691",
  "modified_by": "Administrator",
  "module": "Setup",
  "name": "Transaction Deletion Record Item",
@@ -35,5 +28,6 @@
  "permissions": [],
  "sort_field": "modified",
  "sort_order": "DESC",
+ "states": [],
  "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py
index f154cdb..9066607 100644
--- a/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py
+++ b/erpnext/setup/doctype/transaction_deletion_record_item/transaction_deletion_record_item.py
@@ -16,7 +16,6 @@
 		from frappe.types import DF
 
 		doctype_name: DF.Link
-		no_of_docs: DF.Data | None
 		parent: DF.Data
 		parentfield: DF.Data
 		parenttype: DF.Data
diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js
index 0f0221f..aec752a 100644
--- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js
+++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.js
@@ -8,7 +8,7 @@
 	},
 
 	generate_closing_balance(frm) {
-		if (in_list(["Queued", "Failed"], frm.doc.status)) {
+		if (["Queued", "Failed"].includes(frm.doc.status)) {
 			frm.add_custom_button(__("Generate Closing Stock Balance"), () => {
 				frm.call({
 					method: "enqueue_job",
diff --git a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
index 1c70183..e99a0b1 100644
--- a/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
+++ b/erpnext/stock/doctype/closing_stock_balance/closing_stock_balance.py
@@ -123,7 +123,9 @@
 			)
 		)
 
-		create_json_gz_file({"columns": columns, "data": data}, self.doctype, self.name)
+		create_json_gz_file(
+			{"columns": columns, "data": data}, self.doctype, self.name, "closing-stock-balance"
+		)
 
 	def get_prepared_data(self):
 		if attachments := get_attachments(self.doctype, self.name):
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index c04d5c1..23d0adc 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -3,6 +3,8 @@
 
 cur_frm.add_fetch("customer", "tax_id", "tax_id");
 
+cur_frm.cscript.tax_table = "Sales Taxes and Charges";
+
 frappe.provide("erpnext.stock");
 frappe.provide("erpnext.stock.delivery_note");
 frappe.provide("erpnext.accounts.dimensions");
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.json b/erpnext/stock/doctype/delivery_note/delivery_note.json
index d07a825..87c3333 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.json
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.json
@@ -680,7 +680,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "label": "Taxes and Charges Calculation",
    "no_copy": 1,
    "oldfieldtype": "HTML",
@@ -1397,7 +1397,7 @@
  "idx": 146,
  "is_submittable": 1,
  "links": [],
- "modified": "2024-03-05 11:58:47.784349",
+ "modified": "2024-03-20 16:05:02.854990",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Delivery Note",
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index a3903a3..e17a0a2 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -76,7 +76,7 @@
 		ignore_pricing_rule: DF.Check
 		in_words: DF.Data | None
 		incoterm: DF.Link | None
-		installation_status: DF.Literal
+		installation_status: DF.Literal[None]
 		instructions: DF.Text | None
 		inter_company_reference: DF.Link | None
 		is_internal_customer: DF.Check
@@ -90,7 +90,7 @@
 		named_place: DF.Data | None
 		naming_series: DF.Literal["MAT-DN-.YYYY.-", "MAT-DN-RET-.YYYY.-"]
 		net_total: DF.Currency
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		packed_items: DF.Table[PackedItem]
 		per_billed: DF.Percent
 		per_installed: DF.Percent
@@ -251,6 +251,7 @@
 	def validate(self):
 		self.validate_posting_time()
 		super(DeliveryNote, self).validate()
+		self.validate_references()
 		self.set_status()
 		self.so_required()
 		self.validate_proj_cust()
@@ -333,6 +334,7 @@
 							"type_of_transaction": "Outward",
 							"serial_and_batch_bundle": bundle_id,
 							"item_code": item.get("item_code"),
+							"warehouse": item.get("warehouse"),
 						}
 					)
 
@@ -340,6 +342,58 @@
 
 					item.serial_and_batch_bundle = cls_obj.serial_and_batch_bundle
 
+	def validate_references(self):
+		self.validate_sales_order_references()
+		self.validate_sales_invoice_references()
+
+	def validate_sales_order_references(self):
+		err_msg = ""
+		for item in self.items:
+			if (item.against_sales_order and not item.so_detail) or (
+				not item.against_sales_order and item.so_detail
+			):
+				if not item.against_sales_order:
+					err_msg += (
+						_("'Sales Order' reference ({1}) is missing in row {0}").format(
+							frappe.bold(item.idx), frappe.bold("against_sales_order")
+						)
+						+ "<br>"
+					)
+				else:
+					err_msg += (
+						_("'Sales Order Item' reference ({1}) is missing in row {0}").format(
+							frappe.bold(item.idx), frappe.bold("so_detail")
+						)
+						+ "<br>"
+					)
+
+		if err_msg:
+			frappe.throw(err_msg, title=_("References to Sales Orders are Incomplete"))
+
+	def validate_sales_invoice_references(self):
+		err_msg = ""
+		for item in self.items:
+			if (item.against_sales_invoice and not item.si_detail) or (
+				not item.against_sales_invoice and item.si_detail
+			):
+				if not item.against_sales_invoice:
+					err_msg += (
+						_("'Sales Invoice' reference ({1}) is missing in row {0}").format(
+							frappe.bold(item.idx), frappe.bold("against_sales_invoice")
+						)
+						+ "<br>"
+					)
+				else:
+					err_msg += (
+						_("'Sales Invoice Item' reference ({1}) is missing in row {0}").format(
+							frappe.bold(item.idx), frappe.bold("si_detail")
+						)
+						+ "<br>"
+					)
+
+		if err_msg:
+			frappe.throw(err_msg, title=_("References to Sales Invoices are Incomplete"))
+
 	def validate_proj_cust(self):
 		"""check for does customer belong to same project as entered.."""
 		if self.project and self.customer:
diff --git a/erpnext/stock/doctype/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 293ef9f..905287d 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -824,6 +824,15 @@
 		dn.cancel()
 		self.assertEqual(dn.status, "Cancelled")
 
+	def test_sales_order_reference_validation(self):
+		so = make_sales_order(po_no="12345")
+		dn = create_dn_against_so(so.name, delivered_qty=2, do_not_submit=True)
+		dn.items[0].against_sales_order = None
+		self.assertRaises(frappe.ValidationError, dn.save)
+		dn.reload()
+		dn.items[0].so_detail = None
+		self.assertRaises(frappe.ValidationError, dn.save)
+
 	def test_dn_billing_status_case1(self):
 		# SO -> DN -> SI
 		so = make_sales_order(po_no="12345")
@@ -1099,9 +1108,30 @@
 		dn.load_from_db()
 
 		batch_no = get_batch_from_bundle(dn.packed_items[0].serial_and_batch_bundle)
+		packed_name = dn.packed_items[0].name
 		self.assertTrue(batch_no)
 
+		dn.cancel()
+
+		# Cancel the reposting entry
+		reposting_entries = frappe.get_all("Repost Item Valuation", filters={"docstatus": 1})
+		for entry in reposting_entries:
+			doc = frappe.get_doc("Repost Item Valuation", entry.name)
+			doc.cancel()
+			doc.delete()
+
+		frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 1)
+
+		dn.reload()
+		dn.delete()
+
+		bundle = frappe.db.get_value(
+			"Serial and Batch Bundle", {"voucher_detail_no": packed_name}, "name"
+		)
+		self.assertFalse(bundle)
+
 		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+		frappe.db.set_single_value("Accounts Settings", "delete_linked_ledger_entries", 0)
 
 	def test_payment_terms_are_fetched_when_creating_sales_invoice(self):
 		from erpnext.accounts.doctype.payment_entry.test_payment_entry import (
diff --git a/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js b/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js
index 230107c..65a1be3 100644
--- a/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js
+++ b/erpnext/stock/doctype/delivery_trip/delivery_trip_list.js
@@ -1,9 +1,9 @@
 frappe.listview_settings["Delivery Trip"] = {
 	add_fields: ["status"],
 	get_indicator: function (doc) {
-		if (in_list(["Cancelled", "Draft"], doc.status)) {
+		if (["Cancelled", "Draft"].includes(doc.status)) {
 			return [__(doc.status), "red", "status,=," + doc.status];
-		} else if (in_list(["In Transit", "Scheduled"], doc.status)) {
+		} else if (["In Transit", "Scheduled"].includes(doc.status)) {
 			return [__(doc.status), "orange", "status,=," + doc.status];
 		} else if (doc.status === "Completed") {
 			return [__(doc.status), "green", "status,=," + doc.status];
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 7a38024..5310a0f 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -406,14 +406,6 @@
 			};
 		};
 
-		frm.fields_dict.customer_items.grid.get_field("customer_name").get_query = function (doc, cdt, cdn) {
-			return { query: "erpnext.controllers.queries.customer_query" };
-		};
-
-		frm.fields_dict.supplier_items.grid.get_field("supplier").get_query = function (doc, cdt, cdn) {
-			return { query: "erpnext.controllers.queries.supplier_query" };
-		};
-
 		frm.fields_dict["item_defaults"].grid.get_field("default_warehouse").get_query = function (
 			doc,
 			cdt,
diff --git a/erpnext/stock/doctype/item_price/item_price.json b/erpnext/stock/doctype/item_price/item_price.json
index 707f346..bf944a4 100644
--- a/erpnext/stock/doctype/item_price/item_price.json
+++ b/erpnext/stock/doctype/item_price/item_price.json
@@ -104,7 +104,8 @@
    "in_standard_filter": 1,
    "label": "Price List",
    "options": "Price List",
-   "reqd": 1
+   "reqd": 1,
+   "search_index": 1
   },
   {
    "bold": 1,
@@ -220,7 +221,7 @@
  "idx": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2024-01-30 14:02:19.304854",
+ "modified": "2024-03-13 12:23:39.630290",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Item Price",
diff --git a/erpnext/stock/doctype/item_price/item_price.py b/erpnext/stock/doctype/item_price/item_price.py
index de2add6..25a28b4 100644
--- a/erpnext/stock/doctype/item_price/item_price.py
+++ b/erpnext/stock/doctype/item_price/item_price.py
@@ -7,7 +7,6 @@
 from frappe.model.document import Document
 from frappe.query_builder import Criterion
 from frappe.query_builder.functions import Cast_
-from frappe.utils import getdate
 
 
 class ItemPriceDuplicateItem(frappe.ValidationError):
@@ -46,7 +45,7 @@
 
 	def validate(self):
 		self.validate_item()
-		self.validate_dates()
+		self.validate_from_to_dates("valid_from", "valid_upto")
 		self.update_price_list_details()
 		self.update_item_details()
 		self.check_duplicates()
@@ -56,11 +55,6 @@
 		if not frappe.db.exists("Item", self.item_code):
 			frappe.throw(_("Item {0} not found.").format(self.item_code))
 
-	def validate_dates(self):
-		if self.valid_from and self.valid_upto:
-			if getdate(self.valid_from) > getdate(self.valid_upto):
-				frappe.throw(_("Valid From Date must be lesser than Valid Up To Date."))
-
 	def update_price_list_details(self):
 		if self.price_list:
 			price_list_details = frappe.db.get_value(
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 8a1f79d..4eab7e8 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -184,7 +184,11 @@
 
 	def delink_serial_and_batch_bundle(self):
 		for row in self.locations:
-			if row.serial_and_batch_bundle:
+			if (
+				row.serial_and_batch_bundle
+				and frappe.db.get_value("Serial and Batch Bundle", row.serial_and_batch_bundle, "docstatus")
+				== 1
+			):
 				frappe.db.set_value(
 					"Serial and Batch Bundle",
 					row.serial_and_batch_bundle,
@@ -774,7 +778,7 @@
 
 	if picked_item_details:
 		for location in list(locations):
-			if location["qty"] < 1:
+			if location["qty"] < 0:
 				locations.remove(location)
 
 		total_qty_available = sum(location.get("qty") for location in locations)
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
index 997cdd0..bfac438 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.js
@@ -3,6 +3,8 @@
 
 frappe.provide("erpnext.stock");
 
+cur_frm.cscript.tax_table = "Purchase Taxes and Charges";
+
 erpnext.accounts.taxes.setup_tax_filters("Purchase Taxes and Charges");
 erpnext.accounts.taxes.setup_tax_validations("Purchase Receipt");
 erpnext.buying.setup_buying_controller();
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
index a181022..b926e98 100755
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.json
@@ -651,7 +651,7 @@
   },
   {
    "fieldname": "other_charges_calculation",
-   "fieldtype": "Long Text",
+   "fieldtype": "Text Editor",
    "label": "Taxes and Charges Calculation",
    "no_copy": 1,
    "oldfieldtype": "HTML",
@@ -1252,7 +1252,7 @@
  "idx": 261,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-12-18 17:26:41.279663",
+ "modified": "2024-03-20 16:05:31.713453",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Purchase Receipt",
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 2eec58f..034dd0a 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -87,7 +87,7 @@
 		named_place: DF.Data | None
 		naming_series: DF.Literal["MAT-PRE-.YYYY.-", "MAT-PR-RET-.YYYY.-"]
 		net_total: DF.Currency
-		other_charges_calculation: DF.LongText | None
+		other_charges_calculation: DF.TextEditor | None
 		per_billed: DF.Percent
 		per_returned: DF.Percent
 		plc_conversion_rate: DF.Float
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index fa2c21f..5cf2080 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -2559,6 +2559,280 @@
 				self.assertEqual(row.serial_no, "\n".join(serial_nos[:2]))
 				self.assertEqual(row.rejected_serial_no, serial_nos[2])
 
+	def test_internal_transfer_with_serial_batch_items_and_their_valuation(self):
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+		prepare_data_for_internal_transfer()
+
+		customer = "_Test Internal Customer 2"
+		company = "_Test Company with perpetual inventory"
+
+		batch_item_doc = make_item(
+			"_Test Batch Item For Stock Transfer",
+			{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "BT-BIFST-.####"},
+		)
+
+		serial_item_doc = make_item(
+			"_Test Serial No Item For Stock Transfer",
+			{"has_serial_no": 1, "serial_no_series": "BT-BIFST-.####"},
+		)
+
+		inward_entry = make_purchase_receipt(
+			item_code=batch_item_doc.name,
+			qty=10,
+			rate=150,
+			warehouse="Stores - TCP1",
+			company="_Test Company with perpetual inventory",
+			use_serial_batch_fields=1,
+			do_not_submit=1,
+		)
+
+		inward_entry.append(
+			"items",
+			{
+				"item_code": serial_item_doc.name,
+				"qty": 15,
+				"rate": 250,
+				"item_name": serial_item_doc.item_name,
+				"conversion_factor": 1.0,
+				"uom": serial_item_doc.stock_uom,
+				"stock_uom": serial_item_doc.stock_uom,
+				"warehouse": "Stores - TCP1",
+				"use_serial_batch_fields": 1,
+			},
+		)
+
+		inward_entry.submit()
+		inward_entry.reload()
+
+		for row in inward_entry.items:
+			self.assertTrue(row.serial_and_batch_bundle)
+
+		inter_transfer_dn = create_delivery_note(
+			item_code=inward_entry.items[0].item_code,
+			company=company,
+			customer=customer,
+			cost_center="Main - TCP1",
+			expense_account="Cost of Goods Sold - TCP1",
+			qty=10,
+			rate=500,
+			warehouse="Stores - TCP1",
+			target_warehouse="Work In Progress - TCP1",
+			batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
+			use_serial_batch_fields=1,
+			do_not_submit=1,
+		)
+
+		inter_transfer_dn.append(
+			"items",
+			{
+				"item_code": serial_item_doc.name,
+				"qty": 15,
+				"rate": 350,
+				"item_name": serial_item_doc.item_name,
+				"conversion_factor": 1.0,
+				"uom": serial_item_doc.stock_uom,
+				"stock_uom": serial_item_doc.stock_uom,
+				"warehouse": "Stores - TCP1",
+				"target_warehouse": "Work In Progress - TCP1",
+				"serial_no": "\n".join(
+					get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
+				),
+				"use_serial_batch_fields": 1,
+			},
+		)
+
+		inter_transfer_dn.submit()
+		inter_transfer_dn.reload()
+		for row in inter_transfer_dn.items:
+			if row.item_code == batch_item_doc.name:
+				self.assertEqual(row.rate, 150.0)
+			else:
+				self.assertEqual(row.rate, 250.0)
+
+			self.assertTrue(row.serial_and_batch_bundle)
+
+		inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
+		for row in inter_transfer_pr.items:
+			row.from_warehouse = "Work In Progress - TCP1"
+			row.warehouse = "Stores - TCP1"
+		inter_transfer_pr.submit()
+
+		for row in inter_transfer_pr.items:
+			if row.item_code == batch_item_doc.name:
+				self.assertEqual(row.rate, 150.0)
+			else:
+				self.assertEqual(row.rate, 250.0)
+
+			self.assertTrue(row.serial_and_batch_bundle)
+
+		inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name)
+
+		inter_transfer_pr_return.submit()
+		inter_transfer_pr_return.reload()
+		for row in inter_transfer_pr_return.items:
+			self.assertTrue(row.serial_and_batch_bundle)
+			if row.item_code == serial_item_doc.name:
+				self.assertEqual(row.rate, 250.0)
+				serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle)
+				for sn in serial_nos:
+					serial_no_details = frappe.db.get_value("Serial No", sn, ["status", "warehouse"], as_dict=1)
+					self.assertTrue(serial_no_details.status == "Active")
+					self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1")
+
+		inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name)
+		inter_transfer_dn_return.posting_date = today()
+		inter_transfer_dn_return.posting_time = nowtime()
+		for row in inter_transfer_dn_return.items:
+			row.target_warehouse = "Work In Progress - TCP1"
+
+		inter_transfer_dn_return.submit()
+		inter_transfer_dn_return.reload()
+
+		for row in inter_transfer_dn_return.items:
+			self.assertTrue(row.serial_and_batch_bundle)
+
+	def test_internal_transfer_with_serial_batch_items_without_user_serial_batch_fields(self):
+		from erpnext.controllers.sales_and_purchase_return import make_return_doc
+		from erpnext.stock.doctype.delivery_note.delivery_note import make_inter_company_purchase_receipt
+		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
+
+		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
+
+		prepare_data_for_internal_transfer()
+
+		customer = "_Test Internal Customer 2"
+		company = "_Test Company with perpetual inventory"
+
+		batch_item_doc = make_item(
+			"_Test Batch Item For Stock Transfer USE SERIAL BATCH FIELDS",
+			{"has_batch_no": 1, "create_new_batch": 1, "batch_number_series": "USBF-BT-BIFST-.####"},
+		)
+
+		serial_item_doc = make_item(
+			"_Test Serial No Item For Stock Transfer USE SERIAL BATCH FIELDS",
+			{"has_serial_no": 1, "serial_no_series": "USBF-BT-BIFST-.####"},
+		)
+
+		inward_entry = make_purchase_receipt(
+			item_code=batch_item_doc.name,
+			qty=10,
+			rate=150,
+			warehouse="Stores - TCP1",
+			company="_Test Company with perpetual inventory",
+			use_serial_batch_fields=0,
+			do_not_submit=1,
+		)
+
+		inward_entry.append(
+			"items",
+			{
+				"item_code": serial_item_doc.name,
+				"qty": 15,
+				"rate": 250,
+				"item_name": serial_item_doc.item_name,
+				"conversion_factor": 1.0,
+				"uom": serial_item_doc.stock_uom,
+				"stock_uom": serial_item_doc.stock_uom,
+				"warehouse": "Stores - TCP1",
+				"use_serial_batch_fields": 0,
+			},
+		)
+
+		inward_entry.submit()
+		inward_entry.reload()
+
+		for row in inward_entry.items:
+			self.assertTrue(row.serial_and_batch_bundle)
+
+		inter_transfer_dn = create_delivery_note(
+			item_code=inward_entry.items[0].item_code,
+			company=company,
+			customer=customer,
+			cost_center="Main - TCP1",
+			expense_account="Cost of Goods Sold - TCP1",
+			qty=10,
+			rate=500,
+			warehouse="Stores - TCP1",
+			target_warehouse="Work In Progress - TCP1",
+			batch_no=get_batch_from_bundle(inward_entry.items[0].serial_and_batch_bundle),
+			use_serial_batch_fields=0,
+			do_not_submit=1,
+		)
+
+		inter_transfer_dn.append(
+			"items",
+			{
+				"item_code": serial_item_doc.name,
+				"qty": 15,
+				"rate": 350,
+				"item_name": serial_item_doc.item_name,
+				"conversion_factor": 1.0,
+				"uom": serial_item_doc.stock_uom,
+				"stock_uom": serial_item_doc.stock_uom,
+				"warehouse": "Stores - TCP1",
+				"target_warehouse": "Work In Progress - TCP1",
+				"serial_no": "\n".join(
+					get_serial_nos_from_bundle(inward_entry.items[1].serial_and_batch_bundle)
+				),
+				"use_serial_batch_fields": 0,
+			},
+		)
+
+		inter_transfer_dn.submit()
+		inter_transfer_dn.reload()
+		for row in inter_transfer_dn.items:
+			if row.item_code == batch_item_doc.name:
+				self.assertEqual(row.rate, 150.0)
+			else:
+				self.assertEqual(row.rate, 250.0)
+
+			self.assertTrue(row.serial_and_batch_bundle)
+
+		inter_transfer_pr = make_inter_company_purchase_receipt(inter_transfer_dn.name)
+		for row in inter_transfer_pr.items:
+			row.from_warehouse = "Work In Progress - TCP1"
+			row.warehouse = "Stores - TCP1"
+		inter_transfer_pr.submit()
+
+		for row in inter_transfer_pr.items:
+			if row.item_code == batch_item_doc.name:
+				self.assertEqual(row.rate, 150.0)
+			else:
+				self.assertEqual(row.rate, 250.0)
+
+			self.assertTrue(row.serial_and_batch_bundle)
+
+		inter_transfer_pr_return = make_return_doc("Purchase Receipt", inter_transfer_pr.name)
+
+		inter_transfer_pr_return.submit()
+		inter_transfer_pr_return.reload()
+		for row in inter_transfer_pr_return.items:
+			self.assertTrue(row.serial_and_batch_bundle)
+			if row.item_code == serial_item_doc.name:
+				self.assertEqual(row.rate, 250.0)
+				serial_nos = get_serial_nos_from_bundle(row.serial_and_batch_bundle)
+				for sn in serial_nos:
+					serial_no_details = frappe.db.get_value("Serial No", sn, ["status", "warehouse"], as_dict=1)
+					self.assertTrue(serial_no_details.status == "Active")
+					self.assertEqual(serial_no_details.warehouse, "Work In Progress - TCP1")
+
+		inter_transfer_dn_return = make_return_doc("Delivery Note", inter_transfer_dn.name)
+		inter_transfer_dn_return.posting_date = today()
+		inter_transfer_dn_return.posting_time = nowtime()
+		for row in inter_transfer_dn_return.items:
+			row.target_warehouse = "Work In Progress - TCP1"
+
+		inter_transfer_dn_return.submit()
+		inter_transfer_dn_return.reload()
+
+		for row in inter_transfer_dn_return.items:
+			self.assertTrue(row.serial_and_batch_bundle)
+
+		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
 
 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/serial_and_batch_bundle/serial_and_batch_bundle.json b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
index 7a58462..59ef43e 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.json
@@ -113,6 +113,7 @@
   {
    "fieldname": "voucher_no",
    "fieldtype": "Dynamic Link",
+   "in_standard_filter": 1,
    "label": "Voucher No",
    "no_copy": 1,
    "options": "voucher_type",
@@ -250,7 +251,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-12-07 17:56:55.528563",
+ "modified": "2024-03-15 15:22:24.003486",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Bundle",
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 08cb3ca..58971e8 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
@@ -778,6 +778,10 @@
 			or_filters=or_filters,
 		)
 
+		if not vouchers and self.voucher_type == "Delivery Note":
+			frappe.db.set_value("Packed Item", self.voucher_detail_no, "serial_and_batch_bundle", None)
+			return
+
 		for voucher in vouchers:
 			if voucher.get("current_serial_and_batch_bundle"):
 				frappe.db.set_value(self.child_table, voucher.name, "current_serial_and_batch_bundle", None)
@@ -801,6 +805,7 @@
 		self.set_purchase_document_no()
 
 	def on_submit(self):
+		self.validate_batch_inventory()
 		self.validate_serial_nos_inventory()
 
 	def set_purchase_document_no(self):
@@ -827,6 +832,13 @@
 		if not self.has_batch_no:
 			return
 
+		if (
+			self.voucher_type == "Stock Reconciliation"
+			and self.type_of_transaction == "Outward"
+			and frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty") > 0
+		):
+			return
+
 		batches = [d.batch_no for d in self.entries if d.batch_no]
 		if not batches:
 			return
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index f79803c..5b321de 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -2069,6 +2069,7 @@
 			as_dict=1,
 		)
 
+		precision = frappe.get_precision("Stock Entry Detail", "qty")
 		for key, row in available_materials.items():
 			remaining_qty_to_produce = flt(wo_data.trans_qty) - flt(wo_data.produced_qty)
 			if remaining_qty_to_produce <= 0 and not self.is_return:
@@ -2091,7 +2092,8 @@
 				serial_nos = row.serial_nos[0 : cint(qty)]
 				row.serial_nos = serial_nos
 
-			self.update_item_in_stock_entry_detail(row, item, qty)
+			if flt(qty, precision) != 0.0:
+				self.update_item_in_stock_entry_detail(row, item, qty)
 
 	def update_batches_to_be_consume(self, batches, row, qty):
 		qty_to_be_consumed = qty
@@ -2609,6 +2611,7 @@
 						"type_of_transaction": "Outward",
 						"serial_and_batch_bundle": item.get("serial_and_batch_bundle"),
 						"item_code": item.get("item_code"),
+						"warehouse": item.get("t_warehouse"),
 					}
 				)
 
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 39166e2..01a43b3 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1008,6 +1008,7 @@
 				"type_of_transaction": "Inward",
 				"serial_and_batch_bundle": s2.items[0].serial_and_batch_bundle,
 				"item_code": "_Test Serialized Item",
+				"warehouse": "_Test Warehouse - _TC",
 			}
 		)
 
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
index 3a094f1..e8e82af 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.json
@@ -230,7 +230,7 @@
   },
   {
    "fieldname": "stock_queue",
-   "fieldtype": "Text",
+   "fieldtype": "Long Text",
    "label": "FIFO Stock Queue (qty, rate)",
    "oldfieldname": "fcfs_stack",
    "oldfieldtype": "Text",
@@ -360,7 +360,7 @@
  "in_create": 1,
  "index_web_pages_for_search": 1,
  "links": [],
- "modified": "2024-02-07 09:18:13.999231",
+ "modified": "2024-03-13 09:56:13.021696",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Ledger Entry",
diff --git a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
index a3e51ca..b49fe22 100644
--- a/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
+++ b/erpnext/stock/doctype/stock_ledger_entry/stock_ledger_entry.py
@@ -58,7 +58,7 @@
 		recalculate_rate: DF.Check
 		serial_and_batch_bundle: DF.Link | None
 		serial_no: DF.LongText | None
-		stock_queue: DF.Text | None
+		stock_queue: DF.LongText | None
 		stock_uom: DF.Link | None
 		stock_value: DF.Currency
 		stock_value_difference: DF.Currency
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 3356ad5..0311481 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -154,7 +154,6 @@
 					{
 						"current_serial_and_batch_bundle": sn_doc.name,
 						"current_serial_no": "",
-						"batch_no": "",
 					}
 				)
 
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index 1cb1057..12049f1 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -103,22 +103,8 @@
 	if args.customer and cint(args.is_pos):
 		out.update(get_pos_profile_item_details(args.company, args, update_data=True))
 
-	if (
-		args.get("doctype") == "Material Request"
-		and args.get("material_request_type") == "Material Transfer"
-	):
-		out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
-
-	elif out.get("warehouse"):
-		if doc and doc.get("doctype") == "Purchase Order":
-			# calculate company_total_stock only for po
-			bin_details = get_bin_details(
-				args.item_code, out.warehouse, args.company, include_child_warehouses=True
-			)
-		else:
-			bin_details = get_bin_details(args.item_code, out.warehouse, include_child_warehouses=True)
-
-		out.update(bin_details)
+	if item.is_stock_item:
+		update_bin_details(args, out, doc)
 
 	# update args with out, if key or value not exists
 	for key, value in out.items():
@@ -169,6 +155,24 @@
 		out.update(get_valuation_rate(args.item_code, args.company, out.get("warehouse")))
 
 
+def update_bin_details(args, out, doc):
+	if (
+		args.get("doctype") == "Material Request"
+		and args.get("material_request_type") == "Material Transfer"
+	):
+		out.update(get_bin_details(args.item_code, args.get("from_warehouse")))
+
+	elif out.get("warehouse"):
+		company = args.company if (doc and doc.get("doctype") == "Purchase Order") else None
+
+		# calculate company_total_stock only for po
+		bin_details = get_bin_details(
+			args.item_code, out.warehouse, company, include_child_warehouses=True
+		)
+
+		out.update(bin_details)
+
+
 def process_args(args):
 	if isinstance(args, str):
 		args = json.loads(args)
@@ -816,7 +820,9 @@
 			price_list_rate = get_price_list_rate_for(args, item_doc.variant_of)
 
 		# insert in database
-		if price_list_rate is None:
+		if price_list_rate is None or frappe.db.get_single_value(
+			"Stock Settings", "update_existing_price_list_rate"
+		):
 			if args.price_list and args.rate:
 				insert_item_price(args)
 			return out
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 2e4b08c..e98351a 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -60,6 +60,7 @@
 		if filters.get("batch_no") or inventory_dimension_filters_applied:
 			actual_qty += flt(sle.actual_qty, precision)
 			stock_value += sle.stock_value_difference
+			batch_balance_dict[sle.batch_no] += sle.actual_qty
 
 			if sle.voucher_type == "Stock Reconciliation" and not sle.actual_qty:
 				actual_qty = sle.qty_after_transaction
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 12df0fab..7b42103 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -599,6 +599,7 @@
 		elif self.sle.voucher_no:
 			query = query.where(parent.voucher_no != self.sle.voucher_no)
 
+		query = query.where(parent.voucher_type != "Pick List")
 		if timestamp_condition:
 			query = query.where(timestamp_condition)
 
@@ -819,6 +820,10 @@
 			self.remove_returned_serial_nos(new_package)
 
 		new_package.docstatus = 0
+		new_package.warehouse = self.warehouse
+		new_package.voucher_no = ""
+		new_package.posting_date = today()
+		new_package.posting_time = nowtime()
 		new_package.type_of_transaction = self.type_of_transaction
 		new_package.returned_against = self.get("returned_against")
 		new_package.save()
diff --git a/erpnext/templates/form_grid/item_grid.html b/erpnext/templates/form_grid/item_grid.html
index 027046f..72db6c8 100644
--- a/erpnext/templates/form_grid/item_grid.html
+++ b/erpnext/templates/form_grid/item_grid.html
@@ -18,7 +18,7 @@
 					actual_qty = (frm.doc.doctype==="Sales Order"
 						? doc.projected_qty : doc.actual_qty);
                 if(flt(frm.doc.per_delivered, 2) < 100
-                    && in_list(["Sales Order Item", "Delivery Note Item"], doc.doctype)) {
+                    && ["Sales Order Item", "Delivery Note Item"].includes(doc.doctype)) {
     				if(actual_qty != undefined) {
     					if(actual_qty >= doc.qty) {
     						var color = "green";
diff --git a/erpnext/templates/generators/sales_partner.html b/erpnext/templates/generators/sales_partner.html
index 39138d3..9dd2db9 100644
--- a/erpnext/templates/generators/sales_partner.html
+++ b/erpnext/templates/generators/sales_partner.html
@@ -8,18 +8,20 @@
 <div class="partner-content" itemscope itemtype="http://schema.org/Organization">
 	<div class="row">
 		<div class="col-md-4">
-			{% if logo -%}
+			{% if logo %}
 			<img itemprop="brand" src="{{ logo }}" class="partner-logo"
 				alt="{{ partner_name }}" title="{{ partner_name }}" />
 			<br><br>
-			{%- endif %}
-			<address>
-				{% if partner_website -%}<p><a href="{{ partner_website }}"
-					target="_blank">{{ partner_website }}</a></p>{%- endif %}
-				{% if partner_address -%}<p itemprop="address">{{ partner_address }}</p>{%- endif %}
-				{% if phone -%}<p itemprop="telephone">{{ phone }}</p>{%- endif %}
-				{% if email -%}<p itemprop="email"><span class="fa fa-envelope"></span> {{ email }}</p>{%- endif %}
-			</address>
+			{% endif %}
+			{% if addresses %}
+				{% for address in addresses %}
+					<address>
+						{% if address.partner_address %}<p itemprop="address">{{ address.partner_address }}</p>{% endif %}
+						{% if address.phone %}<p itemprop="telephone">{{ address.phone }}</p>{% endif %}
+						{% if address.email %}<p itemprop="email"><span class="fa fa-envelope"></span> {{ address.email }}</p>{% endif %}
+					</address>
+				{% endfor %}
+			{% endif %}
 		</div>
 		<div class="col-md-8">
 			<p>{{ description }}</p>