fix: use serial batch fields for subcontracting receipt (#40311)

diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index c46ef50..a477a0d 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -439,9 +439,21 @@
 
 	filtered_batches = get_filterd_batches(batches)
 
+	if filters.get("is_inward"):
+		filtered_batches.extend(get_empty_batches(filters))
+
 	return filtered_batches
 
 
+def get_empty_batches(filters):
+	return frappe.get_all(
+		"Batch",
+		fields=["name", "batch_qty"],
+		filters={"item": filters.get("item_code"), "batch_qty": 0.0},
+		as_list=1,
+	)
+
+
 def get_filterd_batches(data):
 	batches = OrderedDict()
 
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index a67fbdc..2f05ec3 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -190,43 +190,23 @@
 			if row.use_serial_batch_fields and (
 				not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
 			):
-				if self.doctype == "Stock Reconciliation":
-					qty = row.qty
-					type_of_transaction = "Inward"
-					warehouse = row.warehouse
-				elif table_name == "packed_items":
-					qty = row.qty
-					warehouse = row.warehouse
-					type_of_transaction = "Outward"
-					if self.is_return:
-						type_of_transaction = "Inward"
-				else:
-					qty = row.stock_qty if self.doctype != "Stock Entry" else row.transfer_qty
-					type_of_transaction = get_type_of_transaction(self, row)
-					warehouse = (
-						row.warehouse if self.doctype != "Stock Entry" else row.s_warehouse or row.t_warehouse
-					)
+				bundle_details = {
+					"item_code": row.item_code,
+					"posting_date": self.posting_date,
+					"posting_time": self.posting_time,
+					"voucher_type": self.doctype,
+					"voucher_no": self.name,
+					"voucher_detail_no": row.name,
+					"company": self.company,
+					"is_rejected": 1 if row.get("rejected_warehouse") else 0,
+					"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
+					"batch_no": row.batch_no,
+					"use_serial_batch_fields": row.use_serial_batch_fields,
+					"do_not_submit": True,
+				}
 
-				sn_doc = SerialBatchCreation(
-					{
-						"item_code": row.item_code,
-						"warehouse": warehouse,
-						"posting_date": self.posting_date,
-						"posting_time": self.posting_time,
-						"voucher_type": self.doctype,
-						"voucher_no": self.name,
-						"voucher_detail_no": row.name,
-						"qty": qty,
-						"type_of_transaction": type_of_transaction,
-						"company": self.company,
-						"is_rejected": 1 if row.get("rejected_warehouse") else 0,
-						"serial_nos": get_serial_nos(row.serial_no) if row.serial_no else None,
-						"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
-						"batch_no": row.batch_no,
-						"use_serial_batch_fields": row.use_serial_batch_fields,
-						"do_not_submit": True,
-					}
-				).make_serial_and_batch_bundle()
+				self.update_bundle_details(bundle_details, table_name, row)
+				sn_doc = SerialBatchCreation(bundle_details).make_serial_and_batch_bundle()
 
 				if sn_doc.is_rejected:
 					row.rejected_serial_and_batch_bundle = sn_doc.name
@@ -243,6 +223,34 @@
 						}
 					)
 
+	def update_bundle_details(self, bundle_details, table_name, row):
+		# Since qty field is different for different doctypes
+		qty = row.get("qty")
+		warehouse = row.get("warehouse")
+
+		if table_name == "packed_items":
+			type_of_transaction = "Inward"
+			if not self.is_return:
+				type_of_transaction = "Outward"
+		else:
+			type_of_transaction = get_type_of_transaction(self, row)
+
+		if hasattr(row, "stock_qty"):
+			qty = row.stock_qty
+
+		if self.doctype == "Stock Entry":
+			qty = row.transfer_qty
+			warehouse = row.s_warehouse or row.t_warehouse
+
+		bundle_details.update(
+			{
+				"qty": qty,
+				"type_of_transaction": type_of_transaction,
+				"warehouse": warehouse,
+				"batches": frappe._dict({row.batch_no: qty}) if row.batch_no else None,
+			}
+		)
+
 	def validate_serial_nos_and_batches_with_bundle(self, row):
 		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index fccaf88..d0064b3 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -371,11 +371,18 @@
 					label: __('Batch No'),
 					in_list_view: 1,
 					get_query: () => {
+						let is_inward = false;
+						if ((["Purchase Receipt", "Purchase Invoice"].includes(this.frm.doc.doctype) && !this.frm.doc.is_return)
+							|| (this.frm.doc.doctype === 'Stock Entry' && this.frm.doc.purpose === 'Material Receipt')) {
+							is_inward = true;
+						}
+
 						return {
 							query : "erpnext.controllers.queries.get_batch_no",
 							filters: {
 								'item_code': this.item.item_code,
-								'warehouse': this.item.s_warehouse || this.item.t_warehouse,
+								'warehouse': this.item.s_warehouse || this.item.t_warehouse || this.item.warehouse,
+								'is_inward': is_inward
 							}
 						}
 					},
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 dd59a5d..08cb3ca 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
@@ -1263,6 +1263,13 @@
 	if parent_doc.get("is_return"):
 		type_of_transaction = "Inward" if type_of_transaction == "Outward" else "Outward"
 
+	if parent_doc.get("doctype") == "Subcontracting Receipt":
+		type_of_transaction = "Outward"
+		if child_row.get("doctype") == "Subcontracting Receipt Item":
+			type_of_transaction = "Inward"
+	elif parent_doc.get("doctype") == "Stock Reconciliation":
+		type_of_transaction = "Inward"
+
 	return type_of_transaction
 
 
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index d8d9d68..f79803c 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -10,17 +10,7 @@
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
 from frappe.query_builder.functions import Sum
