perf: serial and batch bundle valuation (reposting) (#40255)

perf: serial and batch bundle valuation
diff --git a/erpnext/stock/deprecated_serial_batch.py b/erpnext/stock/deprecated_serial_batch.py
index ab38c15..7be1418 100644
--- a/erpnext/stock/deprecated_serial_batch.py
+++ b/erpnext/stock/deprecated_serial_batch.py
@@ -13,7 +13,9 @@
 		):
 			return
 
-		serial_nos = self.get_serial_nos()
+		serial_nos = self.get_filterd_serial_nos()
+		if not serial_nos:
+			return
 
 		actual_qty = flt(self.sle.actual_qty)
 
@@ -25,8 +27,21 @@
 
 		self.stock_value_change += stock_value_change
 
+	def get_filterd_serial_nos(self):
+		serial_nos = []
+		non_filtered_serial_nos = self.get_serial_nos()
+
+		# If the serial no inwarded using the Serial and Batch Bundle, then the serial no should not be considered
+		for serial_no in non_filtered_serial_nos:
+			if serial_no and serial_no not in self.serial_no_incoming_rate:
+				serial_nos.append(serial_no)
+
+		return serial_nos
+
 	@deprecated
 	def get_incoming_value_for_serial_nos(self, serial_nos):
+		from erpnext.stock.utils import get_combine_datetime
+
 		# get rate from serial nos within same company
 		incoming_values = 0.0
 		for serial_no in serial_nos:
@@ -42,18 +57,19 @@
 						| (table.serial_no.like("%\n" + serial_no + "\n%"))
 					)
 					& (table.company == self.sle.company)
+					& (table.warehouse == self.sle.warehouse)
 					& (table.serial_and_batch_bundle.isnull())
+					& (table.actual_qty > 0)
 					& (table.is_cancelled == 0)
+					& table.posting_datetime
+					<= get_combine_datetime(self.sle.posting_date, self.sle.posting_time)
 				)
 				.orderby(table.posting_datetime, order=Order.desc)
+				.limit(1)
 			).run(as_dict=1)
 
 			for sle in stock_ledgers:
-				self.serial_no_incoming_rate[serial_no] += (
-					flt(sle.incoming_rate)
-					if sle.actual_qty > 0
-					else (sle.stock_value_difference / sle.actual_qty) * -1
-				)
+				self.serial_no_incoming_rate[serial_no] += flt(sle.incoming_rate)
 				incoming_values += self.serial_no_incoming_rate[serial_no]
 
 		return incoming_values
diff --git a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
index 257f263..4058aa8 100644
--- a/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
+++ b/erpnext/stock/doctype/landed_cost_voucher/test_landed_cost_voucher.py
@@ -415,23 +415,6 @@
 		create_landed_cost_voucher("Purchase Receipt", pr.name, pr.company, charges=charges)
 		new_purchase_rate = serial_no_rate + charges
 
