fix: incorrect batch fetched for the serialized items (#20119)

* fix: incorrect batch fetched for the serialized items

* Update stock_controller.py

Co-authored-by: Nabin Hait <nabinhait@gmail.com>
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 542073e..f4a13d5 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -20,6 +20,7 @@
 	def validate(self):
 		super(StockController, self).validate()
 		self.validate_inspection()
+		self.validate_serialized_batch()
 
 	def make_gl_entries(self, gl_entries=None, repost_future_gle=True, from_repost=False):
 		if self.docstatus == 2:
@@ -42,6 +43,17 @@
 			gl_entries = self.get_asset_gl_entry(gl_entries)
 			make_gl_entries(gl_entries, from_repost=from_repost)
 
+	def validate_serialized_batch(self):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+		for d in self.get("items"):
+			if hasattr(d, 'serial_no') and hasattr(d, 'batch_no') and d.serial_no and d.batch_no:
+				serial_nos = get_serial_nos(d.serial_no)
+				for serial_no_data in frappe.get_all("Serial No",
+					filters={"name": ("in", serial_nos)}, fields=["batch_no", "name"]):
+					if serial_no_data.batch_no != d.batch_no:
+						frappe.throw(_("Row #{0}: Serial No {1} does not belong to Batch {2}")
+							.format(d.idx, serial_no_data.name, d.batch_no))
+
 	def get_gl_entries(self, warehouse_account=None, default_expense_account=None,
 			default_cost_center=None):
 
diff --git a/erpnext/stock/doctype/batch/batch.py b/erpnext/stock/doctype/batch/batch.py
index 0524eee..8ae978e 100644
--- a/erpnext/stock/doctype/batch/batch.py
+++ b/erpnext/stock/doctype/batch/batch.py
@@ -226,16 +226,14 @@
 		warehouse = d.get(warehouse_field, None)
 		if has_batch_no and warehouse and qty > 0:
 			if not d.batch_no:
-				d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw)
+				d.batch_no = get_batch_no(d.item_code, warehouse, qty, throw, d.serial_no)
 			else:
 				batch_qty = get_batch_qty(batch_no=d.batch_no, warehouse=warehouse)
 				if flt(batch_qty, d.precision("qty")) < flt(qty, d.precision("qty")):
 					frappe.throw(_("Row #{0}: The batch {1} has only {2} qty. Please select another batch which has {3} qty available or split the row into multiple rows, to deliver/issue from multiple batches").format(d.idx, d.batch_no, batch_qty, qty))
 
-
-
 @frappe.whitelist()
-def get_batch_no(item_code, warehouse, qty=1, throw=False):
+def get_batch_no(item_code, warehouse, qty=1, throw=False, serial_no=None):
 	"""
 	Get batch number using First Expiring First Out method.
 	:param item_code: `item_code` of Item Document
@@ -245,7 +243,7 @@
 	"""
 
 	batch_no = None
-	batches = get_batches(item_code, warehouse, qty, throw)
+	batches = get_batches(item_code, warehouse, qty, throw, serial_no)
 
 	for batch in batches:
 		if cint(qty) <= cint(batch.qty):
@@ -260,7 +258,23 @@
 	return batch_no
 
 
-def get_batches(item_code, warehouse, qty=1, throw=False):
+def get_batches(item_code, warehouse, qty=1, throw=False, serial_no=None):
+	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+	cond = ''
+	if serial_no:
+		batch = frappe.get_all("Serial No",
+			fields = ["distinct batch_no"],
+			filters= {
+				"item_code": item_code,
+				"warehouse": warehouse,
+				"name": ("in", get_serial_nos(serial_no))
+			}
+		)
+
+		if batch and len(batch) > 1:
+			return []
+
+		cond = " and `tabBatch`.name = %s" %(frappe.db.escape(batch[0].batch_no))
 
 	return frappe.db.sql("""
 		select batch_id, sum(`tabStock Ledger Entry`.actual_qty) as qty
@@ -268,7 +282,7 @@
 			join `tabStock Ledger Entry` ignore index (item_code, warehouse)
 				on (`tabBatch`.batch_id = `tabStock Ledger Entry`.batch_no )
 		where `tabStock Ledger Entry`.item_code = %s and `tabStock Ledger Entry`.warehouse = %s
-			and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL)
+			and (`tabBatch`.expiry_date >= CURDATE() or `tabBatch`.expiry_date IS NULL) {0}
 		group by batch_id
 		order by `tabBatch`.expiry_date ASC, `tabBatch`.creation ASC
-	""", (item_code, warehouse), as_dict=True)
\ No newline at end of file
+	""".format(cond), (item_code, warehouse), as_dict=True)
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 400d1f4..01d54de 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -75,6 +75,7 @@
 			set_batch_nos(self, 's_warehouse')
 
 		self.set_incoming_rate()
+		self.validate_serialized_batch()
 		self.set_actual_qty()
 		self.calculate_rate_and_amount(update_finished_item_rate=False)