Merge pull request #39998 from rohitwaghchaure/fixed-10262

fix: Completed Work Orders report not working
diff --git a/erpnext/assets/doctype/asset/depreciation.py b/erpnext/assets/doctype/asset/depreciation.py
index df4593b..191675c 100644
--- a/erpnext/assets/doctype/asset/depreciation.py
+++ b/erpnext/assets/doctype/asset/depreciation.py
@@ -561,15 +561,14 @@
 def reverse_depreciation_entry_made_after_disposal(asset, date):
 	for row in asset.get("finance_books"):
 		asset_depr_schedule_doc = get_asset_depr_schedule_doc(asset.name, "Active", row.finance_book)
-		if not asset_depr_schedule_doc:
+		if not asset_depr_schedule_doc or not asset_depr_schedule_doc.get("depreciation_schedule"):
 			continue
 
 		for schedule_idx, schedule in enumerate(asset_depr_schedule_doc.get("depreciation_schedule")):
-			if schedule.schedule_date == date:
+			if schedule.schedule_date == date and schedule.journal_entry:
 				if not disposal_was_made_on_original_schedule_date(
 					schedule_idx, row, date
 				) or disposal_happens_in_the_future(date):
-
 					reverse_journal_entry = make_reverse_journal_entry(schedule.journal_entry)
 					reverse_journal_entry.posting_date = nowdate()
 
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
index 2f0de97..110f2c4 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.js
@@ -6,7 +6,7 @@
 
 erpnext.assets.AssetCapitalization = class AssetCapitalization extends erpnext.stock.StockController {
 	setup() {
-		this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
+		this.frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle', 'Asset Movement'];
 		this.setup_posting_date_time_check();
 	}
 
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index c9ed806..e27a492 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -138,6 +138,7 @@
 			"Repost Item Valuation",
 			"Serial and Batch Bundle",
 			"Asset",
+			"Asset Movement",
 		)
 		self.cancel_target_asset()
 		self.update_stock_ledger()
@@ -147,7 +148,7 @@
 	def cancel_target_asset(self):
 		if self.entry_type == "Capitalization" and self.target_asset:
 			asset_doc = frappe.get_doc("Asset", self.target_asset)
-			frappe.db.set_value("Asset", self.target_asset, "capitalized_in", None)
+			asset_doc.db_set("capitalized_in", None)
 			if asset_doc.docstatus == 1:
 				asset_doc.cancel()
 
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 146c03e..77469df 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -418,14 +418,13 @@
 			)
 
 			# Adjust depreciation amount in the last period based on the expected value after useful life