-		sn_obj = SerialNoValuation(
-			sle=frappe._dict(
-				{
-					"posting_date": today(),
-					"posting_time": nowtime(),
-					"item_code": "_Test Serialized Item",
-					"warehouse": "Stores - TCP1",
-					"serial_nos": [serial_no],
-				}
-			)
-		)
-
-		new_serial_no_rate = sn_obj.get_incoming_rate_of_serial_no(serial_no)
-
-		# Since the serial no is already delivered the rate must be zero
-		self.assertFalse(new_serial_no_rate)
-
 		stock_value_difference = frappe.db.get_value(
 			"Stock Ledger Entry",
 			filters={
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 33f0dce..d01dfef 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
@@ -332,13 +332,8 @@
 			rate = frappe.db.get_value(child_table, self.voucher_detail_no, valuation_field)
 
 		for d in self.entries:
-			if not rate or (
-				flt(rate, precision) == flt(d.incoming_rate, precision) and d.stock_value_difference
-			):
-				continue
-
 			d.incoming_rate = flt(rate, precision)
-			if self.has_batch_no:
+			if d.qty:
 				d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate)
 
 			if save:
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index 24dd9d1..1fcc439 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -4,8 +4,9 @@
 import frappe
 from frappe import _, bold
 from frappe.model.naming import make_autoname
-from frappe.query_builder.functions import CombineDatetime, Sum
+from frappe.query_builder.functions import CombineDatetime, Sum, Timestamp
 from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
+from pypika import Order
 
 from erpnext.stock.deprecated_serial_batch import (
 	DeprecatedBatchNoValuation,
@@ -424,19 +425,21 @@
 			)
 
 		else:
-			entries = self.get_serial_no_ledgers()
-
 			self.serial_no_incoming_rate = defaultdict(float)
 			self.stock_value_change = 0.0
 
-			for ledger in entries:
-				self.stock_value_change += ledger.incoming_rate
-				self.serial_no_incoming_rate[ledger.serial_no] += ledger.incoming_rate
+			serial_nos = self.get_serial_nos()
+			for serial_no in serial_nos:
+				incoming_rate = self.get_incoming_rate_from_bundle(serial_no)
+				if not incoming_rate:
+					continue
+
+				self.stock_value_change += incoming_rate
+				self.serial_no_incoming_rate[serial_no] += incoming_rate
 
 			self.calculate_stock_value_from_deprecarated_ledgers()
 
-	def get_serial_no_ledgers(self):
-		serial_nos = self.get_serial_nos()
+	def get_incoming_rate_from_bundle(self, serial_no) -> float:
 		bundle = frappe.qb.DocType("Serial and Batch Bundle")
 		bundle_child = frappe.qb.DocType("Serial and Batch Entry")
 
@@ -444,20 +447,18 @@
 			frappe.qb.from_(bundle)
 			.inner_join(bundle_child)
 			.on(bundle.name == bundle_child.parent)
-			.select(
-				bundle.name,
-				bundle_child.serial_no,
-				(bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"),
-			)
+			.select((bundle_child.incoming_rate * bundle_child.qty).as_("incoming_rate"))
 			.where(
 				(bundle.is_cancelled == 0)
 				& (bundle.docstatus == 1)
-				& (bundle_child.serial_no.isin(serial_nos))
-				& (bundle.type_of_transaction.isin(["Inward", "Outward"]))
+				& (bundle_child.serial_no == serial_no)
+				& (bundle.type_of_transaction == "Inward")
+				& (bundle_child.qty > 0)
 				& (bundle.item_code == self.sle.item_code)
 				& (bundle_child.warehouse == self.sle.warehouse)
 			)
-			.orderby(bundle.posting_date, bundle.posting_time, bundle.creation)
+			.orderby(Timestamp(bundle.posting_date, bundle.posting_time), order=Order.desc)
+			.limit(1)
 		)
 
 		# Important to exclude the current voucher to calculate correct the stock value difference
@@ -474,7 +475,8 @@
 
 			query = query.where(timestamp_condition)
 
-		return query.run(as_dict=True)
+		incoming_rate = query.run()
+		return flt(incoming_rate[0][0]) if incoming_rate else 0.0
 
 	def get_serial_nos(self):
 		if self.sle.get("serial_nos"):
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 2ae6c19..2b62baf 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -952,7 +952,12 @@
 					get_rate_for_return,  # don't move this import to top
 				)
 
-				if self.valuation_method == "Moving Average":
+				if (
+					self.valuation_method == "Moving Average"
+					and not sle.get("serial_no")
+					and not sle.get("batch_no")
+					and not sle.get("serial_and_batch_bundle")
+				):
 					rate = get_incoming_rate(
 						{
 							"item_code": sle.item_code,
@@ -979,6 +984,18 @@
 						voucher_detail_no=sle.voucher_detail_no,
 						sle=sle,
 					)
+
+				if (
+					sle.get("serial_and_batch_bundle")
+					and rate > 0
+					and sle.voucher_type in ["Delivery Note", "Sales Invoice"]
+				):
+					frappe.db.set_value(
+						sle.voucher_type + " Item",
+						sle.voucher_detail_no,
+						"incoming_rate",
+						rate,
+					)
 			elif (
 				sle.voucher_type in ["Purchase Receipt", "Purchase Invoice"]
 				and sle.voucher_detail_no