Merge pull request #38720 from s-aga-r/FIX-6545

fix(ux): don't override Item Name and Description in MR
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index fbc4d24..ed0921b 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -594,6 +594,27 @@
 
 			invoice_exchange_map.update(purchase_invoice_map)
 
+		journals = [
+			d.get("invoice_number") for d in invoices if d.get("invoice_type") == "Journal Entry"
+		]
+		journals.extend(
+			[d.get("reference_name") for d in payments if d.get("reference_type") == "Journal Entry"]
+		)
+		if journals:
+			journals = list(set(journals))
+			journals_map = frappe._dict(
+				frappe.db.get_all(
+					"Journal Entry Account",
+					filters={"parent": ("in", journals), "account": ("in", [self.receivable_payable_account])},
+					fields=[
+						"parent as `name`",
+						"exchange_rate",
+					],
+					as_list=1,
+				)
+			)
+			invoice_exchange_map.update(journals_map)
+
 		return invoice_exchange_map
 
 	def validate_allocation(self):
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
index a4141af..b57ebec 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.py
@@ -34,4 +34,6 @@
 		unreconciled_amount: DF.Currency
 	# end: auto-generated types
 
-	pass
+	@staticmethod
+	def get_list(args):
+		pass
diff --git a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
index 1e9f6ce..fa18ccd 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_invoice/payment_reconciliation_invoice.py
@@ -26,4 +26,6 @@
 		parenttype: DF.Data
 	# end: auto-generated types
 
-	pass
+	@staticmethod
+	def get_list(args):
+		pass
diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
index aa956fe..4ab80ec 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
+++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.py
@@ -30,4 +30,6 @@
 		remark: DF.SmallText | None
 	# end: auto-generated types
 
-	pass
+	@staticmethod
+	def get_list(args):
+		pass
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
index 8c23c67..7aa631b 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/repost_accounting_ledger.py
@@ -126,7 +126,7 @@
 		return rendered_page
 
 	def on_submit(self):