-			if row.expected_value_after_useful_life and (
-				(
-					n == cint(final_number_of_depreciations) - 1
-					and value_after_depreciation != row.expected_value_after_useful_life
+			if (
+				n == cint(final_number_of_depreciations) - 1
+				and flt(value_after_depreciation) != flt(row.expected_value_after_useful_life)
+			) or flt(value_after_depreciation) < flt(row.expected_value_after_useful_life):
+				depreciation_amount += flt(value_after_depreciation) - flt(
+					row.expected_value_after_useful_life
 				)
-				or value_after_depreciation < row.expected_value_after_useful_life
-			):
-				depreciation_amount += value_after_depreciation - row.expected_value_after_useful_life
 				skip_row = True
 
 			if flt(depreciation_amount, asset_doc.precision("gross_purchase_amount")) > 0:
@@ -813,15 +812,11 @@
 	asset_depr_schedules_names = []
 
 	for row in asset_doc.get("finance_books"):
-		draft_asset_depr_schedule_name = get_asset_depr_schedule_name(
-			asset_doc.name, "Draft", row.finance_book
+		asset_depr_schedule = get_asset_depr_schedule_name(
+			asset_doc.name, ["Draft", "Active"], row.finance_book
 		)
 
-		active_asset_depr_schedule_name = get_asset_depr_schedule_name(
-			asset_doc.name, "Active", row.finance_book
-		)
-
-		if not draft_asset_depr_schedule_name and not active_asset_depr_schedule_name:
+		if not asset_depr_schedule:
 			name = make_draft_asset_depr_schedule(asset_doc, row)
 			asset_depr_schedules_names.append(name)
 
@@ -997,16 +992,20 @@
 
 
 def get_asset_depr_schedule_name(asset_name, status, finance_book=None):
-	finance_book_filter = ["finance_book", "is", "not set"]
-	if finance_book:
+	if finance_book is None:
+		finance_book_filter = ["finance_book", "is", "not set"]
+	else:
 		finance_book_filter = ["finance_book", "=", finance_book]
 
+	if isinstance(status, str):
+		status = [status]
+
 	return frappe.db.get_value(
 		doctype="Asset Depreciation Schedule",
 		filters=[
 			["asset", "=", asset_name],
 			finance_book_filter,
-			["status", "=", status],
+			["status", "in", status],
 		],
 	)
 
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 27ac9d5..91ee53a 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -824,7 +824,8 @@
 		if self.doctype == "Purchase Invoice" and not self.get("update_stock"):
 			return
 
-		frappe.db.sql("delete from `tabAsset Movement` where reference_name=%s", self.name)
+		asset_movement = frappe.db.get_value("Asset Movement", {"reference_name": self.name}, "name")
+		frappe.delete_doc("Asset Movement", asset_movement, force=1)
 
 	def validate_schedule_date(self):
 		if not self.get("items"):
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index dc5ce5e..fdbfd10 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -7,7 +7,7 @@
 
 import frappe
 from frappe import _, bold
-from frappe.utils import cint, flt, get_link_to_form, getdate
+from frappe.utils import cint, cstr, flt, get_link_to_form, getdate
 
 import erpnext
 from erpnext.accounts.general_ledger import (
@@ -174,13 +174,16 @@
 			table_name = "stock_items"
 
 		for row in self.get(table_name):
+			if row.serial_and_batch_bundle and (row.serial_no or row.batch_no):
+				self.validate_serial_nos_and_batches_with_bundle(row)
+
 			if not row.serial_no and not row.batch_no and not row.get("rejected_serial_no"):
 				continue
 
 			if not row.use_serial_batch_fields and (
 				row.serial_no or row.batch_no or row.get("rejected_serial_no")
 			):
-				frappe.throw(_("Please enable Use Old Serial / Batch Fields to make_bundle"))
+				row.use_serial_batch_fields = 1
 
 			if row.use_serial_batch_fields and (
 				not row.serial_and_batch_bundle and not row.get("rejected_serial_and_batch_bundle")
@@ -232,6 +235,41 @@
 						}
 					)
 
+	def validate_serial_nos_and_batches_with_bundle(self, row):
+		from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
+
+		throw_error = False
+		if row.serial_no:
+			serial_nos = frappe.get_all(
+				"Serial and Batch Entry", fields=["serial_no"], filters={"parent": row.serial_and_batch_bundle}
+			)
+			serial_nos = sorted([cstr(d.serial_no) for d in serial_nos])
+			parsed_serial_nos = get_serial_nos(row.serial_no)
+
+			if len(serial_nos) != len(parsed_serial_nos):
+				throw_error = True
+			elif serial_nos != parsed_serial_nos:
+				for serial_no in serial_nos:
+					if serial_no not in parsed_serial_nos:
+						throw_error = True
+						break
+
+		elif row.batch_no:
+			batches = frappe.get_all(
+				"Serial and Batch Entry", fields=["batch_no"], filters={"parent": row.serial_and_batch_bundle}
+			)
+			batches = sorted([d.batch_no for d in batches])
+
+			if batches != [row.batch_no]:
+				throw_error = True
+
+		if throw_error:
+			frappe.throw(
+				_(
+					"At row {0}: Serial and Batch Bundle {1} has already created. Please remove the values from the serial no or batch no fields."
+				).format(row.idx, row.serial_and_batch_bundle)
+			)
+
 	def set_use_serial_batch_fields(self):
 		if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
 			for row in self.items:
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index 47762ac..95a7bcb 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -401,7 +401,7 @@
 			{
 				"main_item_code": "Subcontracted Item SA4",
 				"item_code": "Subcontracted SRM Item 3",
-				"qty": 1.0,
+				"qty": 3.0,
 				"rate": 100.0,
 				"stock_uom": "Nos",
 				"warehouse": "_Test Warehouse - _TC",
@@ -914,12 +914,6 @@
 		else child_row.get("consumed_qty")
 	)
 
-	if child_row.serial_no:
-		details.serial_no.extend(get_serial_nos(child_row.serial_no))
-
-	if child_row.batch_no:
-		details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
-
 	if child_row.serial_and_batch_bundle:
 		doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
 		for row in doc.get("entries"):
@@ -928,6 +922,12 @@
 
 			if row.batch_no:
 				details.batch_no[row.batch_no] += row.qty * (-1 if doc.type_of_transaction == "Outward" else 1)
+	else:
+		if child_row.serial_no:
+			details.serial_no.extend(get_serial_nos(child_row.serial_no))
+
+		if child_row.batch_no:
+			details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")
 
 
 def make_stock_transfer_entry(**args):
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 4ead7e7..a259540 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -357,3 +357,4 @@
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
 erpnext.stock.doctype.delivery_note.patches.drop_unused_return_against_index # 2023-12-20
 erpnext.patches.v14_0.set_maintain_stock_for_bom_item
+erpnext.patches.v15_0.delete_orphaned_asset_movement_item_records
\ No newline at end of file
diff --git a/erpnext/patches/v15_0/delete_orphaned_asset_movement_item_records.py b/erpnext/patches/v15_0/delete_orphaned_asset_movement_item_records.py
new file mode 100644
index 0000000..a1d7dc9
--- /dev/null
+++ b/erpnext/patches/v15_0/delete_orphaned_asset_movement_item_records.py
@@ -0,0 +1,11 @@
+import frappe
+
+
+def execute():
+	# nosemgrep
+	frappe.db.sql(
+		"""
+		DELETE FROM `tabAsset Movement Item`
+		WHERE parent NOT IN (SELECT name FROM `tabAsset Movement`)
+		"""
+	)
diff --git a/erpnext/public/js/controllers/buying.js b/erpnext/public/js/controllers/buying.js
index b3d301d..1d0d47e 100644
--- a/erpnext/public/js/controllers/buying.js
+++ b/erpnext/public/js/controllers/buying.js
@@ -368,6 +368,7 @@
 
 											let update_values = {
 												"serial_and_batch_bundle": r.name,
+												"use_serial_batch_fields": 0,
 												"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 											}
 
@@ -408,6 +409,7 @@
 
 											let update_values = {
 												"serial_and_batch_bundle": r.name,
+												"use_serial_batch_fields": 0,
 												"rejected_qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 											}
 
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 1975c34..2c985a0 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -145,6 +145,12 @@
 			});
 		}
 
+		if(this.frm.fields_dict['items'].grid.get_field('batch_no')) {
+			this.frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
+				return me.set_query_for_batch(doc, cdt, cdn);
+			});
+		}
+
 		if(
 			this.frm.docstatus < 2
 			&& this.frm.fields_dict["payment_terms_template"]
@@ -1633,18 +1639,6 @@
 		return item_list;
 	}
 