-from frappe.utils import (
-	cint,
-	comma_or,
-	cstr,
-	flt,
-	format_time,
-	formatdate,
-	getdate,
-	month_diff,
-	nowdate,
-)
+from frappe.utils import cint, comma_or, cstr, flt, format_time, formatdate, getdate, nowdate
 
 import erpnext
 from erpnext.accounts.general_ledger import process_gl_map
@@ -237,41 +227,6 @@
 			self.reset_default_field_value("from_warehouse", "items", "s_warehouse")
 			self.reset_default_field_value("to_warehouse", "items", "t_warehouse")
 
-	def submit(self):
-		if self.is_enqueue_action():
-			frappe.msgprint(
-				_(
-					"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Draft stage"
-				)
-			)
-			self.queue_action("submit", timeout=2000)
-		else:
-			self._submit()
-
-	def cancel(self):
-		if self.is_enqueue_action():
-			frappe.msgprint(
-				_(
-					"The task has been enqueued as a background job. In case there is any issue on processing in background, the system will add a comment about the error on this Stock Entry and revert to the Submitted stage"
-				)
-			)
-			self.queue_action("cancel", timeout=2000)
-		else:
-			self._cancel()
-
-	def is_enqueue_action(self, force=False) -> bool:
-		if force:
-			return True
-
-		if frappe.flags.in_test:
-			return False
-
-		# If line items are more than 100 or record is older than 6 months
-		if len(self.items) > 50 or month_diff(nowdate(), self.posting_date) > 6:
-			return True
-
-		return False
-
 	def on_submit(self):
 		self.validate_closed_subcontracting_order()
 		self.make_bundle_using_old_serial_batch_fields()
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 9d1a3f7..39166e2 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1642,36 +1642,6 @@
 
 		self.assertRaises(frappe.ValidationError, sr_doc.submit)
 
-	def test_enqueue_action(self):
-		frappe.flags.in_test = False
-		item_code = "Test Enqueue Item - 001"
-		create_item(item_code=item_code, is_stock_item=1, valuation_rate=10)
-
-		doc = make_stock_entry(
-			item_code=item_code,
-			posting_date=add_to_date(today(), months=-7),
-			posting_time="00:00:00",
-			purpose="Material Receipt",
-			qty=10,
-			to_warehouse="_Test Warehouse - _TC",
-			do_not_submit=True,
-		)
-
-		self.assertTrue(doc.is_enqueue_action())
-
-		doc = make_stock_entry(
-			item_code=item_code,
-			posting_date=today(),
-			posting_time="00:00:00",
-			purpose="Material Receipt",
-			qty=10,
-			to_warehouse="_Test Warehouse - _TC",
-			do_not_submit=True,
-		)
-
-		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(
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 0450038..4738a70 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -1061,6 +1061,77 @@
 
 		self.assertTrue(frappe.db.get_value("Purchase Receipt", {"subcontracting_receipt": scr.name}))
 
+	def test_use_serial_batch_fields_for_subcontracting_receipt(self):
+		fg_item = make_item(
+			"Test Subcontracted Item With Batch No",
+			properties={
+				"is_stock_item": 1,
+				"has_batch_no": 1,
+				"create_new_batch": 1,
+				"batch_number_series": "BATCH-BNGS-.####",
+				"is_sub_contracted_item": 1,
+			},
+		).name
+
+		make_item(
+			"Test Subcontracted Item With Batch No Service Item 1",
+			properties={"is_stock_item": 0},
+		)
+
+		make_bom(
+			item=fg_item,
+			raw_materials=[
+				make_item(
+					"Test Subcontracted Item With Batch No RM Item 1",
+					properties={
+						"is_stock_item": 1,
+						"has_batch_no": 1,
+						"create_new_batch": 1,
+						"batch_number_series": "BATCH-RM-BNGS-.####",
+					},
+				).name
+			],
+		)
+
+		service_items = [
+			{
+				"warehouse": "_Test Warehouse - _TC",
+				"item_code": "Test Subcontracted Item With Batch No Service Item 1",
+				"qty": 1,
+				"rate": 100,
+				"fg_item": fg_item,
+				"fg_item_qty": 1,
+			},
+		]
+		sco = get_subcontracting_order(service_items=service_items)
+		rm_items = get_rm_items(sco.supplied_items)
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		make_stock_transfer_entry(
+			sco_no=sco.name,
+			rm_items=rm_items,
+			itemwise_details=copy.deepcopy(itemwise_details),
+		)
+
+		batch_no = "BATCH-BNGS-0001"
+		if not frappe.db.exists("Batch", batch_no):
+			frappe.get_doc(
+				{
+					"doctype": "Batch",
+					"batch_id": batch_no,
+					"item": fg_item,
+				}
+			).insert()
+
+		scr = make_subcontracting_receipt(sco.name)
+		self.assertFalse(scr.items[0].serial_and_batch_bundle)
+		scr.items[0].use_serial_batch_fields = 1
+		scr.items[0].batch_no = batch_no
+
+		scr.save()
+		scr.submit()
+		scr.reload()
+		self.assertTrue(scr.items[0].serial_and_batch_bundle)
+
 
 def make_return_subcontracting_receipt(**args):
 	args = frappe._dict(args)