Merge pull request #40561 from rohitwaghchaure/fixed-11260

fix: Cannot read properties of undefined (reading 'rate')
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/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/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 961ee20..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 {
@@ -623,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",
@@ -1042,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) {
@@ -1213,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(
@@ -1227,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(
@@ -1323,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 8a5d2c6..b4d1d39 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.py
@@ -489,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():
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/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/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/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index 28d4a5e..382c034 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
@@ -742,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,
@@ -756,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)
@@ -891,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):
@@ -1029,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(
@@ -1163,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/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/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/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index e62d22b..cf38302 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -291,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 &&
@@ -336,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"),
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/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 b16e073..8bdee22 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1739,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()
 
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/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/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/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 934becf..1e94c00 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -129,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 1f85eb6..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);
 			});
@@ -1623,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
@@ -2269,7 +2269,7 @@
 	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";
@@ -2509,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/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/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/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index f7e65e0..7327fde 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -2141,6 +2141,40 @@
 		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")
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/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/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/delivery_note/test_delivery_note.py b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
index 434e001..905287d 100644
--- a/erpnext/stock/doctype/delivery_note/test_delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/test_delivery_note.py
@@ -1108,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_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/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/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py
index 9a7395f..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)
@@ -828,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_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/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";