-	items_delete() {
-		this.update_localstorage_scanned_data();
-	}
-
-	update_localstorage_scanned_data() {
-		let doctypes = ["Sales Invoice", "Purchase Invoice", "Delivery Note", "Purchase Receipt"];
-		if (this.frm.is_new() && doctypes.includes(this.frm.doc.doctype)) {
-			const barcode_scanner = new erpnext.utils.BarcodeScanner({frm:this.frm});
-			barcode_scanner.update_localstorage_scanned_data();
-		}
-	}
-
 	_set_values_for_item_list(children) {
 		const items_rule_dict = {};
 
diff --git a/erpnext/public/js/utils/sales_common.js b/erpnext/public/js/utils/sales_common.js
index 4bb7843..a957530 100644
--- a/erpnext/public/js/utils/sales_common.js
+++ b/erpnext/public/js/utils/sales_common.js
@@ -339,6 +339,7 @@
 
 											frappe.model.set_value(item.doctype, item.name, {
 												"serial_and_batch_bundle": r.name,
+												"use_serial_batch_fields": 0,
 												"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 											});
 										}
diff --git a/erpnext/public/js/utils/serial_no_batch_selector.js b/erpnext/public/js/utils/serial_no_batch_selector.js
index 80ade70..fccaf88 100644
--- a/erpnext/public/js/utils/serial_no_batch_selector.js
+++ b/erpnext/public/js/utils/serial_no_batch_selector.js
@@ -71,6 +71,10 @@
 		let warehouse = this.item?.type_of_transaction === "Outward" ?
 			(this.item.warehouse || this.item.s_warehouse) : "";
 
+		if (this.frm.doc.doctype === 'Stock Entry') {
+			warehouse = this.item.s_warehouse || this.item.t_warehouse;
+		}
+
 		if (!warehouse && this.frm.doc.doctype === 'Stock Reconciliation') {
 			warehouse = this.get_warehouse();
 		}
@@ -367,19 +371,11 @@
 					label: __('Batch No'),
 					in_list_view: 1,
 					get_query: () => {
-						if (this.item.type_of_transaction !== "Outward") {
-							return {
-								filters: {
-									'item': this.item.item_code,
-								}
-							}
-						} else {
-							return {
-								query : "erpnext.controllers.queries.get_batch_no",
-								filters: {
-									'item_code': this.item.item_code,
-									'warehouse': this.get_warehouse()
-								}
+						return {
+							query : "erpnext.controllers.queries.get_batch_no",
+							filters: {
+								'item_code': this.item.item_code,
+								'warehouse': this.item.s_warehouse || this.item.t_warehouse,
 							}
 						}
 					},
diff --git a/erpnext/stock/doctype/material_request/material_request.js b/erpnext/stock/doctype/material_request/material_request.js
index 77a3d6d..1b40f2b 100644
--- a/erpnext/stock/doctype/material_request/material_request.js
+++ b/erpnext/stock/doctype/material_request/material_request.js
@@ -250,7 +250,7 @@
 			fields: [
 				{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
 					options:"BOM", reqd: 1, get_query: function() {
-						return {filters: { docstatus:1 }};
+						return {filters: { docstatus:1, "is_active": 1 }};
 					}},
 				{"fieldname":"warehouse", "fieldtype":"Link", "label":__("For Warehouse"),
 					options:"Warehouse", reqd: 1},
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 056cd5c..3a5daa1 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -330,6 +330,7 @@
 									let qty = Math.abs(r.total_qty);
 									frappe.model.set_value(item.doctype, item.name, {
 										"serial_and_batch_bundle": r.name,
+										"use_serial_batch_fields": 0,
 										"qty": qty / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 									});
 								}
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 0e1f8d7..8a1f79d 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -73,6 +73,10 @@
 		self.update_status()
 		self.set_item_locations()
 
+		if self.get("locations"):
+			self.validate_sales_order_percentage()
+
+	def validate_sales_order_percentage(self):
 		# set percentage picked in SO
 		for location in self.get("locations"):
 			if (
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index 3afed4b..d5bc14b 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -2317,6 +2317,30 @@
 			serial_no_status = frappe.db.get_value("Serial No", sn, "status")
 			self.assertTrue(serial_no_status != "Active")
 
+	def test_auto_set_batch_based_on_bundle(self):
+		item_code = make_item(
+			"_Test Auto Set Batch Based on Bundle",
+			properties={
+				"has_batch_no": 1,
+				"batch_number_series": "BATCH-BNU-TASBBB-.#####",
+				"create_new_batch": 1,
+			},
+		).name
+
+		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)
+
+		pr = make_purchase_receipt(
+			item_code=item_code,
+			qty=5,
+			rate=100,
+		)
+
+		self.assertTrue(pr.items[0].batch_no)
+		batch_no = get_batch_from_bundle(pr.items[0].serial_and_batch_bundle)
+		self.assertEqual(pr.items[0].batch_no, batch_no)
+
+		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
+
 
 def prepare_data_for_internal_transfer():
 	from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
index 31fc2ca..a383798 100644
--- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
+++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py
@@ -294,9 +294,20 @@
 		doc.log_error("Unable to repost item valuation")
 
 		message = frappe.message_log.pop() if frappe.message_log else ""
+		if isinstance(message, dict):
+			message = message.get("message")
+
 		if traceback:
-			message += "<br>" + "Traceback: <br>" + traceback
-		frappe.db.set_value(doc.doctype, doc.name, "error_log", message)
+			message += "<br><br>" + "<b>Traceback:</b> <br>" + traceback
+
+		frappe.db.set_value(
+			doc.doctype,
+			doc.name,
+			{
+				"error_log": message,
+				"status": "Failed",
+			},
+		)
 
 		outgoing_email_account = frappe.get_cached_value(
 			"Email Account", {"default_outgoing": 1, "enable_outgoing": 1}, "name"
diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
index 91b7430..1f7bb4d 100644
--- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
+++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.js
@@ -207,13 +207,24 @@
 			};
 		});
 
-		frm.set_query('batch_no', 'entries', () => {
-			return {
-				filters: {
-					item: frm.doc.item_code,
-					disabled: 0,
+		frm.set_query('batch_no', 'entries', (doc) => {
+
+			if (doc.type_of_transaction ==="Outward") {
+				return {
+					query : "erpnext.controllers.queries.get_batch_no",
+					filters: {
+						item_code: doc.item_code,
+						warehouse: doc.warehouse,
+					}
 				}
-			};
+			} else {
+				return {
+					filters: {
+						item: doc.item_code,
+						disabled: 0,
+					}
+				};
+			}
 		});
 
 		frm.set_query('warehouse', 'entries', () => {
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.js b/erpnext/stock/doctype/stock_entry/stock_entry.js
index 8c76291..7f79f04 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.js
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.js
@@ -543,7 +543,9 @@
 
 		let fields = [
 			{"fieldname":"bom", "fieldtype":"Link", "label":__("BOM"),
-			options:"BOM", reqd: 1, get_query: filters()},
+			options:"BOM", reqd: 1, get_query: () => {
+				return {filters: { docstatus:1, "is_active": 1 }};
+			}},
 			{"fieldname":"source_warehouse", "fieldtype":"Link", "label":__("Source Warehouse"),
 			options:"Warehouse"},
 			{"fieldname":"target_warehouse", "fieldtype":"Link", "label":__("Target Warehouse"),
@@ -1178,6 +1180,7 @@
 							if (r) {
 								frappe.model.set_value(item.doctype, item.name, {
 									"serial_and_batch_bundle": r.name,
+									"use_serial_batch_fields": 0,
 									"qty": Math.abs(r.total_qty) / flt(item.conversion_factor || 1, precision("conversion_factor", item))
 								});
 							}
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 276b2f4..9f1e523 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -839,6 +839,7 @@
 					currency=erpnext.get_company_currency(self.company),
 					company=self.company,
 					raise_error_if_no_rate=raise_error_if_no_rate,
+					batch_no=d.batch_no,
 					serial_and_batch_bundle=d.serial_and_batch_bundle,
 				)
 
@@ -867,7 +868,7 @@
 				if reset_outgoing_rate:
 					args = self.get_args_for_incoming_rate(d)
 					rate = get_incoming_rate(args, raise_error_if_no_rate)
-					if rate > 0:
+					if rate >= 0:
 						d.basic_rate = rate
 
 				d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
@@ -890,6 +891,8 @@
 				"allow_zero_valuation": item.allow_zero_valuation_rate,
 				"serial_and_batch_bundle": item.serial_and_batch_bundle,
 				"voucher_detail_no": item.name,
+				"batch_no": item.batch_no,
+				"serial_no": item.serial_no,
 			}
 		)
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index ce08615..c69b20b 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -834,6 +834,7 @@
 			if voucher_detail_no != row.name:
 				continue
 
+			val_rate = 0.0
 			current_qty = 0.0
 			if row.current_serial_and_batch_bundle:
 				current_qty = self.get_current_qty_for_serial_or_batch(row)
@@ -843,7 +844,6 @@
 					row.warehouse,
 					self.posting_date,
 					self.posting_time,
-					voucher_no=self.name,
 				)
 
 				current_qty = item_dict.get("qty")
@@ -885,7 +885,7 @@
 					{"voucher_detail_no": row.name, "actual_qty": ("<", 0), "is_cancelled": 0},
 					"name",
 				)
-				and (not row.current_serial_and_batch_bundle and not row.batch_no)
+				and (not row.current_serial_and_batch_bundle)
 			):
 				self.set_current_serial_and_batch_bundle(voucher_detail_no, save=True)
 				row.reload()
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.js b/erpnext/stock/report/stock_ledger/stock_ledger.js
index b00b422..2ec757b 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.js
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.js
@@ -91,6 +91,12 @@
 			"options": "Currency\nFloat",
 			"default": "Currency"
 		},
+		{
+			"fieldname": "segregate_serial_batch_bundle",
+			"label": __("Segregate Serial / Batch Bundle"),
+			"fieldtype": "Check",
+			"default": 0
+		}
 	],
 	"formatter": function (value, row, column, data, default_formatter) {
 		value = default_formatter(value, row, column, data);
diff --git a/erpnext/stock/report/stock_ledger/stock_ledger.py b/erpnext/stock/report/stock_ledger/stock_ledger.py
index 86af9e9..5076435 100644
--- a/erpnext/stock/report/stock_ledger/stock_ledger.py
+++ b/erpnext/stock/report/stock_ledger/stock_ledger.py
@@ -2,6 +2,8 @@
 # License: GNU General Public License v3. See license.txt
 
 
+import copy
+
 import frappe
 from frappe import _
 from frappe.query_builder.functions import CombineDatetime
@@ -26,6 +28,10 @@
 	item_details = get_item_details(items, sl_entries, include_uom)
 	opening_row = get_opening_balance(filters, columns, sl_entries)
 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
+	bundle_details = {}
+
+	if filters.get("segregate_serial_batch_bundle"):
+		bundle_details = get_serial_batch_bundle_details(sl_entries)
 
 	data = []
 	conversion_factors = []
@@ -45,6 +51,9 @@
 		item_detail = item_details[sle.item_code]
 
 		sle.update(item_detail)
+		if bundle_info := bundle_details.get(sle.serial_and_batch_bundle):
+			data.extend(get_segregated_bundle_entries(sle, bundle_info))
+			continue
 
 		if filters.get("batch_no") or inventory_dimension_filters_applied:
 			actual_qty += flt(sle.actual_qty, precision)
@@ -76,6 +85,60 @@
 	return columns, data
 
 
+def get_segregated_bundle_entries(sle, bundle_details):
+	segregated_entries = []
+	qty_before_transaction = sle.qty_after_transaction - sle.actual_qty
+	stock_value_before_transaction = sle.stock_value - sle.stock_value_difference
+
+	for row in bundle_details:
+		new_sle = copy.deepcopy(sle)
+		new_sle.update(row)
+
+		new_sle.update(
+			{
+				"in_out_rate": flt(new_sle.stock_value_difference / row.qty) if row.qty else 0,
+				"in_qty": row.qty if row.qty > 0 else 0,
+				"out_qty": row.qty if row.qty < 0 else 0,
+				"qty_after_transaction": qty_before_transaction + row.qty,
+				"stock_value": stock_value_before_transaction + new_sle.stock_value_difference,
+				"incoming_rate": row.incoming_rate if row.qty > 0 else 0,
+			}
+		)
+
+		qty_before_transaction += row.qty
+		stock_value_before_transaction += new_sle.stock_value_difference
+
+		new_sle.valuation_rate = (
+			stock_value_before_transaction / qty_before_transaction if qty_before_transaction else 0
+		)
+
+		segregated_entries.append(new_sle)
+
+	return segregated_entries
+
+
+def get_serial_batch_bundle_details(sl_entries):
+	bundle_details = []
+	for sle in sl_entries:
+		if sle.serial_and_batch_bundle:
+			bundle_details.append(sle.serial_and_batch_bundle)
+
+	if not bundle_details:
+		return frappe._dict({})
+
+	_bundle_details = frappe._dict({})
+	batch_entries = frappe.get_all(
+		"Serial and Batch Entry",
+		filters={"parent": ("in", bundle_details)},
+		fields=["parent", "qty", "incoming_rate", "stock_value_difference", "batch_no", "serial_no"],
+		order_by="parent, idx",
+	)
+	for entry in batch_entries:
+		_bundle_details.setdefault(entry.parent, []).append(entry)
+
+	return _bundle_details
+
+
 def update_available_serial_nos(available_serial_nos, sle):
 	serial_nos = get_serial_nos(sle.serial_no)
 	key = (sle.item_code, sle.warehouse)
@@ -256,7 +319,6 @@
 				"options": "Serial and Batch Bundle",
 				"width": 100,
 			},