-		if len(self.vouchers) > 1:
+		if len(self.vouchers) > 5:
 			job_name = "repost_accounting_ledger_" + self.name
 			frappe.enqueue(
 				method="erpnext.accounts.doctype.repost_accounting_ledger.repost_accounting_ledger.start_repost",
@@ -170,8 +170,6 @@
 						doc.make_gl_entries(1)
 					doc.make_gl_entries()
 
-				frappe.db.commit()
-
 
 def get_allowed_types_from_settings():
 	return [
diff --git a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
index dda0ec7..d6f7096 100644
--- a/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
+++ b/erpnext/accounts/doctype/repost_accounting_ledger/test_repost_accounting_ledger.py
@@ -20,18 +20,11 @@
 		self.create_company()
 		self.create_customer()
 		self.create_item()
-		self.update_repost_settings()
+		update_repost_settings()
 
-	def teadDown(self):
+	def tearDown(self):
 		frappe.db.rollback()
 
-	def update_repost_settings(self):
-		allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
-		repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
-		for x in allowed_types:
-			repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
-			repost_settings.save()
-
 	def test_01_basic_functions(self):
 		si = create_sales_invoice(
 			item=self.item,
@@ -90,9 +83,6 @@
 		# Submit repost document
 		ral.save().submit()
 
-		# background jobs don't run on test cases. Manually triggering repost function.
-		start_repost(ral.name)
-
 		res = (
 			qb.from_(gl)
 			.select(gl.voucher_no, Sum(gl.debit).as_("debit"), Sum(gl.credit).as_("credit"))
@@ -177,26 +167,6 @@
 		pe = get_payment_entry(si.doctype, si.name)
 		pe.save().submit()
 
-		# without deletion flag set
-		ral = frappe.new_doc("Repost Accounting Ledger")
-		ral.company = self.company
-		ral.delete_cancelled_entries = False
-		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
-		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
-		ral.save()
-
-		# assert preview data is generated
-		preview = ral.generate_preview()
-		self.assertIsNotNone(preview)
-
-		ral.save().submit()
-
-		# background jobs don't run on test cases. Manually triggering repost function.
-		start_repost(ral.name)
-
-		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
-		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
-
 		# with deletion flag set
 		ral = frappe.new_doc("Repost Accounting Ledger")
 		ral.company = self.company
@@ -205,6 +175,38 @@
 		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
 		ral.save().submit()
 
-		start_repost(ral.name)
 		self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
 		self.assertIsNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
+
+	def test_05_without_deletion_flag(self):
+		si = create_sales_invoice(
+			item=self.item,
+			company=self.company,
+			customer=self.customer,
+			debit_to=self.debit_to,
+			parent_cost_center=self.cost_center,
+			cost_center=self.cost_center,
+			rate=100,
+		)
+
+		pe = get_payment_entry(si.doctype, si.name)
+		pe.save().submit()
+
+		# without deletion flag set
+		ral = frappe.new_doc("Repost Accounting Ledger")
+		ral.company = self.company
+		ral.delete_cancelled_entries = False
+		ral.append("vouchers", {"voucher_type": si.doctype, "voucher_no": si.name})
+		ral.append("vouchers", {"voucher_type": pe.doctype, "voucher_no": pe.name})
+		ral.save().submit()
+
+		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": si.name, "is_cancelled": 1}))
+		self.assertIsNotNone(frappe.db.exists("GL Entry", {"voucher_no": pe.name, "is_cancelled": 1}))
+
+
+def update_repost_settings():
+	allowed_types = ["Sales Invoice", "Purchase Invoice", "Payment Entry", "Journal Entry"]
+	repost_settings = frappe.get_doc("Repost Accounting Ledger Settings")
+	for x in allowed_types:
+		repost_settings.append("allowed_types", {"document_type": x, "allowed": True})
+		repost_settings.save()
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index e9b71dd..6163749 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -2793,6 +2793,12 @@
 	@change_settings("Selling Settings", {"enable_discount_accounting": 1})
 	def test_additional_discount_for_sales_invoice_with_discount_accounting_enabled(self):
 
+		from erpnext.accounts.doctype.repost_accounting_ledger.test_repost_accounting_ledger import (
+			update_repost_settings,
+		)
+
+		update_repost_settings()
+
 		additional_discount_account = create_account(
 			account_name="Discount Account",
 			parent_account="Indirect Expenses - _TC",
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 0021140..67234cc 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -340,6 +340,10 @@
 				n == 0
 				and (has_pro_rata or has_wdv_or_dd_non_yearly_pro_rata)
 				and not self.opening_accumulated_depreciation
+				and get_updated_rate_of_depreciation_for_wdv_and_dd(
+					asset_doc, value_after_depreciation, row, False
+				)
+				== row.rate_of_depreciation
 			):
 				from_date = add_days(
 					asset_doc.available_for_use_date, -1
@@ -605,7 +609,9 @@
 
 
 @erpnext.allow_regional
-def get_updated_rate_of_depreciation_for_wdv_and_dd(asset, depreciable_value, fb_row):
+def get_updated_rate_of_depreciation_for_wdv_and_dd(
+	asset, depreciable_value, fb_row, show_msg=True
+):
 	return fb_row.rate_of_depreciation
 
 
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index eea8cd5..5b8be44 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -119,6 +119,15 @@
 			supplier.quote_status = "Pending"
 		self.send_to_supplier()
 
+	def before_print(self, settings=None):
+		"""Use the first suppliers data to render the print preview."""
+		if self.vendor or not self.suppliers:
+			# If a specific supplier is already set, via Tools > Download PDF,
+			# we don't want to override it.
+			return
+
+		self.update_supplier_part_no(self.suppliers[0].supplier)
+
 	def on_cancel(self):
 		self.db_set("status", "Cancelled")
 
diff --git a/erpnext/crm/doctype/lead/lead.json b/erpnext/crm/doctype/lead/lead.json
index dafbd9f..92f446d 100644
--- a/erpnext/crm/doctype/lead/lead.json
+++ b/erpnext/crm/doctype/lead/lead.json
@@ -516,7 +516,7 @@
  "idx": 5,
  "image_field": "image",
  "links": [],
- "modified": "2023-08-28 22:28:00.104413",
+ "modified": "2023-12-01 18:46:49.468526",
  "modified_by": "Administrator",
  "module": "CRM",
  "name": "Lead",
@@ -577,6 +577,7 @@
  ],
  "search_fields": "lead_name,lead_owner,status",
  "sender_field": "email_id",
+ "sender_name_field": "lead_name",
  "show_name_in_global_search": 1,
  "sort_field": "modified",
  "sort_order": "DESC",
diff --git a/erpnext/crm/doctype/lead/lead.py b/erpnext/crm/doctype/lead/lead.py
index ef6cfb0..f3c7e57 100644
--- a/erpnext/crm/doctype/lead/lead.py
+++ b/erpnext/crm/doctype/lead/lead.py
@@ -16,6 +16,7 @@
 from erpnext.accounts.party import set_taxes
 from erpnext.controllers.selling_controller import SellingController
 from erpnext.crm.utils import CRMNote, copy_comments, link_communications, link_open_events
+from erpnext.selling.doctype.customer.customer import parse_full_name
 
 
 class Lead(SellingController, CRMNote):
@@ -113,6 +114,10 @@
 					return
 			self.contact_doc = self.create_contact()
 
+		# leads created by email inbox only have the full name set
+		if self.lead_name and not any([self.first_name, self.middle_name, self.last_name]):
+			self.first_name, self.middle_name, self.last_name = parse_full_name(self.lead_name)
+
 	def after_insert(self):
 		self.link_to_contact()
 
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 0de100a..9b070bd 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -351,5 +351,6 @@
 erpnext.patches.v14_0.create_accounting_dimensions_in_supplier_quotation
 erpnext.patches.v14_0.update_zero_asset_quantity_field
 execute:frappe.db.set_single_value("Buying Settings", "project_update_frequency", "Each Transaction")
+execute:frappe.db.set_default("date_format", frappe.db.get_single_value("System Settings", "date_format"))
 # below migration patch should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/portal/doctype/homepage/homepage.js b/erpnext/portal/doctype/homepage/homepage.js
index 6797904..6739979 100644
--- a/erpnext/portal/doctype/homepage/homepage.js
+++ b/erpnext/portal/doctype/homepage/homepage.js
@@ -2,14 +2,6 @@
 // For license information, please see license.txt
 
 frappe.ui.form.on('Homepage', {
-	setup: function(frm) {
-		frm.fields_dict["products"].grid.get_field("item").get_query = function() {
-			return {
-				filters: {'published': 1}
-			}
-		}
-	},
-
 	refresh: function(frm) {
 		frm.add_custom_button(__('Set Meta Tags'), () => {
 			frappe.utils.set_meta_tag('home');
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 156c05b..3935783 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -380,6 +380,7 @@
 	}
 
 	scan_barcode() {
+		frappe.flags.dialog_set = false;
 		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
 		barcode_scanner.process_scan();
 	}
@@ -471,6 +472,7 @@
 				item.pricing_rules = ''
 				return this.frm.call({
 					method: "erpnext.stock.get_item_details.get_item_details",
+					child: item,
 					args: {
 						doc: me.frm.doc,
 						args: {
@@ -521,19 +523,6 @@
 						if(!r.exc) {
 							frappe.run_serially([
 								() => {
-									var child = locals[cdt][cdn];
-									var std_field_list = ["doctype"]
-										.concat(frappe.model.std_fields_list)
-										.concat(frappe.model.child_table_field_list);
-
-									for (var key in r.message) {
-										if (std_field_list.indexOf(key) === -1) {
-											if (key === "qty" && child[key]) continue;
-											child[key] = r.message[key];
-										}
-									}
-								},
-								() => {
 									var d = locals[cdt][cdn];
 									me.add_taxes_from_item_tax_template(d.item_tax_rate);
 									if (d.free_item_data && d.free_item_data.length > 0) {
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index 25fc754..b0ea568 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -8,7 +8,7 @@
 		if(!company && cur_frm)
 			company = cur_frm.doc.company;
 		if(company)
-			return frappe.get_doc(":Company", company).default_currency || frappe.boot.sysdefaults.currency;
+			return frappe.get_doc(":Company", company)?.default_currency || frappe.boot.sysdefaults.currency;
 		else
 			return frappe.boot.sysdefaults.currency;
 	},
@@ -1077,7 +1077,7 @@
 }
 
 function get_time_left(timestamp, agreement_status) {
-	const diff = moment(timestamp).diff(moment());
+	const diff = moment(timestamp).diff(frappe.datetime.system_datetime(true));
 	const diff_display = diff >= 44500 ? moment.duration(diff).humanize() : 'Failed';
 	let indicator = (diff_display == 'Failed' && agreement_status != 'Fulfilled') ? 'red' : 'green';
 	return {'diff_display': diff_display, 'indicator': indicator};
diff --git a/erpnext/public/js/utils/barcode_scanner.js b/erpnext/public/js/utils/barcode_scanner.js
index a4f74bd..a1ebfe9 100644
--- a/erpnext/public/js/utils/barcode_scanner.js
+++ b/erpnext/public/js/utils/barcode_scanner.js
@@ -114,13 +114,13 @@
 
 			frappe.run_serially([
 				() => this.set_selector_trigger_flag(data),
+				() => this.set_serial_no(row, serial_no),
+				() => this.set_batch_no(row, batch_no),
+				() => this.set_barcode(row, barcode),
 				() => this.set_item(row, item_code, barcode, batch_no, serial_no).then(qty => {
 					this.show_scan_message(row.idx, row.item_code, qty);
 				}),
 				() => this.set_barcode_uom(row, uom),
-				() => this.set_serial_no(row, serial_no),
-				() => this.set_batch_no(row, batch_no),
-				() => this.set_barcode(row, barcode),
 				() => this.clean_up(),
 				() => this.revert_selector_flag(),
 				() => resolve(row)
@@ -131,10 +131,10 @@
 	// batch and serial selector is reduandant when all info can be added by scan
 	// this flag on item row is used by transaction.js to avoid triggering selector
 	set_selector_trigger_flag(data) {
-		const {batch_no, serial_no, has_batch_no, has_serial_no} = data;
+		const {has_batch_no, has_serial_no} = data;
 
-		const require_selecting_batch = has_batch_no && !batch_no;
-		const require_selecting_serial = has_serial_no && !serial_no;
+		const require_selecting_batch = has_batch_no;
+		const require_selecting_serial = has_serial_no;
 
 		if (!(require_selecting_batch || require_selecting_serial)) {
 			frappe.flags.hide_serial_batch_dialog = true;
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 0de6774..7b9cdfe 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -31,7 +31,10 @@
 			secondary_action: () => this.edit_full_form(),
 		});
 
-		this.dialog.set_value("qty", this.item.qty).then(() => {
+		this.dialog.show();
+
+		let qty = this.item.stock_qty || this.item.transfer_qty || this.item.qty;
+		this.dialog.set_value("qty", qty).then(() => {
 			if (this.item.serial_no) {
 				this.dialog.set_value("scan_serial_no", this.item.serial_no);
 				frappe.model.set_value(this.item.doctype, this.item.name, 'serial_no', '');
@@ -39,9 +42,10 @@
 				this.dialog.set_value("scan_batch_no", this.item.batch_no);
 				frappe.model.set_value(this.item.doctype, this.item.name, 'batch_no', '');
 			}
+
+			this.dialog.fields_dict.entries.grid.refresh();
 		});
 
-		this.dialog.show();
 		this.$scan_btn = this.dialog.$wrapper.find(".link-btn");
 		this.$scan_btn.css("display", "inline");
 	}
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index d1214fd..7a1d5e2 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -85,8 +85,6 @@
 			except frappe.ValidationError:
 				pass
 
-	frappe.db.set_default("date_format", "dd-mm-yyyy")
-
 	setup_currency_exchange()
 
 
diff --git a/erpnext/startup/leaderboard.py b/erpnext/startup/leaderboard.py
index da7edbf..5a60d2f 100644
--- a/erpnext/startup/leaderboard.py
+++ b/erpnext/startup/leaderboard.py
@@ -1,5 +1,5 @@
 import frappe
-from frappe.utils import cint
+from frappe.utils.deprecations import deprecated
 
 
 def get_leaderboards():
@@ -54,12 +54,13 @@
 
 @frappe.whitelist()
 def get_all_customers(date_range, company, field, limit=None):
+	filters = [["docstatus", "=", "1"], ["company", "=", company]]
+	from_date, to_date = parse_date_range(date_range)
 	if field == "outstanding_amount":
-		filters = [["docstatus", "=", "1"], ["company", "=", company]]
-		if date_range:
-			date_range = frappe.parse_json(date_range)
-			filters.append(["posting_date", ">=", "between", [date_range[0], date_range[1]]])
-		return frappe.db.get_all(
+		if from_date and to_date:
+			filters.append(["posting_date", "between", [from_date, to_date]])
+
+		return frappe.get_list(
 			"Sales Invoice",
 			fields=["customer as name", "sum(outstanding_amount) as value"],
 			filters=filters,
@@ -69,26 +70,20 @@
 		)
 	else:
 		if field == "total_sales_amount":
-			select_field = "sum(so_item.base_net_amount)"
+			select_field = "base_net_total"
 		elif field == "total_qty_sold":
-			select_field = "sum(so_item.stock_qty)"
+			select_field = "total_qty"
 
-		date_condition = get_date_condition(date_range, "so.transaction_date")
+		if from_date and to_date:
+			filters.append(["transaction_date", "between", [from_date, to_date]])
 
-		return frappe.db.sql(
-			"""
-			select so.customer as name, {0} as value
-			FROM `tabSales Order` as so JOIN `tabSales Order Item` as so_item
-				ON so.name = so_item.parent
-			where so.docstatus = 1 {1} and so.company = %s
-			group by so.customer
-			order by value DESC
-			limit %s
-		""".format(
-				select_field, date_condition
-			),
-			(company, cint(limit)),
-			as_dict=1,
+		return frappe.get_list(
+			"Sales Order",
+			fields=["customer as name", f"sum({select_field}) as value"],
+			filters=filters,
+			group_by="customer",
+			order_by="value desc",
+			limit=limit,
 		)
 
 
@@ -96,55 +91,58 @@
 def get_all_items(date_range, company, field, limit=None):
 	if field in ("available_stock_qty", "available_stock_value"):
 		select_field = "sum(actual_qty)" if field == "available_stock_qty" else "sum(stock_value)"
-		return frappe.db.get_all(
+		results = frappe.db.get_all(
 			"Bin",
 			fields=["item_code as name", "{0} as value".format(select_field)],
 			group_by="item_code",
 			order_by="value desc",
 			limit=limit,
 		)
+		readable_active_items = set(frappe.get_list("Item", filters={"disabled": 0}, pluck="name"))
+		return [item for item in results if item["name"] in readable_active_items]
 	else:
 		if field == "total_sales_amount":
-			select_field = "sum(order_item.base_net_amount)"
+			select_field = "base_net_amount"
 			select_doctype = "Sales Order"
 		elif field == "total_purchase_amount":
-			select_field = "sum(order_item.base_net_amount)"
+			select_field = "base_net_amount"
 			select_doctype = "Purchase Order"
 		elif field == "total_qty_sold":
-			select_field = "sum(order_item.stock_qty)"
+			select_field = "stock_qty"
 			select_doctype = "Sales Order"
 		elif field == "total_qty_purchased":
-			select_field = "sum(order_item.stock_qty)"
+			select_field = "stock_qty"
 			select_doctype = "Purchase Order"
 
-		date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+		filters = [["docstatus", "=", "1"], ["company", "=", company]]
+		from_date, to_date = parse_date_range(date_range)
+		if from_date and to_date:
+			filters.append(["transaction_date", "between", [from_date, to_date]])
 
-		return frappe.db.sql(
-			"""
-			select order_item.item_code as name, {0} as value
-			from `tab{1}` sales_order join `tab{1} Item` as order_item
-				on sales_order.name = order_item.parent
-			where sales_order.docstatus = 1
-				and sales_order.company = %s {2}
-			group by order_item.item_code
-			order by value desc
-			limit %s
-		""".format(
-				select_field, select_doctype, date_condition
-			),
-			(company, cint(limit)),
-			as_dict=1,
-		)  # nosec
+		child_doctype = f"{select_doctype} Item"
+		return frappe.get_list(
+			select_doctype,
+			fields=[
+				f"`tab{child_doctype}`.item_code as name",
+				f"sum(`tab{child_doctype}`.{select_field}) as value",
+			],
+			filters=filters,
+			order_by="value desc",
+			group_by=f"`tab{child_doctype}`.item_code",
+			limit=limit,
+		)
 
 
 @frappe.whitelist()
 def get_all_suppliers(date_range, company, field, limit=None):
+	filters = [["docstatus", "=", "1"], ["company", "=", company]]
+	from_date, to_date = parse_date_range(date_range)
+
 	if field == "outstanding_amount":
-		filters = [["docstatus", "=", "1"], ["company", "=", company]]
-		if date_range:
-			date_range = frappe.parse_json(date_range)
-			filters.append(["posting_date", "between", [date_range[0], date_range[1]]])
-		return frappe.db.get_all(
+		if from_date and to_date:
+			filters.append(["posting_date", "between", [from_date, to_date]])
+
+		return frappe.get_list(
 			"Purchase Invoice",
 			fields=["supplier as name", "sum(outstanding_amount) as value"],
 			filters=filters,
@@ -154,48 +152,40 @@
 		)
 	else:
 		if field == "total_purchase_amount":
-			select_field = "sum(purchase_order_item.base_net_amount)"
+			select_field = "base_net_total"
 		elif field == "total_qty_purchased":
-			select_field = "sum(purchase_order_item.stock_qty)"
+			select_field = "total_qty"
 
-		date_condition = get_date_condition(date_range, "purchase_order.modified")
+		if from_date and to_date:
+			filters.append(["transaction_date", "between", [from_date, to_date]])
 
-		return frappe.db.sql(
-			"""
-			select purchase_order.supplier as name, {0} as value
-			FROM `tabPurchase Order` as purchase_order LEFT JOIN `tabPurchase Order Item`
-				as purchase_order_item ON purchase_order.name = purchase_order_item.parent
-			where
-				purchase_order.docstatus = 1
-				{1}
-				and  purchase_order.company = %s
-			group by purchase_order.supplier
-			order by value DESC
-			limit %s""".format(
-				select_field, date_condition
-			),
-			(company, cint(limit)),
-			as_dict=1,
-		)  # nosec
+		return frappe.get_list(
+			"Purchase Order",
+			fields=["supplier as name", f"sum({select_field}) as value"],
+			filters=filters,
+			group_by="supplier",
+			order_by="value desc",
+			limit=limit,
+		)
 
 
 @frappe.whitelist()
 def get_all_sales_partner(date_range, company, field, limit=None):
 	if field == "total_sales_amount":
-		select_field = "sum(`base_net_total`)"
+		select_field = "base_net_total"
 	elif field == "total_commission":
-		select_field = "sum(`total_commission`)"
+		select_field = "total_commission"
 
-	filters = {"sales_partner": ["!=", ""], "docstatus": 1, "company": company}
-	if date_range:
-		date_range = frappe.parse_json(date_range)
-		filters["transaction_date"] = ["between", [date_range[0], date_range[1]]]
+	filters = [["docstatus", "=", "1"], ["company", "=", company], ["sales_partner", "is", "set"]]
+	from_date, to_date = parse_date_range(date_range)
+	if from_date and to_date:
+		filters.append(["transaction_date", "between", [from_date, to_date]])
 
 	return frappe.get_list(
 		"Sales Order",
 		fields=[
-			"`sales_partner` as name",
-			"{} as value".format(select_field),
+			"sales_partner as name",
+			f"sum({select_field}) as value",
 		],
 		filters=filters,
 		group_by="sales_partner",
@@ -206,27 +196,29 @@
 
 @frappe.whitelist()
 def get_all_sales_person(date_range, company, field=None, limit=0):
-	date_condition = get_date_condition(date_range, "sales_order.transaction_date")
+	filters = [
+		["docstatus", "=", "1"],
+		["company", "=", company],
+		["Sales Team", "sales_person", "is", "set"],
+	]
+	from_date, to_date = parse_date_range(date_range)
+	if from_date and to_date:
+		filters.append(["transaction_date", "between", [from_date, to_date]])
 
-	return frappe.db.sql(
-		"""
-		select sales_team.sales_person as name, sum(sales_order.base_net_total) as value
-		from `tabSales Order` as sales_order join `tabSales Team` as sales_team
-			on sales_order.name = sales_team.parent and sales_team.parenttype = 'Sales Order'
-		where sales_order.docstatus = 1
-			and sales_order.company = %s
-			{date_condition}
-		group by sales_team.sales_person
-		order by value DESC
-		limit %s
-	""".format(
-			date_condition=date_condition
-		),
-		(company, cint(limit)),
-		as_dict=1,
+	return frappe.get_list(
+		"Sales Order",
+		fields=[
+			"`tabSales Team`.sales_person as name",
+			"sum(`tabSales Team`.allocated_amount) as value",
+		],
+		filters=filters,
+		group_by="`tabSales Team`.sales_person",
+		order_by="value desc",
+		limit=limit,
 	)
 
 
+@deprecated
 def get_date_condition(date_range, field):
 	date_condition = ""
 	if date_range:
@@ -236,3 +228,11 @@
 			field, frappe.db.escape(from_date), frappe.db.escape(to_date)
 		)
 	return date_condition
+
+
+def parse_date_range(date_range):
+	if date_range:
+		date_range = frappe.parse_json(date_range)
+		return date_range[0], date_range[1]
+
+	return None, None
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 6785881..ecb9314 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
@@ -709,6 +709,7 @@
 					"item_code": self.item_code,
 					"warehouse": self.warehouse,
 					"batch_no": batches,
+					"consider_negative_batches": True,
 				}
 			)
 		)
@@ -719,6 +720,9 @@
 		available_batches = get_available_batches_qty(available_batches)
 		for batch_no in batches:
 			if batch_no not in available_batches or available_batches[batch_no] < 0:
+				if flt(available_batches.get(batch_no)) < 0:
+					self.validate_negative_batch(batch_no, available_batches[batch_no])
+
 				self.throw_error_message(
 					f"Batch {bold(batch_no)} is not available in the selected warehouse {self.warehouse}"
 				)
@@ -1491,7 +1495,8 @@
 			available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches
 		)
 
-	available_batches = list(filter(lambda x: x.qty > 0, available_batches))
+	if not kwargs.consider_negative_batches:
+		available_batches = list(filter(lambda x: x.qty > 0, available_batches))
 
 	if not qty:
 		return available_batches
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 7334b35..7af5d1a 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -781,10 +781,9 @@
 						});
 						refresh_field("items");
 
-						let no_batch_serial_number_value = !d.serial_no;
-						if (d.has_batch_no && !d.has_serial_no) {
-							// check only batch_no for batched item
-							no_batch_serial_number_value = !d.batch_no;
+						let no_batch_serial_number_value = false;
+						if (d.has_serial_no || d.has_batch_no) {
+							no_batch_serial_number_value = true;
 						}
 
 						if (no_batch_serial_number_value && !frappe.flags.hide_serial_batch_dialog && !frappe.flags.dialog_set) {
@@ -941,6 +940,7 @@
 	}
 
 	scan_barcode() {
+		frappe.flags.dialog_set = false;
 		const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
 		barcode_scanner.process_scan();
 	}
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index eb1c7a8..e0e364f 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1733,6 +1733,45 @@
 		self.assertFalse(doc.is_enqueue_action())
 		frappe.flags.in_test = True
 
+	def test_negative_batch(self):
+		item_code = "Test Negative Batch Item - 001"
+		make_item(
+			item_code,
+			{"has_batch_no": 1, "create_new_batch": 1, "batch_naming_series": "Test-BCH-NNS.#####"},
+		)
+
+		se1 = make_stock_entry(
+			item_code=item_code,
+			purpose="Material Receipt",
+			qty=100,
+			target="_Test Warehouse - _TC",
+		)
+
+		se1.reload()
+
+		batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle)
+
+		se2 = make_stock_entry(
+			item_code=item_code,
+			purpose="Material Issue",
+			batch_no=batch_no,
+			qty=10,
+			source="_Test Warehouse - _TC",
+		)
+
+		se2.reload()
+
+		se3 = make_stock_entry(
+			item_code=item_code,
+			purpose="Material Receipt",
+			qty=100,
+			target="_Test Warehouse - _TC",
+		)
+
+		se3.reload()
+
+		self.assertRaises(frappe.ValidationError, se1.cancel)
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index dfeb1ee..e746595 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -358,7 +358,6 @@
 			"net_amount": 0.0,
 			"discount_percentage": 0.0,
 			"discount_amount": flt(args.discount_amount) or 0.0,
-			"supplier": get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults),
 			"update_stock": args.get("update_stock")
 			if args.get("doctype") in ["Sales Invoice", "Purchase Invoice"]
 			else 0,
@@ -378,6 +377,10 @@
 		}
 	)
 
+	default_supplier = get_default_supplier(args, item_defaults, item_group_defaults, brand_defaults)
+	if default_supplier:
+		out.supplier = default_supplier
+
 	if item.get("enable_deferred_revenue") or item.get("enable_deferred_expense"):
 		out.update(calculate_service_end_date(args, item))
 
@@ -572,8 +575,8 @@
 			item_tax_template = _get_item_tax_template(args, item_group_doc.taxes, out)
 			item_group = item_group_doc.parent_item_group
 
-	if args.child_doctype and item_tax_template:
-		out.update(get_fetch_values(args.child_doctype, "item_tax_template", item_tax_template))
+	if args.get("child_doctype") and item_tax_template:
+		out.update(get_fetch_values(args.get("child_doctype"), "item_tax_template", item_tax_template))
 
 
 def _get_item_tax_template(args, taxes, out=None, for_validate=False):
diff --git a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
index ae12fbb..810dc46 100644
--- a/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
+++ b/erpnext/stock/report/serial_no_ledger/serial_no_ledger.py
@@ -1,9 +1,12 @@
 # Copyright (c) 2013, Frappe Technologies Pvt. Ltd. and contributors
 # For license information, please see license.txt
 
+import copy
+
 import frappe
 from frappe import _
 
+from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos as get_serial_nos_from_sle
 from erpnext.stock.stock_ledger import get_stock_ledger_entries
 
 
@@ -15,8 +18,8 @@
 
 def get_columns(filters):
 	columns = [
-		{"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date"},
-		{"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time"},
+		{"label": _("Posting Date"), "fieldtype": "Date", "fieldname": "posting_date", "width": 120},
+		{"label": _("Posting Time"), "fieldtype": "Time", "fieldname": "posting_time", "width": 90},
 		{
 			"label": _("Voucher Type"),
 			"fieldtype": "Link",
@@ -29,7 +32,7 @@
 			"fieldtype": "Dynamic Link",
 			"fieldname": "voucher_no",
 			"options": "voucher_type",
-			"width": 180,
+			"width": 230,
 		},
 		{
 			"label": _("Company"),
@@ -49,7 +52,7 @@
 			"label": _("Status"),
 			"fieldtype": "Data",
 			"fieldname": "status",
-			"width": 120,
+			"width": 90,
 		},
 		{
 			"label": _("Serial No"),
@@ -62,7 +65,7 @@
 			"label": _("Valuation Rate"),
 			"fieldtype": "Float",
 			"fieldname": "valuation_rate",
-			"width": 150,
+			"width": 130,
 		},
 		{
 			"label": _("Qty"),
@@ -102,15 +105,29 @@
 			}
 		)
 
-		serial_nos = [{"serial_no": row.serial_no, "valuation_rate": row.valuation_rate}]
+		serial_nos = []
+		if row.serial_no:
+			parsed_serial_nos = get_serial_nos_from_sle(row.serial_no)
+			for serial_no in parsed_serial_nos:
+				if filters.get("serial_no") and filters.get("serial_no") != serial_no:
+					continue
+
+				serial_nos.append(
+					{
+						"serial_no": serial_no,
+						"valuation_rate": abs(row.stock_value_difference / row.actual_qty),
+					}
+				)
+
 		if row.serial_and_batch_bundle:
-			serial_nos = bundle_wise_serial_nos.get(row.serial_and_batch_bundle, [])
+			serial_nos.extend(bundle_wise_serial_nos.get(row.serial_and_batch_bundle, []))
 
 		for index, bundle_data in enumerate(serial_nos):
 			if index == 0:
-				args.serial_no = bundle_data.get("serial_no")
-				args.valuation_rate = bundle_data.get("valuation_rate")
-				data.append(args)
+				new_args = copy.deepcopy(args)
+				new_args.serial_no = bundle_data.get("serial_no")
+				new_args.valuation_rate = bundle_data.get("valuation_rate")
+				data.append(new_args)
 			else:
 				data.append(
 					{
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index 6c187f8..0fe8c13 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -7,6 +7,7 @@
 from frappe.utils import flt
 
 from erpnext.buying.doctype.purchase_order.purchase_order import is_subcontracting_order_created
+from erpnext.buying.doctype.purchase_order.purchase_order import update_status as update_po_status
 from erpnext.controllers.subcontracting_controller import SubcontractingController
 from erpnext.stock.stock_balance import update_bin_qty
 from erpnext.stock.utils import get_bin
@@ -308,6 +309,9 @@
 				"Subcontracting Order", self.name, "status", status, update_modified=update_modified
 			)
 
+			if status == "Closed":
+				update_po_status("Closed", self.purchase_order)
+
 
 @frappe.whitelist()
 def make_subcontracting_receipt(source_name, target_doc=None):
diff --git a/erpnext/templates/pages/home.html b/erpnext/templates/pages/home.html
index 08e0432..b9b435c 100644
--- a/erpnext/templates/pages/home.html
+++ b/erpnext/templates/pages/home.html
@@ -26,6 +26,26 @@
 		{{ render_homepage_section(homepage.hero_section_doc) }}
 	{% endif %}
 
+	{% if homepage.products %}
+	<section class="container section-products my-5">
+		<h3>{{ _('Products') }}</h3>
+
+		<div class="row">
+			{% for item in homepage.products %}
+			<div class="col-md-4 mb-4">
+				<div class="card h-100 justify-content-between">
+					<img class="card-img-top website-image-extra-large" src="{{ item.image }}" loading="lazy" alt="{{ item.item_name }}"></img>
+					<div class="card-body flex-grow-0">
+						<h5 class="card-title">{{ item.item_name }}</h5>
+						<a href="{{ item.route }}" class="card-link">{{ _('More details') }}</a>
+					</div>
+				</div>
+			</div>
+			{% endfor %}
+		</div>
+	</section>
+	{% endif %}
+
 	{% if blogs %}
 	<section class="container my-5">
 		<h3>{{ _('Publications') }}</h3>