-			{"label": _("Balance Serial No"), "fieldname": "balance_serial_no", "width": 100},
 			{
 				"label": _("Project"),
 				"fieldname": "project",
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index d8b5b34..fc1cca4 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -5,7 +5,7 @@
 from frappe import _, bold
 from frappe.model.naming import make_autoname
 from frappe.query_builder.functions import CombineDatetime, Sum
-from frappe.utils import cint, flt, get_link_to_form, now, nowtime, today
+from frappe.utils import cint, cstr, flt, get_link_to_form, now, nowtime, today
 
 from erpnext.stock.deprecated_serial_batch import (
 	DeprecatedBatchNoValuation,
@@ -138,9 +138,17 @@
 				self.child_doctype, self.sle.voucher_detail_no, "rejected_serial_and_batch_bundle", sn_doc.name
 			)
 		else:
-			frappe.db.set_value(
-				self.child_doctype, self.sle.voucher_detail_no, "serial_and_batch_bundle", sn_doc.name
-			)
+			values_to_update = {
+				"serial_and_batch_bundle": sn_doc.name,
+			}
+
+			if frappe.db.get_single_value("Stock Settings", "use_serial_batch_fields"):
+				if sn_doc.has_serial_no:
+					values_to_update["serial_no"] = "\n".join(cstr(d.serial_no) for d in sn_doc.entries)
+				elif sn_doc.has_batch_no and len(sn_doc.entries) == 1:
+					values_to_update["batch_no"] = sn_doc.entries[0].batch_no
+
+			frappe.db.set_value(self.child_doctype, self.sle.voucher_detail_no, values_to_update)
 
 	@property
 	def child_doctype(self):
@@ -905,8 +913,6 @@
 			self.batches = get_available_batches(kwargs)
 
 	def set_auto_serial_batch_entries_for_inward(self):
-		print(self.get("serial_nos"))
-
 		if (self.get("batches") and self.has_batch_no) or (
 			self.get("serial_nos") and self.has_serial_no
 		):
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index e88b192..d0815c9 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -899,7 +899,7 @@
 
 		precision = doc.precision("total_qty")
 		self.wh_data.qty_after_transaction += flt(doc.total_qty, precision)
-		if self.wh_data.qty_after_transaction:
+		if flt(self.wh_data.qty_after_transaction, precision):
 			self.wh_data.valuation_rate = flt(self.wh_data.stock_value, precision) / flt(
 				self.wh_data.qty_after_transaction, precision
 			)
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 9eac172..00f030e 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -262,7 +262,7 @@
 			item_code=args.get("item_code"),
 		)
 
-		in_rate = sn_obj.get_incoming_rate()
+		return sn_obj.get_incoming_rate()
 
 	elif item_details and item_details.has_batch_no and args.get("serial_and_batch_bundle"):
 		args.actual_qty = args.qty
@@ -272,23 +272,33 @@
 			item_code=args.get("item_code"),
 		)
 
-		in_rate = batch_obj.get_incoming_rate()
+		return batch_obj.get_incoming_rate()
 
 	elif (args.get("serial_no") or "").strip() and not args.get("serial_and_batch_bundle"):
-		in_rate = get_avg_purchase_rate(args.get("serial_no"))
+		args.actual_qty = args.qty
+		args.serial_nos = get_serial_nos_data(args.get("serial_no"))
+
+		sn_obj = SerialNoValuation(
+			sle=args, warehouse=args.get("warehouse"), item_code=args.get("item_code")
+		)
+
+		return sn_obj.get_incoming_rate()
 	elif (
 		args.get("batch_no")
 		and frappe.db.get_value("Batch", args.get("batch_no"), "use_batchwise_valuation", cache=True)
 		and not args.get("serial_and_batch_bundle")
 	):
-		in_rate = get_batch_incoming_rate(
-			item_code=args.get("item_code"),
+
+		args.actual_qty = args.qty
+		args.batch_nos = frappe._dict({args.batch_no: args})
+
+		batch_obj = BatchNoValuation(
+			sle=args,
 			warehouse=args.get("warehouse"),
-			batch_no=args.get("batch_no"),
-			posting_date=args.get("posting_date"),
-			posting_time=args.get("posting_time"),
+			item_code=args.get("item_code"),
 		)
 
+		return batch_obj.get_incoming_rate()
 	else:
 		valuation_method = get_valuation_method(args.get("item_code"))
 		previous_sle = get_previous_sle(args)