Merge pull request #36940 from ruthra-kumar/invalid_gain_loss_in_expense_claim

fix: invalid gain/loss JE created on base currency Expense Claim
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js
index e548b4c..b3cc1cb 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.js
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.js
@@ -13,10 +13,11 @@
 		});
 	},
 	refresh(frm) {
-		frm.add_custom_button(__('Unreconcile Transaction'), () => {
-			frm.call('remove_payment_entries')
-			.then( () => frm.refresh() );
-		});
+		if (!frm.is_dirty() && frm.doc.payment_entries.length > 0) {
+			frm.add_custom_button(__("Unreconcile Transaction"), () => {
+				frm.call("remove_payment_entries").then(() => frm.refresh());
+			});
+		}
 	},
 	bank_account: function (frm) {
 		set_bank_statement_filter(frm);
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 7ef5278..4ef35fd 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -116,7 +116,7 @@
 				"Journal Entry" as reference_type, t1.name as reference_name,
 				t1.posting_date, t1.remark as remarks, t2.name as reference_row,
 				{dr_or_cr} as amount, t2.is_advance, t2.exchange_rate,
-				t2.account_currency as currency
+				t2.account_currency as currency, t2.cost_center as cost_center
 			from
 				`tabJournal Entry` t1, `tabJournal Entry Account` t2
 			where
@@ -209,6 +209,7 @@
 								"amount": -(inv.outstanding_in_account_currency),
 								"posting_date": inv.posting_date,
 								"currency": inv.currency,
+								"cost_center": inv.cost_center,
 							}
 						)
 					)
@@ -357,6 +358,7 @@
 				"allocated_amount": allocated_amount,
 				"difference_amount": pay.get("difference_amount"),
 				"currency": inv.get("currency"),
+				"cost_center": pay.get("cost_center"),
 			}
 		)
 
@@ -431,6 +433,7 @@
 				"allocated_amount": flt(row.get("allocated_amount")),
 				"difference_amount": flt(row.get("difference_amount")),
 				"difference_account": row.get("difference_account"),
+				"cost_center": row.get("cost_center"),
 			}
 		)
 
@@ -603,7 +606,7 @@
 						inv.dr_or_cr: abs(inv.allocated_amount),
 						"reference_type": inv.against_voucher_type,
 						"reference_name": inv.against_voucher,
-						"cost_center": erpnext.get_default_cost_center(company),
+						"cost_center": inv.cost_center or erpnext.get_default_cost_center(company),
 						"exchange_rate": inv.exchange_rate,
 						"user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} against {inv.against_voucher}",
 					},
@@ -618,7 +621,7 @@
 						),
 						"reference_type": inv.voucher_type,
 						"reference_name": inv.voucher_no,
-						"cost_center": erpnext.get_default_cost_center(company),
+						"cost_center": inv.cost_center or erpnext.get_default_cost_center(company),
 						"exchange_rate": inv.exchange_rate,
 						"user_remark": f"{fmt_money(flt(inv.allocated_amount), currency=company_currency)} from {inv.voucher_no}",
 					},
@@ -657,4 +660,5 @@
 				inv.against_voucher_type,
 				inv.against_voucher,
 				None,
+				inv.cost_center,
 			)
diff --git a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
index 0f7e47a..ec718aa 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_allocation/payment_reconciliation_allocation.json
@@ -22,7 +22,8 @@
   "column_break_7",
   "difference_account",
   "exchange_rate",
-  "currency"
+  "currency",
+  "cost_center"
  ],
  "fields": [
   {
@@ -144,11 +145,17 @@
    "fieldtype": "Float",
    "label": "Exchange Rate",
    "read_only": 1
+  },
+  {
+   "fieldname": "cost_center",
+   "fieldtype": "Link",
+   "label": "Cost Center",
+   "options": "Cost Center"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2022-12-24 21:01:14.882747",
+ "modified": "2023-09-03 07:52:33.684217",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Reconciliation Allocation",
diff --git a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
index d300ea9..17f3900 100644
--- a/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
+++ b/erpnext/accounts/doctype/payment_reconciliation_payment/payment_reconciliation_payment.json
@@ -16,7 +16,8 @@
   "sec_break1",
   "remark",
   "currency",
-  "exchange_rate"
+  "exchange_rate",
+  "cost_center"
  ],
  "fields": [
   {
@@ -98,11 +99,17 @@
    "fieldtype": "Float",
    "hidden": 1,
    "label": "Exchange Rate"
+  },
+  {
+   "fieldname": "cost_center",
+   "fieldtype": "Link",
+   "label": "Cost Center",
+   "options": "Cost Center"
   }
  ],
  "istable": 1,
  "links": [],
- "modified": "2022-11-08 18:18:36.268760",
+ "modified": "2023-09-03 07:43:29.965353",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Reconciliation Payment",
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index 4947248..af1c066 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -126,7 +126,7 @@
 	def make_gl_entries(self, get_opening_entries=False):
 		gl_entries = self.get_gl_entries()
 		closing_entries = self.get_grouped_gl_entries(get_opening_entries=get_opening_entries)
-		if len(gl_entries) > 5000:
+		if len(gl_entries + closing_entries) > 3000:
 			frappe.enqueue(
 				process_gl_entries,
 				gl_entries=gl_entries,
diff --git a/erpnext/accounts/report/accounts_payable/accounts_payable.js b/erpnext/accounts/report/accounts_payable/accounts_payable.js
index e1a30a4..27a8570 100644
--- a/erpnext/accounts/report/accounts_payable/accounts_payable.js
+++ b/erpnext/accounts/report/accounts_payable/accounts_payable.js
@@ -38,24 +38,6 @@
 			}
 		},
 		{
-			"fieldname": "supplier",
-			"label": __("Supplier"),
-			"fieldtype": "Link",
-			"options": "Supplier",
-			on_change: () => {
-				var supplier = frappe.query_report.get_filter_value('supplier');
-				if (supplier) {
-					frappe.db.get_value('Supplier', supplier, "tax_id", function(value) {
-						frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
-					});
-				} else {
-					frappe.query_report.set_filter_value('tax_id', "");
-				}
-
-				frappe.query_report.refresh();
-			}
-		},
-		{
 			"fieldname": "party_account",
 			"label": __("Payable Account"),
 			"fieldtype": "Link",
@@ -113,10 +95,37 @@
 			"options": "Payment Terms Template"
 		},
 		{
+			"fieldname": "party_type",
+			"label": __("Party Type"),
+			"fieldtype": "Link",
+			"options": "Party Type",
+			get_query: () => {
+				return {
+					filters: {
+						'account_type': 'Payable'
+					}
+				};
+			},
+			on_change: () => {
+				frappe.query_report.set_filter_value('party', "");
+				let party_type = frappe.query_report.get_filter_value('party_type');
+				frappe.query_report.toggle_filter_display('supplier_group', frappe.query_report.get_filter_value('party_type') !== "Supplier");
+
+			}
+
+		},
+		{
+			"fieldname":"party",
+			"label": __("Party"),
+			"fieldtype": "Dynamic Link",
+			"options": "party_type",
+		},
+		{
 			"fieldname": "supplier_group",
 			"label": __("Supplier Group"),
 			"fieldtype": "Link",
-			"options": "Supplier Group"
+			"options": "Supplier Group",
+			"hidden": 1
 		},
 		{
 			"fieldname": "group_by_party",
@@ -134,12 +143,6 @@
 			"fieldtype": "Check",
 		},
 		{
-			"fieldname": "tax_id",
-			"label": __("Tax Id"),
-			"fieldtype": "Data",
-			"hidden": 1
-		},
-		{
 			"fieldname": "show_future_payments",
 			"label": __("Show Future Payments"),
 			"fieldtype": "Check",
diff --git a/erpnext/accounts/report/accounts_payable/test_accounts_payable.py b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py
new file mode 100644
index 0000000..cb84cf4
--- /dev/null
+++ b/erpnext/accounts/report/accounts_payable/test_accounts_payable.py
@@ -0,0 +1,67 @@
+import unittest
+
+import frappe
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils import add_days, flt, getdate, today
+
+from erpnext import get_default_cost_center
+from erpnext.accounts.doctype.payment_entry.payment_entry import get_payment_entry
+from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
+from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_sales_invoice
+from erpnext.accounts.report.accounts_payable.accounts_payable import execute
+from erpnext.accounts.test.accounts_mixin import AccountsTestMixin
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
+
+class TestAccountsReceivable(AccountsTestMixin, FrappeTestCase):
+	def setUp(self):
+		self.create_company()
+		self.create_customer()
+		self.create_item()
+		self.create_supplier(currency="USD", supplier_name="Test Supplier2")
+		self.create_usd_payable_account()
+
+	def tearDown(self):
+		frappe.db.rollback()
+
+	def test_accounts_receivable_with_supplier(self):
+		pi = self.create_purchase_invoice(do_not_submit=True)
+		pi.currency = "USD"
+		pi.conversion_rate = 80
+		pi.credit_to = self.creditors_usd
+		pi = pi.save().submit()
+
+		filters = {
+			"company": self.company,
+			"party_type": "Supplier",
+			"party": self.supplier,
+			"report_date": today(),
+			"range1": 30,
+			"range2": 60,
+			"range3": 90,
+			"range4": 120,
+		}
+
+		data = execute(filters)
+		self.assertEqual(data[1][0].get("outstanding"), 300)
+		self.assertEqual(data[1][0].get("currency"), "USD")
+
+	def create_purchase_invoice(self, do_not_submit=False):
+		frappe.set_user("Administrator")
+		pi = make_purchase_invoice(
+			item=self.item,
+			company=self.company,
+			supplier=self.supplier,
+			is_return=False,
+			update_stock=False,
+			posting_date=frappe.utils.datetime.date(2021, 5, 1),
+			do_not_save=1,
+			rate=300,
+			price_list_rate=300,
+			qty=1,
+		)
+
+		pi = pi.save()
+		if not do_not_submit:
+			pi = pi.submit()
+		return pi
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 0b4e577..cb8ec87 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -46,8 +46,7 @@
 				var customer = frappe.query_report.get_filter_value('customer');
 				var company = frappe.query_report.get_filter_value('company');
 				if (customer) {
-					frappe.db.get_value('Customer', customer, ["tax_id", "customer_name", "payment_terms"], function(value) {
-						frappe.query_report.set_filter_value('tax_id', value["tax_id"]);
+					frappe.db.get_value('Customer', customer, ["customer_name", "payment_terms"], function(value) {
 						frappe.query_report.set_filter_value('customer_name', value["customer_name"]);
 						frappe.query_report.set_filter_value('payment_terms', value["payment_terms"]);
 					});
@@ -59,7 +58,6 @@
 						}
 					}, "Customer");
 				} else {
-					frappe.query_report.set_filter_value('tax_id', "");
 					frappe.query_report.set_filter_value('customer_name', "");
 					frappe.query_report.set_filter_value('credit_limit', "");
 					frappe.query_report.set_filter_value('payment_terms', "");
@@ -173,12 +171,6 @@
 			"fieldtype": "Check",
 		},
 		{
-			"fieldname": "tax_id",
-			"label": __("Tax Id"),
-			"fieldtype": "Data",
-			"hidden": 1
-		},
-		{
 			"fieldname": "show_remarks",
 			"label": __("Show Remarks"),
 			"fieldtype": "Check",
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 751063a..3700f00 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -211,11 +211,10 @@
 			return
 
 		# amount in "Party Currency", if its supplied. If not, amount in company currency
-		for party_type in self.party_type:
-			if self.filters.get(scrub(party_type)):
-				amount = ple.amount_in_account_currency
-			else:
-				amount = ple.amount
+		if self.filters.get("party_type") and self.filters.get("party"):
+			amount = ple.amount_in_account_currency
+		else:
+			amount = ple.amount
 		amount_in_account_currency = ple.amount_in_account_currency
 
 		# update voucher
@@ -426,10 +425,9 @@
 		# customer / supplier name
 		party_details = self.get_party_details(row.party) or {}
 		row.update(party_details)
-		for party_type in self.party_type:
-			if self.filters.get(scrub(party_type)):
-				row.currency = row.account_currency
-				break
+
+		if self.filters.get("party_type") and self.filters.get("party"):
+			row.currency = row.account_currency
 		else:
 			row.currency = self.company_currency
 
@@ -765,6 +763,7 @@
 	def prepare_conditions(self):
 		self.qb_selection_filter = []
 		self.or_filters = []
+
 		for party_type in self.party_type:
 			party_type_field = scrub(party_type)
 			self.or_filters.append(self.ple.party_type == party_type)
@@ -800,6 +799,12 @@
 		if self.filters.get(party_type_field):
 			self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field))
 
+		if self.filters.get("party_type"):
+			self.qb_selection_filter.append(self.filters.party_type == self.ple.party_type)
+
+		if self.filters.get("party"):
+			self.qb_selection_filter.append(self.filters.party == self.ple.party)
+
 		if self.filters.party_account:
 			self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
 		else:
diff --git a/erpnext/accounts/report/balance_sheet/balance_sheet.js b/erpnext/accounts/report/balance_sheet/balance_sheet.js
index c65b9e8..ecc13d7 100644
--- a/erpnext/accounts/report/balance_sheet/balance_sheet.js
+++ b/erpnext/accounts/report/balance_sheet/balance_sheet.js
@@ -15,7 +15,6 @@
 		fieldtype: "Check",
 		default: 1,
 	});
-	console.log(frappe.query_reports["Balance Sheet"]["filters"]);
 
 	frappe.query_reports["Balance Sheet"]["filters"].push({
 		fieldname: "include_default_book_entries",
diff --git a/erpnext/accounts/test/accounts_mixin.py b/erpnext/accounts/test/accounts_mixin.py
index bf01362..0868860 100644
--- a/erpnext/accounts/test/accounts_mixin.py
+++ b/erpnext/accounts/test/accounts_mixin.py
@@ -126,6 +126,28 @@
 			acc = frappe.get_doc("Account", name)
 		self.debtors_usd = acc.name
 
+	def create_usd_payable_account(self):
+		account_name = "Creditors USD"
+		if not frappe.db.get_value(
+			"Account", filters={"account_name": account_name, "company": self.company}
+		):
+			acc = frappe.new_doc("Account")
+			acc.account_name = account_name
+			acc.parent_account = "Accounts Payable - " + self.company_abbr
+			acc.company = self.company
+			acc.account_currency = "USD"
+			acc.account_type = "Payable"
+			acc.insert()
+		else:
+			name = frappe.db.get_value(
+				"Account",
+				filters={"account_name": account_name, "company": self.company},
+				fieldname="name",
+				pluck=True,
+			)
+			acc = frappe.get_doc("Account", name)
+		self.creditors_usd = acc.name
+
 	def clear_old_entries(self):
 		doctype_list = [
 			"GL Entry",
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 1aefeaa..06d05cb 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -474,10 +474,12 @@
 
 			# update ref in advance entry
 			if voucher_type == "Journal Entry":
-				update_reference_in_journal_entry(entry, doc, do_not_save=True)
+				referenced_row = update_reference_in_journal_entry(entry, doc, do_not_save=False)
 				# advance section in sales/purchase invoice and reconciliation tool,both pass on exchange gain/loss
 				# amount and account in args
-				doc.make_exchange_gain_loss_journal(args)
+				# referenced_row is used to deduplicate gain/loss journal
+				entry.update({"referenced_row": referenced_row})
+				doc.make_exchange_gain_loss_journal([entry])
 			else:
 				update_reference_in_payment_entry(
 					entry, doc, do_not_save=True, skip_ref_details_update_for_pe=skip_ref_details_update_for_pe
@@ -627,6 +629,8 @@
 	if not do_not_save:
 		journal_entry.save(ignore_permissions=True)
 
+	return new_row.name
+
 
 def update_reference_in_payment_entry(
 	d, payment_entry, do_not_save=False, skip_ref_details_update_for_pe=False
@@ -1164,7 +1168,7 @@
 
 
 @frappe.whitelist()
-def get_coa(doctype, parent, is_root, chart=None):
+def get_coa(doctype, parent, is_root=None, chart=None):
 	from erpnext.accounts.doctype.account.chart_of_accounts.chart_of_accounts import (
 		build_tree_from_json,
 	)
@@ -1750,6 +1754,7 @@
 				ple.posting_date,
 				ple.due_date,
 				ple.account_currency.as_("currency"),
+				ple.cost_center.as_("cost_center"),
 				Sum(ple.amount).as_("amount"),
 				Sum(ple.amount_in_account_currency).as_("amount_in_account_currency"),
 			)
@@ -1812,6 +1817,7 @@
 				).as_("paid_amount_in_account_currency"),
 				Table("vouchers").due_date,
 				Table("vouchers").currency,
+				Table("vouchers").cost_center.as_("cost_center"),
 			)
 			.where(Criterion.all(filter_on_outstanding_amount))
 		)
@@ -1895,12 +1901,14 @@
 	ref2_dt,
 	ref2_dn,
 	ref2_detail_no,
+	cost_center,
 ) -> str:
 	journal_entry = frappe.new_doc("Journal Entry")
 	journal_entry.voucher_type = "Exchange Gain Or Loss"
 	journal_entry.company = company
 	journal_entry.posting_date = nowdate()
 	journal_entry.multi_currency = 1
+	journal_entry.is_system_generated = True
 
 	party_account_currency = frappe.get_cached_value("Account", party_account, "account_currency")
 
@@ -1919,7 +1927,7 @@
 			"party": party,
 			"account_currency": party_account_currency,
 			"exchange_rate": 0,
-			"cost_center": erpnext.get_default_cost_center(company),
+			"cost_center": cost_center or erpnext.get_default_cost_center(company),
 			"reference_type": ref1_dt,
 			"reference_name": ref1_dn,
 			"reference_detail_no": ref1_detail_no,
@@ -1935,7 +1943,7 @@
 			"account": gain_loss_account,
 			"account_currency": gain_loss_account_currency,
 			"exchange_rate": 1,
-			"cost_center": erpnext.get_default_cost_center(company),
+			"cost_center": cost_center or erpnext.get_default_cost_center(company),
 			"reference_type": ref2_dt,
 			"reference_name": ref2_dn,
 			"reference_detail_no": ref2_detail_no,
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index ddb09c1..b5a4c2d 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -840,7 +840,7 @@
 	je.voucher_type = "Depreciation Entry"
 	je.naming_series = depreciation_series
 	je.company = asset.company
-	je.remark = _("Depreciation Entry against asset {0}").format(asset_name)
+	je.remark = ("Depreciation Entry against asset {0}").format(asset_name)
 
 	je.append(
 		"accounts",
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 0bf2fbb..662e4b9 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -404,14 +404,11 @@
 	def get_gl_entries_for_consumed_asset_items(
 		self, gl_entries, target_account, target_against, precision
 	):
-		self.are_all_asset_items_non_depreciable = True
-
 		# Consumed Assets
 		for item in self.asset_items:
 			asset = frappe.get_doc("Asset", item.asset)
 
 			if asset.calculate_depreciation:
-				self.are_all_asset_items_non_depreciable = False
 				notes = _(
 					"This schedule was created when Asset {0} was consumed through Asset Capitalization {1}."
 				).format(
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index f6a1951..88faeee 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -118,6 +118,24 @@
 			frm.set_value("tax_withholding_category", frm.supplier_tds);
 		}
 	},
+
+	get_subcontracting_boms_for_finished_goods: function(fg_item) {
+		return frappe.call({
+			method:"erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom.get_subcontracting_boms_for_finished_goods",
+			args: {
+				fg_items: fg_item
+			},
+		});
+	},
+
+	get_subcontracting_boms_for_service_item: function(service_item) {
+		return frappe.call({
+			method:"erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom.get_subcontracting_boms_for_service_item",
+			args: {
+				service_item: service_item
+			},
+		});
+	},
 });
 
 frappe.ui.form.on("Purchase Order Item", {
@@ -132,15 +150,83 @@
 		}
 	},
 
-	qty: function(frm, cdt, cdn) {
+	item_code: async function(frm, cdt, cdn) {
 		if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
 			var row = locals[cdt][cdn];
 
-			if (row.qty) {
-				row.fg_item_qty = row.qty;
+			if (row.item_code && !row.fg_item) {
+				var result = await frm.events.get_subcontracting_boms_for_service_item(row.item_code)
+
+				if (result.message && Object.keys(result.message).length) {
+					var finished_goods = Object.keys(result.message);
+
+					// Set FG if only one active Subcontracting BOM is found
+					if (finished_goods.length === 1) {
+						row.fg_item = result.message[finished_goods[0]].finished_good;
+						row.uom = result.message[finished_goods[0]].finished_good_uom;
+						refresh_field("items");
+					} else {
+						const dialog = new frappe.ui.Dialog({
+							title: __("Select Finished Good"),
+							size: "small",
+							fields: [
+								{
+									fieldname: "finished_good",
+									fieldtype: "Autocomplete",
+									label: __("Finished Good"),
+									options: finished_goods,
+								}
+							],
+							primary_action_label: __("Select"),
+							primary_action: () => {
+								var subcontracting_bom = result.message[dialog.get_value("finished_good")];
+
+								if (subcontracting_bom) {
+									row.fg_item = subcontracting_bom.finished_good;
+									row.uom = subcontracting_bom.finished_good_uom;
+									refresh_field("items");
+								}
+
+								dialog.hide();
+							},
+						});
+
+						dialog.show();
+					}
+				}
 			}
 		}
-	}
+	},
+
+	fg_item: async function(frm, cdt, cdn) {
+		if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
+			var row = locals[cdt][cdn];
+
+			if (row.fg_item) {
+				var result = await frm.events.get_subcontracting_boms_for_finished_goods(row.fg_item)
+
+				if (result.message && Object.keys(result.message).length) {
+					frappe.model.set_value(cdt, cdn, "item_code", result.message.service_item);
+					frappe.model.set_value(cdt, cdn, "qty", flt(row.fg_item_qty) * flt(result.message.conversion_factor));
+					frappe.model.set_value(cdt, cdn, "uom", result.message.service_item_uom);
+				}
+			}
+		}
+	},
+
+	fg_item_qty: async function(frm, cdt, cdn) {
+		if (frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
+			var row = locals[cdt][cdn];
+
+			if (row.fg_item) {
+				var result = await frm.events.get_subcontracting_boms_for_finished_goods(row.fg_item)
+
+				if (result.message && row.item_code == result.message.service_item && row.uom == result.message.service_item_uom) {
+					frappe.model.set_value(cdt, cdn, "qty", flt(row.fg_item_qty) * flt(result.message.conversion_factor));
+				}
+			}
+		}
+	},
 });
 
 erpnext.buying.PurchaseOrderController = class PurchaseOrderController extends erpnext.buying.BuyingController {
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 3576cd4..465fe96 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -28,6 +28,9 @@
 from erpnext.stock.doctype.item.item import get_item_defaults, get_last_purchase_details
 from erpnext.stock.stock_balance import get_ordered_qty, update_bin_qty
 from erpnext.stock.utils import get_bin
+from erpnext.subcontracting.doctype.subcontracting_bom.subcontracting_bom import (
+	get_subcontracting_boms_for_finished_goods,
+)
 
 form_grid_templates = {"items": "templates/form_grid/item_grid.html"}
 
@@ -451,6 +454,25 @@
 		else:
 			self.db_set("per_received", 0, update_modified=False)
 
+	def set_service_items_for_finished_goods(self):
+		if not self.is_subcontracted or self.is_old_subcontracting_flow:
+			return
+
+		finished_goods_without_service_item = {
+			d.fg_item for d in self.items if (not d.item_code and d.fg_item)
+		}
+
+		if subcontracting_boms := get_subcontracting_boms_for_finished_goods(
+			finished_goods_without_service_item
+		):
+			for item in self.items:
+				if not item.item_code and item.fg_item in subcontracting_boms:
+					subcontracting_bom = subcontracting_boms[item.fg_item]
+
+					item.item_code = subcontracting_bom.service_item
+					item.qty = flt(item.fg_item_qty) * flt(subcontracting_bom.conversion_factor)
+					item.uom = subcontracting_bom.service_item_uom
+
 	def can_update_items(self) -> bool:
 		result = True
 
diff --git a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
index c645b04..414f086 100644
--- a/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
+++ b/erpnext/buying/doctype/purchase_order_item/purchase_order_item.json
@@ -7,13 +7,13 @@
  "editable_grid": 1,
  "engine": "InnoDB",
  "field_order": [
+  "fg_item",
+  "fg_item_qty",
   "item_code",
   "supplier_part_no",
   "item_name",
   "brand",
   "product_bundle",
-  "fg_item",
-  "fg_item_qty",
   "column_break_4",
   "schedule_date",
   "expected_delivery_date",
@@ -862,7 +862,7 @@
    "depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
    "fieldname": "fg_item",
    "fieldtype": "Link",
-   "label": "Finished Good Item",
+   "label": "Finished Good",
    "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
    "options": "Item"
   },
@@ -871,7 +871,7 @@
    "depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow",
    "fieldname": "fg_item_qty",
    "fieldtype": "Float",
-   "label": "Finished Good Item Qty",
+   "label": "Finished Good Qty",
    "mandatory_depends_on": "eval:parent.is_subcontracted && !parent.is_old_subcontracting_flow"
   },
   {
@@ -902,7 +902,7 @@
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2022-11-29 16:47:41.364387",
+ "modified": "2023-08-17 10:17:40.893393",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Order Item",
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index bfaf6bb..fd2be2c 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -1023,6 +1023,44 @@
 				)
 			)
 
+	def gain_loss_journal_already_booked(
+		self,
+		gain_loss_account,
+		exc_gain_loss,
+		ref2_dt,
+		ref2_dn,
+		ref2_detail_no,
+	) -> bool:
+		"""
+		Check if gain/loss is booked
+		"""
+		if res := frappe.db.get_all(
+			"Journal Entry Account",
+			filters={
+				"docstatus": 1,
+				"account": gain_loss_account,
+				"reference_type": ref2_dt,  # this will be Journal Entry
+				"reference_name": ref2_dn,
+				"reference_detail_no": ref2_detail_no,
+			},
+			pluck="parent",
+		):
+			# deduplicate
+			res = list({x for x in res})
+			if exc_vouchers := frappe.db.get_all(
+				"Journal Entry",
+				filters={"name": ["in", res], "voucher_type": "Exchange Gain Or Loss"},
+				fields=["voucher_type", "total_debit", "total_credit"],
+			):
+				booked_voucher = exc_vouchers[0]
+				if (
+					booked_voucher.total_debit == exc_gain_loss
+					and booked_voucher.total_credit == exc_gain_loss
+					and booked_voucher.voucher_type == "Exchange Gain Or Loss"
+				):
+					return True
+		return False
+
 	def make_exchange_gain_loss_journal(self, args: dict = None) -> None:
 		"""
 		Make Exchange Gain/Loss journal for Invoices and Payments
@@ -1051,27 +1089,35 @@
 
 							reverse_dr_or_cr = "debit" if dr_or_cr == "credit" else "credit"
 
-							je = create_gain_loss_journal(
-								self.company,
-								arg.get("party_type"),
-								arg.get("party"),
-								party_account,
+							if not self.gain_loss_journal_already_booked(
 								gain_loss_account,
 								difference_amount,
-								dr_or_cr,
-								reverse_dr_or_cr,
-								arg.get("against_voucher_type"),
-								arg.get("against_voucher"),
-								arg.get("idx"),
 								self.doctype,
 								self.name,
-								arg.get("idx"),
-							)
-							frappe.msgprint(
-								_("Exchange Gain/Loss amount has been booked through {0}").format(
-									get_link_to_form("Journal Entry", je)
+								arg.get("referenced_row"),
+							):
+								je = create_gain_loss_journal(
+									self.company,
+									arg.get("party_type"),
+									arg.get("party"),
+									party_account,
+									gain_loss_account,
+									difference_amount,
+									dr_or_cr,
+									reverse_dr_or_cr,
+									arg.get("against_voucher_type"),
+									arg.get("against_voucher"),
+									arg.get("idx"),
+									self.doctype,
+									self.name,
+									arg.get("referenced_row"),
+									arg.get("cost_center"),
 								)
-							)
+								frappe.msgprint(
+									_("Exchange Gain/Loss amount has been booked through {0}").format(
+										get_link_to_form("Journal Entry", je)
+									)
+								)
 
 			if self.get("doctype") == "Payment Entry":
 				# For Payment Entry, exchange_gain_loss field in the `references` table is the trigger for journal creation
@@ -1144,6 +1190,7 @@
 							self.doctype,
 							self.name,
 							d.idx,
+							self.cost_center,
 						)
 						frappe.msgprint(
 							_("Exchange Gain/Loss amount has been booked through {0}").format(
@@ -3096,7 +3143,9 @@
 
 		if has_reserved_stock(parent.doctype, parent.name):
 			cancel_stock_reservation_entries(parent.doctype, parent.name)
-			parent.create_stock_reservation_entries()
+
+			if parent.per_picked == 0:
+				parent.create_stock_reservation_entries()
 
 
 @erpnext.allow_regional
diff --git a/erpnext/controllers/tests/test_accounts_controller.py b/erpnext/controllers/tests/test_accounts_controller.py
index 0f8e133..391258f 100644
--- a/erpnext/controllers/tests/test_accounts_controller.py
+++ b/erpnext/controllers/tests/test_accounts_controller.py
@@ -55,6 +55,7 @@
 	10 series - Sales Invoice against Payment Entries
 	20 series - Sales Invoice against Journals
 	30 series - Sales Invoice against Credit Notes
+	40 series - Company default Cost center is unset
 	"""
 
 	def setUp(self):
@@ -941,6 +942,60 @@
 		self.assertEqual(exc_je_for_si, [])
 		self.assertEqual(exc_je_for_je, [])
 
+	def test_24_journal_against_multiple_invoices(self):
+		si1 = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
+		si2 = self.create_sales_invoice(qty=1, conversion_rate=80, rate=1)
+
+		# Payment
+		je = self.create_journal_entry(
+			acc1=self.debit_usd,
+			acc1_exc_rate=75,
+			acc2=self.cash,
+			acc1_amount=-2,
+			acc2_amount=-150,
+			acc2_exc_rate=1,
+		)
+		je.accounts[0].party_type = "Customer"
+		je.accounts[0].party = self.customer
+		je = je.save().submit()
+
+		pr = self.create_payment_reconciliation()
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.invoices), 2)
+		self.assertEqual(len(pr.payments), 1)
+		invoices = [x.as_dict() for x in pr.invoices]
+		payments = [x.as_dict() for x in pr.payments]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+		self.assertEqual(len(pr.invoices), 0)
+		self.assertEqual(len(pr.payments), 0)
+
+		si1.reload()
+		si2.reload()
+
+		self.assertEqual(si1.outstanding_amount, 0)
+		self.assertEqual(si2.outstanding_amount, 0)
+		self.assert_ledger_outstanding(si1.doctype, si1.name, 0.0, 0.0)
+		self.assert_ledger_outstanding(si2.doctype, si2.name, 0.0, 0.0)
+
+		# Exchange Gain/Loss Journal should've been created
+		# remove payment JE from list
+		exc_je_for_si1 = [x for x in self.get_journals_for(si1.doctype, si1.name) if x.parent != je.name]
+		exc_je_for_si2 = [x for x in self.get_journals_for(si2.doctype, si2.name) if x.parent != je.name]
+		exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
+		self.assertEqual(len(exc_je_for_si1), 1)
+		self.assertEqual(len(exc_je_for_si2), 1)
+		self.assertEqual(len(exc_je_for_je), 2)
+
+		si1.cancel()
+		# Gain/Loss JE of si1 should've been cancelled
+		exc_je_for_si1 = [x for x in self.get_journals_for(si1.doctype, si1.name) if x.parent != je.name]
+		exc_je_for_si2 = [x for x in self.get_journals_for(si2.doctype, si2.name) if x.parent != je.name]
+		exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
+		self.assertEqual(len(exc_je_for_si1), 0)
+		self.assertEqual(len(exc_je_for_si2), 1)
+		self.assertEqual(len(exc_je_for_je), 1)
+
 	def test_30_cr_note_against_sales_invoice(self):
 		"""
 		Reconciling Cr Note against Sales Invoice, both having different exchange rates
@@ -997,3 +1052,139 @@
 		si.reload()
 		self.assertEqual(si.outstanding_amount, 1)
 		self.assert_ledger_outstanding(si.doctype, si.name, 80.0, 1.0)
+
+	def test_40_cost_center_from_payment_entry(self):
+		"""
+		Gain/Loss JE should inherit cost center from payment if company default is unset
+		"""
+		# remove default cost center
+		cc = frappe.db.get_value("Company", self.company, "cost_center")
+		frappe.db.set_value("Company", self.company, "cost_center", None)
+
+		rate_in_account_currency = 1
+		si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
+		si.cost_center = None
+		si.save().submit()
+
+		pe = get_payment_entry(si.doctype, si.name)
+		pe.source_exchange_rate = 75
+		pe.received_amount = 75
+		pe.cost_center = self.cost_center
+		pe = pe.save().submit()
+
+		# Exchange Gain/Loss Journal should've been created.
+		exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+		exc_je_for_pe = self.get_journals_for(pe.doctype, pe.name)
+		self.assertNotEqual(exc_je_for_si, [])
+		self.assertEqual(len(exc_je_for_si), 1)
+		self.assertEqual(len(exc_je_for_pe), 1)
+		self.assertEqual(exc_je_for_si[0], exc_je_for_pe[0])
+
+		self.assertEqual(
+			[self.cost_center, self.cost_center],
+			frappe.db.get_all(
+				"Journal Entry Account", filters={"parent": exc_je_for_si[0].parent}, pluck="cost_center"
+			),
+		)
+		frappe.db.set_value("Company", self.company, "cost_center", cc)
+
+	def test_41_cost_center_from_journal_entry(self):
+		"""
+		Gain/Loss JE should inherit cost center from payment if company default is unset
+		"""
+		# remove default cost center
+		cc = frappe.db.get_value("Company", self.company, "cost_center")
+		frappe.db.set_value("Company", self.company, "cost_center", None)
+
+		rate_in_account_currency = 1
+		si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
+		si.cost_center = None
+		si.save().submit()
+
+		je = self.create_journal_entry(
+			acc1=self.debit_usd,
+			acc1_exc_rate=75,
+			acc2=self.cash,
+			acc1_amount=-1,
+			acc2_amount=-75,
+			acc2_exc_rate=1,
+		)
+		je.accounts[0].party_type = "Customer"
+		je.accounts[0].party = self.customer
+		je.accounts[0].cost_center = self.cost_center
+		je = je.save().submit()
+
+		# Reconcile
+		pr = self.create_payment_reconciliation()
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.invoices), 1)
+		self.assertEqual(len(pr.payments), 1)
+		invoices = [x.as_dict() for x in pr.invoices]
+		payments = [x.as_dict() for x in pr.payments]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+		self.assertEqual(len(pr.invoices), 0)
+		self.assertEqual(len(pr.payments), 0)
+
+		# Exchange Gain/Loss Journal should've been created.
+		exc_je_for_si = [x for x in self.get_journals_for(si.doctype, si.name) if x.parent != je.name]
+		exc_je_for_je = [x for x in self.get_journals_for(je.doctype, je.name) if x.parent != je.name]
+		self.assertNotEqual(exc_je_for_si, [])
+		self.assertEqual(len(exc_je_for_si), 1)
+		self.assertEqual(len(exc_je_for_je), 1)
+		self.assertEqual(exc_je_for_si[0], exc_je_for_je[0])
+
+		self.assertEqual(
+			[self.cost_center, self.cost_center],
+			frappe.db.get_all(
+				"Journal Entry Account", filters={"parent": exc_je_for_si[0].parent}, pluck="cost_center"
+			),
+		)
+		frappe.db.set_value("Company", self.company, "cost_center", cc)
+
+	def test_42_cost_center_from_cr_note(self):
+		"""
+		Gain/Loss JE should inherit cost center from payment if company default is unset
+		"""
+		# remove default cost center
+		cc = frappe.db.get_value("Company", self.company, "cost_center")
+		frappe.db.set_value("Company", self.company, "cost_center", None)
+
+		rate_in_account_currency = 1
+		si = self.create_sales_invoice(qty=1, rate=rate_in_account_currency, do_not_submit=True)
+		si.cost_center = None
+		si.save().submit()
+
+		cr_note = self.create_sales_invoice(qty=-1, conversion_rate=75, rate=1, do_not_save=True)
+		cr_note.cost_center = self.cost_center
+		cr_note.is_return = 1
+		cr_note.save().submit()
+
+		# Reconcile
+		pr = self.create_payment_reconciliation()
+		pr.get_unreconciled_entries()
+		self.assertEqual(len(pr.invoices), 1)
+		self.assertEqual(len(pr.payments), 1)
+		invoices = [x.as_dict() for x in pr.invoices]
+		payments = [x.as_dict() for x in pr.payments]
+		pr.allocate_entries(frappe._dict({"invoices": invoices, "payments": payments}))
+		pr.reconcile()
+		self.assertEqual(len(pr.invoices), 0)
+		self.assertEqual(len(pr.payments), 0)
+
+		# Exchange Gain/Loss Journal should've been created.
+		exc_je_for_si = self.get_journals_for(si.doctype, si.name)
+		exc_je_for_cr_note = self.get_journals_for(cr_note.doctype, cr_note.name)
+		self.assertNotEqual(exc_je_for_si, [])
+		self.assertEqual(len(exc_je_for_si), 2)
+		self.assertEqual(len(exc_je_for_cr_note), 2)
+		self.assertEqual(exc_je_for_si, exc_je_for_cr_note)
+
+		for x in exc_je_for_si + exc_je_for_cr_note:
+			with self.subTest(x=x):
+				self.assertEqual(
+					[self.cost_center, self.cost_center],
+					frappe.db.get_all("Journal Entry Account", filters={"parent": x.parent}, pluck="cost_center"),
+				)
+
+		frappe.db.set_value("Company", self.company, "cost_center", cc)
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 34e9423..b7a2489 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -673,6 +673,7 @@
 
 				po.append("items", po_data)
 
+			po.set_service_items_for_finished_goods()
 			po.set_missing_values()
 			po.flags.ignore_mandatory = True
 			po.flags.ignore_validate = True
diff --git a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
index 2871a29..dccb903 100644
--- a/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/test_production_plan.py
@@ -412,11 +412,15 @@
 
 	def test_production_plan_for_subcontracting_po(self):
 		from erpnext.manufacturing.doctype.bom.test_bom import create_nested_bom
+		from erpnext.subcontracting.doctype.subcontracting_bom.test_subcontracting_bom import (
+			create_subcontracting_bom,
+		)
 
-		bom_tree_1 = {"Test Laptop 1": {"Test Motherboard 1": {"Test Motherboard Wires 1": {}}}}
+		fg_item = "Test Motherboard 1"
+		bom_tree_1 = {"Test Laptop 1": {fg_item: {"Test Motherboard Wires 1": {}}}}
 		create_nested_bom(bom_tree_1, prefix="")
 
-		item_doc = frappe.get_doc("Item", "Test Motherboard 1")
+		item_doc = frappe.get_doc("Item", fg_item)
 		company = "_Test Company"
 
 		item_doc.is_sub_contracted_item = 1
@@ -429,6 +433,12 @@
 
 		item_doc.save()
 
+		service_item = make_item(properties={"is_stock_item": 0}).name
+		create_subcontracting_bom(
+			finished_good=fg_item,
+			service_item=service_item,
+		)
+
 		plan = create_production_plan(
 			item_code="Test Laptop 1", planned_qty=10, use_multi_level_bom=1, do_not_submit=True
 		)
@@ -445,7 +455,8 @@
 		self.assertEqual(po_doc.items[0].qty, 10.0)
 		self.assertEqual(po_doc.items[0].fg_item_qty, 10.0)
 		self.assertEqual(po_doc.items[0].fg_item_qty, 10.0)
-		self.assertEqual(po_doc.items[0].fg_item, "Test Motherboard 1")
+		self.assertEqual(po_doc.items[0].fg_item, fg_item)
+		self.assertEqual(po_doc.items[0].item_code, service_item)
 
 	def test_production_plan_combine_subassembly(self):
 		"""
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index a25c7c2..b3d6d3e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -339,5 +339,6 @@
 erpnext.patches.v15_0.remove_exotel_integration
 erpnext.patches.v14_0.single_to_multi_dunning
 execute:frappe.db.set_single_value('Selling Settings', 'allow_negative_rates_for_items', 0)
+erpnext.patches.v15_0.correct_asset_value_if_je_with_workflow
 # below migration patch should always run last
 erpnext.patches.v14_0.migrate_gl_to_payment_ledger
diff --git a/erpnext/patches/v15_0/correct_asset_value_if_je_with_workflow.py b/erpnext/patches/v15_0/correct_asset_value_if_je_with_workflow.py
new file mode 100644
index 0000000..aededa2
--- /dev/null
+++ b/erpnext/patches/v15_0/correct_asset_value_if_je_with_workflow.py
@@ -0,0 +1,119 @@
+import frappe
+from frappe.model.workflow import get_workflow_name
+from frappe.query_builder.functions import IfNull, Sum
+
+
+def execute():
+	active_je_workflow = get_workflow_name("Journal Entry")
+	if not active_je_workflow:
+		return
+
+	correct_value_for_assets_with_manual_depr_entries()
+
+	finance_books = frappe.db.get_all("Finance Book", pluck="name")
+
+	if finance_books:
+		for fb_name in finance_books:
+			correct_value_for_assets_with_auto_depr(fb_name)
+
+	correct_value_for_assets_with_auto_depr()
+
+
+def correct_value_for_assets_with_manual_depr_entries():
+	asset = frappe.qb.DocType("Asset")
+	gle = frappe.qb.DocType("GL Entry")
+	aca = frappe.qb.DocType("Asset Category Account")
+	company = frappe.qb.DocType("Company")
+
+	asset_details_and_depr_amount_map = (
+		frappe.qb.from_(gle)
+		.join(asset)
+		.on(gle.against_voucher == asset.name)
+		.join(aca)
+		.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
+		.join(company)
+		.on(company.name == asset.company)
+		.select(
+			asset.name.as_("asset_name"),
+			asset.gross_purchase_amount.as_("gross_purchase_amount"),
+			asset.opening_accumulated_depreciation.as_("opening_accumulated_depreciation"),
+			Sum(gle.debit).as_("depr_amount"),
+		)
+		.where(
+			gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
+		)
+		.where(gle.debit != 0)
+		.where(gle.is_cancelled == 0)
+		.where(asset.docstatus == 1)
+		.where(asset.calculate_depreciation == 0)
+		.groupby(asset.name)
+	)
+
+	frappe.qb.update(asset).join(asset_details_and_depr_amount_map).on(
+		asset_details_and_depr_amount_map.asset_name == asset.name
+	).set(
+		asset.value_after_depreciation,
+		asset_details_and_depr_amount_map.gross_purchase_amount
+		- asset_details_and_depr_amount_map.opening_accumulated_depreciation
+		- asset_details_and_depr_amount_map.depr_amount,
+	).run()
+
+
+def correct_value_for_assets_with_auto_depr(fb_name=None):
+	asset = frappe.qb.DocType("Asset")
+	gle = frappe.qb.DocType("GL Entry")
+	aca = frappe.qb.DocType("Asset Category Account")
+	company = frappe.qb.DocType("Company")
+	afb = frappe.qb.DocType("Asset Finance Book")
+
+	asset_details_and_depr_amount_map = (
+		frappe.qb.from_(gle)
+		.join(asset)
+		.on(gle.against_voucher == asset.name)
+		.join(aca)
+		.on((aca.parent == asset.asset_category) & (aca.company_name == asset.company))
+		.join(company)
+		.on(company.name == asset.company)
+		.select(
+			asset.name.as_("asset_name"),
+			asset.gross_purchase_amount.as_("gross_purchase_amount"),
+			asset.opening_accumulated_depreciation.as_("opening_accumulated_depreciation"),
+			Sum(gle.debit).as_("depr_amount"),
+		)
+		.where(
+			gle.account == IfNull(aca.depreciation_expense_account, company.depreciation_expense_account)
+		)
+		.where(gle.debit != 0)
+		.where(gle.is_cancelled == 0)
+		.where(asset.docstatus == 1)
+		.where(asset.calculate_depreciation == 1)
+		.groupby(asset.name)
+	)
+
+	if fb_name:
+		asset_details_and_depr_amount_map = asset_details_and_depr_amount_map.where(
+			gle.finance_book == fb_name
+		)
+	else:
+		asset_details_and_depr_amount_map = asset_details_and_depr_amount_map.where(
+			(gle.finance_book.isin([""])) | (gle.finance_book.isnull())
+		)
+
+	query = (
+		frappe.qb.update(afb)
+		.join(asset_details_and_depr_amount_map)
+		.on(asset_details_and_depr_amount_map.asset_name == afb.parent)
+		.set(
+			afb.value_after_depreciation,
+			asset_details_and_depr_amount_map.gross_purchase_amount
+			- asset_details_and_depr_amount_map.opening_accumulated_depreciation
+			- asset_details_and_depr_amount_map.depr_amount,
+		)
+	)
+
+	if fb_name:
+		query = query.where(afb.finance_book == fb_name)
+	else:
+		query = query.where((afb.finance_book.isin([""])) | (afb.finance_book.isnull()))
+
+	query.run()
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index a3c10c6..89750f8 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -571,6 +571,7 @@
 	const cannot_add_row = (typeof opts.cannot_add_row === 'undefined') ? true : opts.cannot_add_row;
 	const child_docname = (typeof opts.cannot_add_row === 'undefined') ? "items" : opts.child_docname;
 	const child_meta = frappe.get_meta(`${frm.doc.doctype} Item`);
+	const has_reserved_stock = opts.has_reserved_stock ? true : false;
 	const get_precision = (fieldname) => child_meta.fields.find(f => f.fieldname == fieldname).precision;
 
 	this.data = frm.doc[opts.child_docname].map((d) => {
@@ -734,6 +735,17 @@
 			},
 		],
 		primary_action: function() {
+			if (frm.doctype == "Sales Order" && has_reserved_stock) {
+				this.hide();
+				frappe.confirm(
+					__('The reserved stock will be released when you update items. Are you certain you wish to proceed?'),
+					() => this.update_items(),
+				)
+			} else {
+				this.update_items();
+			}
+		},
+		update_items: function() {
 			const trans_items = this.get_values()["trans_items"].filter((item) => !!item.item_code);
 			frappe.call({
 				method: 'erpnext.controllers.accounts_controller.update_child_qty_rate',
@@ -823,6 +835,8 @@
 				"target_doc": cur_frm.doc,
 				"args": opts.args
 			},
+			freeze: true,
+			freeze_message: __("Mapping {0} ...", [opts.source_doctype]),
 			callback: function(r) {
 				if(!r.exc) {
 					var doc = frappe.model.sync(r.message);
diff --git a/erpnext/selling/doctype/sales_order/sales_order.js b/erpnext/selling/doctype/sales_order/sales_order.js
index 27163db..ba8bc33 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.js
+++ b/erpnext/selling/doctype/sales_order/sales_order.js
@@ -59,19 +59,27 @@
 						child_docname: "items",
 						child_doctype: "Sales Order Detail",
 						cannot_add_row: false,
+						has_reserved_stock: frm.doc.__onload && frm.doc.__onload.has_reserved_stock
 					})
 				});
 
-				// Stock Reservation > Reserve button will be only visible if the SO has unreserved stock.
-				if (frm.doc.__onload && frm.doc.__onload.has_unreserved_stock) {
+				// Stock Reservation > Reserve button should only be visible if the SO has unreserved stock and no Pick List is created against the SO.
+				if (frm.doc.__onload && frm.doc.__onload.has_unreserved_stock && flt(frm.doc.per_picked) === 0) {
 					frm.add_custom_button(__('Reserve'), () => frm.events.create_stock_reservation_entries(frm), __('Stock Reservation'));
 				}
 			}
 
-			// Stock Reservation > Unreserve button will be only visible if the SO has reserved stock.
+			// Stock Reservation > Unreserve button will be only visible if the SO has un-delivered reserved stock.
 			if (frm.doc.__onload && frm.doc.__onload.has_reserved_stock) {
 				frm.add_custom_button(__('Unreserve'), () => frm.events.cancel_stock_reservation_entries(frm), __('Stock Reservation'));
 			}
+
+			frm.doc.items.forEach(item => {
+				if (flt(item.stock_reserved_qty) > 0) {
+					frm.add_custom_button(__('Reserved Stock'), () => frm.events.show_reserved_stock(frm), __('Stock Reservation'));
+					return;
+				}
+			});
 		}
 
 		if (frm.doc.docstatus === 0) {
@@ -82,7 +90,7 @@
 			if (frm.is_new()) {
 				frappe.db.get_single_value("Stock Settings", "enable_stock_reservation").then((value) => {
 					if (value) {
-						frappe.db.get_single_value("Stock Settings", "reserve_stock_on_sales_order_submission").then((value) => {
+						frappe.db.get_single_value("Stock Settings", "auto_reserve_stock_for_sales_order").then((value) => {
 							// If `Reserve Stock on Sales Order Submission` is enabled in Stock Settings, set Reserve Stock to 1 else 0.
 							frm.set_value("reserve_stock", value ? 1 : 0);
 						})
@@ -94,6 +102,11 @@
 				})
 			}
 		}
+
+		// Hide `Reserve Stock` field description in submitted or cancelled Sales Order.
+		if (frm.doc.docstatus > 0) {
+			frm.set_df_property("reserve_stock", "description", null);
+		}
 	},
 
 	get_items_from_internal_purchase_order(frm) {
@@ -171,76 +184,115 @@
 	},
 
 	create_stock_reservation_entries(frm) {
-		let items_data = [];
-
-		const dialog = frappe.prompt({fieldname: 'items', fieldtype: 'Table', label: __('Items to Reserve'),
+		const dialog = new frappe.ui.Dialog({
+			title: __("Stock Reservation"),
+			size: "large",
 			fields: [
 				{
-					fieldtype: 'Data',
-					fieldname: 'name',
-					label: __('Name'),
-					reqd: 1,
-					read_only: 1,
-				},
-				{
-					fieldtype: 'Link',
-					fieldname: 'item_code',
-					label: __('Item Code'),
-					options: 'Item',
-					reqd: 1,
-					read_only: 1,
-					in_list_view: 1,
-				},
-				{
-					fieldtype: 'Link',
-					fieldname: 'warehouse',
-					label: __('Warehouse'),
-					options: 'Warehouse',
-					reqd: 1,
-					in_list_view: 1,
-					get_query: function () {
+					fieldname: "set_warehouse",
+					fieldtype: "Link",
+					label: __("Set Warehouse"),
+					options: "Warehouse",
+					default: frm.doc.set_warehouse,
+					get_query: () => {
 						return {
 							filters: [
 								["Warehouse", "is_group", "!=", 1]
 							]
 						};
 					},
-				},
-				{
-					fieldtype: 'Float',
-					fieldname: 'qty_to_reserve',
-					label: __('Qty'),
-					reqd: 1,
-					in_list_view: 1
-				}
-			],
-			data: items_data,
-			in_place_edit: true,
-			get_data: function() {
-				return items_data;
-			}
-		}, function(data) {
-			if (data.items.length > 0) {
-				frappe.call({
-					doc: frm.doc,
-					method: 'create_stock_reservation_entries',
-					args: {
-						items_details: data.items,
-						notify: true
+					onchange: () => {
+						if (dialog.get_value("set_warehouse")) {
+							dialog.fields_dict.items.df.data.forEach((row) => {
+								row.warehouse = dialog.get_value("set_warehouse");
+							});
+							dialog.fields_dict.items.grid.refresh();
+						}
 					},
-					freeze: true,
-					freeze_message: __('Reserving Stock...'),
-					callback: (r) => {
-						frm.doc.__onload.has_unreserved_stock = false;
-						frm.reload_doc();
-					}
-				});
-			}
-		}, __("Stock Reservation"), __("Reserve Stock"));
+				},
+				{fieldtype: "Column Break"},
+				{fieldtype: "Section Break"},
+				{
+					fieldname: "items",
+					fieldtype: "Table",
+					label: __("Items to Reserve"),
+					allow_bulk_edit: false,
+					cannot_add_rows: true,
+					cannot_delete_rows: true,
+					data: [],
+					fields: [
+						{
+							fieldname: "name",
+							fieldtype: "Data",
+							label: __("Name"),
+							reqd: 1,
+							read_only: 1,
+						},
+						{
+							fieldname: "item_code",
+							fieldtype: "Link",
+							label: __("Item Code"),
+							options: "Item",
+							reqd: 1,
+							read_only: 1,
+							in_list_view: 1,
+						},
+						{
+							fieldname: "warehouse",
+							fieldtype: "Link",
+							label: __("Warehouse"),
+							options: "Warehouse",
+							reqd: 1,
+							in_list_view: 1,
+							get_query: () => {
+								return {
+									filters: [
+										["Warehouse", "is_group", "!=", 1]
+									]
+								};
+							},
+						},
+						{
+							fieldname: "qty_to_reserve",
+							fieldtype: "Float",
+							label: __("Qty"),
+							reqd: 1,
+							in_list_view: 1
+						}
+					],
+				},
+			],
+			primary_action_label: __("Reserve Stock"),
+			primary_action: () => {
+				var data = {items: dialog.fields_dict.items.grid.get_selected_children()};
+
+				if (data.items && data.items.length > 0) {
+					frappe.call({
+						doc: frm.doc,
+						method: "create_stock_reservation_entries",
+						args: {
+							items_details: data.items,
+							notify: true
+						},
+						freeze: true,
+						freeze_message: __("Reserving Stock..."),
+						callback: (r) => {
+							frm.doc.__onload.has_unreserved_stock = false;
+							frm.reload_doc();
+						}
+					});
+				}
+				else {
+					frappe.msgprint(__("Please select items to reserve."));
+				}
+
+				dialog.hide();
+			},
+		});
 
 		frm.doc.items.forEach(item => {
 			if (item.reserve_stock) {
-				let unreserved_qty = (flt(item.stock_qty) - (flt(item.delivered_qty) * flt(item.conversion_factor)) - flt(item.stock_reserved_qty))
+				let unreserved_qty = (flt(item.stock_qty) - (item.stock_reserved_qty ? flt(item.stock_reserved_qty) : (flt(item.delivered_qty) * flt(item.conversion_factor))))
 
 				if (unreserved_qty > 0) {
 					dialog.fields_dict.items.df.data.push({
@@ -254,22 +306,127 @@
 		});
 
 		dialog.fields_dict.items.grid.refresh();
+		dialog.show();
 	},
 
 	cancel_stock_reservation_entries(frm) {
+		const dialog = new frappe.ui.Dialog({
+			title: __("Stock Unreservation"),
+			size: "large",
+			fields: [
+				{
+					fieldname: "sr_entries",
+					fieldtype: "Table",
+					label: __("Reserved Stock"),
+					allow_bulk_edit: false,
+					cannot_add_rows: true,
+					cannot_delete_rows: true,
+					in_place_edit: true,
+					data: [],
+					fields: [
+						{
+							fieldname: "name",
+							fieldtype: "Link",
+							label: __("SRE"),
+							options: "Stock Reservation Entry",
+							reqd: 1,
+							read_only: 1,
+							in_list_view: 1,
+						},
+						{
+							fieldname: "item_code",
+							fieldtype: "Link",
+							label: __("Item Code"),
+							options: "Item",
+							reqd: 1,
+							read_only: 1,
+							in_list_view: 1,
+						},
+						{
+							fieldname: "warehouse",
+							fieldtype: "Link",
+							label: __("Warehouse"),
+							options: "Warehouse",
+							reqd: 1,
+							read_only: 1,
+							in_list_view: 1,
+						},
+						{
+							fieldname: "qty",
+							fieldtype: "Float",
+							label: __("Qty"),
+							reqd: 1,
+							read_only: 1,
+							in_list_view: 1
+						}
+					]
+				}
+			],
+			primary_action_label: __("Unreserve Stock"),
+			primary_action: () => {
+				var data = {sr_entries: dialog.fields_dict.sr_entries.grid.get_selected_children()};
+
+				if (data.sr_entries && data.sr_entries.length > 0) {
+					frappe.call({
+						doc: frm.doc,
+						method: "cancel_stock_reservation_entries",
+						args: {
+							sre_list: data.sr_entries,
+						},
+						freeze: true,
+						freeze_message: __('Unreserving Stock...'),
+						callback: (r) => {
+							frm.doc.__onload.has_reserved_stock = false;
+							frm.reload_doc();
+						}
+					});
+				}
+				else {
+					frappe.msgprint(__("Please select items to unreserve."));
+				}
+
+				dialog.hide();
+			},
+		});
+
 		frappe.call({
-			method: 'erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry.cancel_stock_reservation_entries',
+			method: 'erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry.get_stock_reservation_entries_for_voucher',
 			args: {
 				voucher_type: frm.doctype,
-				voucher_no: frm.docname
+				voucher_no: frm.docname,
 			},
-			freeze: true,
-			freeze_message: __('Unreserving Stock...'),
 			callback: (r) => {
-				frm.doc.__onload.has_reserved_stock = false;
-				frm.reload_doc();
+				if (!r.exc && r.message) {
+					r.message.forEach(sre => {
+						if (flt(sre.reserved_qty) > flt(sre.delivered_qty)) {
+							dialog.fields_dict.sr_entries.df.data.push({
+								'name': sre.name,
+								'item_code': sre.item_code,
+								'warehouse': sre.warehouse,
+								'qty': (flt(sre.reserved_qty) - flt(sre.delivered_qty))
+							});
+						}
+					});
+				}
 			}
-		})
+		}).then(r => {
+			dialog.fields_dict.sr_entries.grid.refresh();
+			dialog.show();
+		});
+	},
+
+	show_reserved_stock(frm) {
+		// Get the latest modified date from the items table.
+		var to_date = moment(new Date(Math.max(...frm.doc.items.map(e => new Date(e.modified))))).format('YYYY-MM-DD');
+
+		frappe.route_options = {
+			company: frm.doc.company,
+			from_date: frm.doc.transaction_date,
+			to_date: to_date,
+			voucher_type: frm.doc.doctype,
+			voucher_no: frm.doc.name,
+		}
+		frappe.set_route("query-report", "Reserved Stock");
 	}
 });
 
@@ -335,8 +492,11 @@
 						}
 					}
 
-					if (flt(doc.per_picked, 2) < 100 && flt(doc.per_delivered, 2) < 100) {
-						this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
+					if (!doc.__onload || !doc.__onload.has_reserved_stock) {
+						// Don't show the `Reserve` button if the Sales Order has Picked Items.
+						if (flt(doc.per_picked, 2) < 100 && flt(doc.per_delivered, 2) < 100) {
+							this.frm.add_custom_button(__('Pick List'), () => this.create_pick_list(), __('Create'));
+						}
 					}
 
 					const order_is_a_sale = ["Sales", "Shopping Cart"].indexOf(doc.order_type) !== -1;
@@ -346,7 +506,7 @@
 
 					// delivery note
 					if(flt(doc.per_delivered, 2) < 100 && (order_is_a_sale || order_is_a_custom_sale) && allow_delivery) {
-						this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(), __('Create'));
+						this.frm.add_custom_button(__('Delivery Note'), () => this.make_delivery_note_based_on_delivery_date(true), __('Create'));
 						this.frm.add_custom_button(__('Work Order'), () => this.make_work_order(), __('Create'));
 					}
 
@@ -639,7 +799,7 @@
 		d.show();
 	}
 
-	make_delivery_note_based_on_delivery_date() {
+	make_delivery_note_based_on_delivery_date(for_reserved_stock=false) {
 		var me = this;
 
 		var delivery_dates = this.frm.doc.items.map(i => i.delivery_date);
@@ -681,22 +841,25 @@
 
 				if(!dates) return;
 
-				me.make_delivery_note(dates);
+				me.make_delivery_note(dates, for_reserved_stock);
 				dialog.hide();
 			});
 			dialog.show();
 		} else {
-			this.make_delivery_note();
+			this.make_delivery_note([], for_reserved_stock);
 		}
 	}
 
-	make_delivery_note(delivery_dates) {
+	make_delivery_note(delivery_dates, for_reserved_stock=false) {
 		frappe.model.open_mapped_doc({
 			method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
 			frm: this.frm,
 			args: {
-				delivery_dates
-			}
+				delivery_dates,
+				for_reserved_stock: for_reserved_stock
+			},
+			freeze: true,
+			freeze_message: __("Creating Delivery Note ...")
 		})
 	}
 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.json b/erpnext/selling/doctype/sales_order/sales_order.json
index f65969e..a74084d 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.json
+++ b/erpnext/selling/doctype/sales_order/sales_order.json
@@ -1027,7 +1027,6 @@
    "length": 240,
    "oldfieldname": "in_words_export",
    "oldfieldtype": "Data",
-   "print_hide": 1,
    "read_only": 1,
    "width": "200px"
   },
@@ -1635,6 +1634,7 @@
    "description": "If checked, Stock Reservation Entries will be created on <b>Submit</b>",
    "fieldname": "reserve_stock",
    "fieldtype": "Check",
+   "hidden": 1,
    "label": "Reserve Stock",
    "no_copy": 1,
    "print_hide": 1,
@@ -1645,7 +1645,7 @@
  "idx": 105,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-06-03 16:16:23.411247",
+ "modified": "2023-07-24 08:59:11.599875",
  "modified_by": "Administrator",
  "module": "Selling",
  "name": "Sales Order",
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index cc141ff..aae0fee 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -31,7 +31,6 @@
 from erpnext.setup.doctype.item_group.item_group import get_item_group_defaults
 from erpnext.stock.doctype.item.item import get_item_defaults
 from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-	cancel_stock_reservation_entries,
 	get_sre_reserved_qty_details_for_voucher,
 	has_reserved_stock,
 )
@@ -283,7 +282,7 @@
 		self.db_set("status", "Cancelled")
 
 		self.update_blanket_order()
-		cancel_stock_reservation_entries("Sales Order", self.name)
+		self.cancel_stock_reservation_entries()
 
 		unlink_inter_company_doc(self.doctype, self.name, self.inter_company_order_reference)
 		if self.coupon_code:
@@ -535,138 +534,26 @@
 		return False
 
 	@frappe.whitelist()
-	def create_stock_reservation_entries(self, items_details=None, notify=True):
+	def create_stock_reservation_entries(self, items_details=None, notify=True) -> None:
 		"""Creates Stock Reservation Entries for Sales Order Items."""
 
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-			get_available_qty_to_reserve,
-			validate_stock_reservation_settings,
+			create_stock_reservation_entries_for_so_items as create_stock_reservation_entries,
 		)
 
-		validate_stock_reservation_settings(self)
+		create_stock_reservation_entries(so=self, items_details=items_details, notify=notify)
 
-		allow_partial_reservation = frappe.db.get_single_value(
-			"Stock Settings", "allow_partial_reservation"
+	@frappe.whitelist()
+	def cancel_stock_reservation_entries(self, sre_list=None, notify=True) -> None:
+		"""Cancel Stock Reservation Entries for Sales Order Items."""
+
+		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+			cancel_stock_reservation_entries,
 		)
 
-		items = []
-		if items_details:
-			for item in items_details:
-				so_item = frappe.get_doc("Sales Order Item", item["name"])
-				so_item.reserve_stock = 1
-				so_item.warehouse = item["warehouse"]
-				so_item.qty_to_reserve = flt(item["qty_to_reserve"]) * flt(so_item.conversion_factor)
-				items.append(so_item)
-
-		sre_count = 0
-		reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", self.name)
-		for item in items or self.get("items"):
-			# Skip if `Reserved Stock` is not checked for the item.
-			if not item.get("reserve_stock"):
-				continue
-
-			# Skip if Non-Stock Item.
-			if not frappe.get_cached_value("Item", item.item_code, "is_stock_item"):
-				frappe.msgprint(
-					_("Row #{0}: Stock cannot be reserved for a non-stock Item {1}").format(
-						item.idx, frappe.bold(item.item_code)
-					),
-					title=_("Stock Reservation"),
-					indicator="yellow",
-				)
-				item.db_set("reserve_stock", 0)
-				continue
-
-			# Skip if Group Warehouse.
-			if frappe.get_cached_value("Warehouse", item.warehouse, "is_group"):
-				frappe.msgprint(
-					_("Row #{0}: Stock cannot be reserved in group warehouse {1}.").format(
-						item.idx, frappe.bold(item.warehouse)
-					),
-					title=_("Stock Reservation"),
-					indicator="yellow",
-				)
-				continue
-
-			unreserved_qty = get_unreserved_qty(item, reserved_qty_details)
-
-			# Stock is already reserved for the item, notify the user and skip the item.
-			if unreserved_qty <= 0:
-				frappe.msgprint(
-					_("Row #{0}: Stock is already reserved for the Item {1}.").format(
-						item.idx, frappe.bold(item.item_code)
-					),
-					title=_("Stock Reservation"),
-					indicator="yellow",
-				)
-				continue
-
-			available_qty_to_reserve = get_available_qty_to_reserve(item.item_code, item.warehouse)
-
-			# No stock available to reserve, notify the user and skip the item.
-			if available_qty_to_reserve <= 0:
-				frappe.msgprint(
-					_("Row #{0}: No available stock to reserve for the Item {1} in Warehouse {2}.").format(
-						item.idx, frappe.bold(item.item_code), frappe.bold(item.warehouse)
-					),
-					title=_("Stock Reservation"),
-					indicator="orange",
-				)
-				continue
-
-			# The quantity which can be reserved.
-			qty_to_be_reserved = min(unreserved_qty, available_qty_to_reserve)
-
-			if hasattr(item, "qty_to_reserve"):
-				if item.qty_to_reserve <= 0:
-					frappe.msgprint(
-						_("Row #{0}: Quantity to reserve for the Item {1} should be greater than 0.").format(
-							item.idx, frappe.bold(item.item_code)
-						),
-						title=_("Stock Reservation"),
-						indicator="orange",
-					)
-					continue
-				else:
-					qty_to_be_reserved = min(qty_to_be_reserved, item.qty_to_reserve)
-
-			# Partial Reservation
-			if qty_to_be_reserved < unreserved_qty:
-				if not item.get("qty_to_reserve") or qty_to_be_reserved < flt(item.get("qty_to_reserve")):
-					frappe.msgprint(
-						_("Row #{0}: Only {1} available to reserve for the Item {2}").format(
-							item.idx,
-							frappe.bold(str(qty_to_be_reserved / item.conversion_factor) + " " + item.uom),
-							frappe.bold(item.item_code),
-						),
-						title=_("Stock Reservation"),
-						indicator="orange",
-					)
-
-				# Skip the item if `Partial Reservation` is disabled in the Stock Settings.
-				if not allow_partial_reservation:
-					continue
-
-			# Create and Submit Stock Reservation Entry
-			sre = frappe.new_doc("Stock Reservation Entry")
-			sre.item_code = item.item_code
-			sre.warehouse = item.warehouse
-			sre.voucher_type = self.doctype
-			sre.voucher_no = self.name
-			sre.voucher_detail_no = item.name
-			sre.available_qty = available_qty_to_reserve
-			sre.voucher_qty = item.stock_qty
-			sre.reserved_qty = qty_to_be_reserved
-			sre.company = self.company
-			sre.stock_uom = item.stock_uom
-			sre.project = self.project
-			sre.save()
-			sre.submit()
-
-			sre_count += 1
-
-		if sre_count and notify:
-			frappe.msgprint(_("Stock Reservation Entries Created"), alert=True, indicator="green")
+		cancel_stock_reservation_entries(
+			voucher_type=self.doctype, voucher_no=self.name, sre_list=sre_list, notify=notify
+		)
 
 
 def get_unreserved_qty(item: object, reserved_qty_details: dict) -> float:
@@ -813,8 +700,31 @@
 
 
 @frappe.whitelist()
-def make_delivery_note(source_name, target_doc=None, skip_item_mapping=False):
+def make_delivery_note(source_name, target_doc=None, kwargs=None):
 	from erpnext.stock.doctype.packed_item.packed_item import make_packing_list
+	from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+		get_sre_details_for_voucher,
+		get_sre_reserved_qty_details_for_voucher,
+		get_ssb_bundle_for_voucher,
+	)
+
+	if not kwargs:
+		kwargs = {
+			"for_reserved_stock": frappe.flags.args and frappe.flags.args.for_reserved_stock,
+			"skip_item_mapping": frappe.flags.args and frappe.flags.args.skip_item_mapping,
+		}
+
+	kwargs = frappe._dict(kwargs)
+
+	sre_details = {}
+	if kwargs.for_reserved_stock:
+		sre_details = get_sre_reserved_qty_details_for_voucher("Sales Order", source_name)
+
+	mapper = {
+		"Sales Order": {"doctype": "Delivery Note", "validation": {"docstatus": ["=", 1]}},
+		"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
+		"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
+	}
 
 	def set_missing_values(source, target):
 		target.run_method("set_missing_values")
@@ -832,6 +742,18 @@
 
 		make_packing_list(target)
 
+	def condition(doc):
+		if doc.name in sre_details:
+			del sre_details[doc.name]
+			return False
+
+		# make_mapped_doc sets js `args` into `frappe.flags.args`
+		if frappe.flags.args and frappe.flags.args.delivery_dates:
+			if cstr(doc.delivery_date) not in frappe.flags.args.delivery_dates:
+				return False
+
+		return abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier != 1
+
 	def update_item(source, target, source_parent):
 		target.base_amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.base_rate)
 		target.amount = (flt(source.qty) - flt(source.delivered_qty)) * flt(source.rate)
@@ -847,21 +769,7 @@
 				or item_group.get("buying_cost_center")
 			)
 
-	mapper = {
-		"Sales Order": {"doctype": "Delivery Note", "validation": {"docstatus": ["=", 1]}},
-		"Sales Taxes and Charges": {"doctype": "Sales Taxes and Charges", "add_if_empty": True},
-		"Sales Team": {"doctype": "Sales Team", "add_if_empty": True},
-	}
-
-	if not skip_item_mapping:
-
-		def condition(doc):
-			# make_mapped_doc sets js `args` into `frappe.flags.args`
-			if frappe.flags.args and frappe.flags.args.delivery_dates:
-				if cstr(doc.delivery_date) not in frappe.flags.args.delivery_dates:
-					return False
-			return abs(doc.delivered_qty) < abs(doc.qty) and doc.delivered_by_supplier != 1
-
+	if not kwargs.skip_item_mapping:
 		mapper["Sales Order Item"] = {
 			"doctype": "Delivery Note Item",
 			"field_map": {
@@ -869,11 +777,56 @@
 				"name": "so_detail",
 				"parent": "against_sales_order",
 			},
-			"postprocess": update_item,
 			"condition": condition,
+			"postprocess": update_item,
 		}
 
-	target_doc = get_mapped_doc("Sales Order", source_name, mapper, target_doc, set_missing_values)
+	so = frappe.get_doc("Sales Order", source_name)
+	target_doc = get_mapped_doc("Sales Order", so.name, mapper, target_doc)
+
+	if not kwargs.skip_item_mapping and kwargs.for_reserved_stock:
+		sre_list = get_sre_details_for_voucher("Sales Order", source_name)
+
+		if sre_list:
+
+			def update_dn_item(source, target, source_parent):
+				update_item(source, target, so)
+
+			so_items = {d.name: d for d in so.items if d.stock_reserved_qty}
+
+			for sre in sre_list:
+				if not condition(so_items[sre.voucher_detail_no]):
+					continue
+
+				dn_item = get_mapped_doc(
+					"Sales Order Item",
+					sre.voucher_detail_no,
+					{
+						"Sales Order Item": {
+							"doctype": "Delivery Note Item",
+							"field_map": {
+								"rate": "rate",
+								"name": "so_detail",
+								"parent": "against_sales_order",
+							},
+							"postprocess": update_dn_item,
+						}
+					},
+				)
+
+				dn_item.qty = flt(sre.reserved_qty) * flt(dn_item.get("conversion_factor", 1))
+
+				if sre.reservation_based_on == "Serial and Batch" and (sre.has_serial_no or sre.has_batch_no):
+					dn_item.serial_and_batch_bundle = get_ssb_bundle_for_voucher(sre)
+
+				target_doc.append("items", dn_item)
+			else:
+				# Correct rows index.
+				for idx, item in enumerate(target_doc.items):
+					item.idx = idx + 1
+
+	# Should be called after mapping items.
+	set_missing_values(so, target_doc)
 	target_doc.set_onload("ignore_price_list", True)
 
 	return target_doc
@@ -1436,6 +1389,16 @@
 def create_pick_list(source_name, target_doc=None):
 	from erpnext.stock.doctype.packed_item.packed_item import is_product_bundle
 
+	def validate_sales_order():
+		so = frappe.get_doc("Sales Order", source_name)
+		for item in so.items:
+			if item.stock_reserved_qty > 0:
+				frappe.throw(
+					_(
+						"Cannot create a pick list for Sales Order {0} because it has reserved stock. Please unreserve the stock in order to create a pick list."
+					).format(frappe.bold(source_name))
+				)
+
 	def update_item_quantity(source, target, source_parent) -> None:
 		picked_qty = flt(source.picked_qty) / (flt(source.conversion_factor) or 1)
 		qty_to_be_picked = flt(source.qty) - max(picked_qty, flt(source.delivered_qty))
@@ -1459,6 +1422,9 @@
 			and not is_product_bundle(item.item_code)
 		)
 
+	# Don't allow a Pick List to be created against a Sales Order that has reserved stock.
+	validate_sales_order()
+
 	doc = get_mapped_doc(
 		"Sales Order",
 		source_name,
diff --git a/erpnext/selling/doctype/sales_order/test_sales_order.py b/erpnext/selling/doctype/sales_order/test_sales_order.py
index 954393f..ed270be 100644
--- a/erpnext/selling/doctype/sales_order/test_sales_order.py
+++ b/erpnext/selling/doctype/sales_order/test_sales_order.py
@@ -1789,147 +1789,6 @@
 		self.assertEqual(pe.references[1].reference_name, so.name)
 		self.assertEqual(pe.references[1].allocated_amount, 300)
 
-	@change_settings(
-		"Stock Settings",
-		{
-			"enable_stock_reservation": 1,
-			"auto_create_serial_and_batch_bundle_for_outward": 1,
-			"pick_serial_and_batch_based_on": "FIFO",
-		},
-	)
-	def test_stock_reservation_against_sales_order(self) -> None:
-		from random import randint, uniform
-
-		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-			cancel_stock_reservation_entries,
-			get_sre_reserved_qty_details_for_voucher,
-			get_stock_reservation_entries_for_voucher,
-			has_reserved_stock,
-		)
-		from erpnext.stock.doctype.stock_reservation_entry.test_stock_reservation_entry import (
-			create_items,
-			create_material_receipt,
-		)
-
-		items_details, warehouse = create_items(), "_Test Warehouse - _TC"
-		se = create_material_receipt(items_details, warehouse, qty=10)
-
-		item_list = []
-		for item_code, properties in items_details.items():
-			stock_uom = properties.stock_uom
-			item_list.append(
-				{
-					"item_code": item_code,
-					"warehouse": warehouse,
-					"qty": flt(uniform(11, 100), 0 if stock_uom == "Nos" else 3),
-					"uom": stock_uom,
-					"rate": randint(10, 200),
-				}
-			)
-
-		so = make_sales_order(
-			item_list=item_list,
-			warehouse="_Test Warehouse - _TC",
-		)
-
-		# Test - 1: Stock should not be reserved if the Available Qty to Reserve is less than the Ordered Qty and Partial Reservation is disabled in Stock Settings.
-		with change_settings("Stock Settings", {"allow_partial_reservation": 0}):
-			so.create_stock_reservation_entries()
-			self.assertFalse(has_reserved_stock("Sales Order", so.name))
-
-		# Test - 2: Stock should be Partially Reserved if the Partial Reservation is enabled in Stock Settings.
-		with change_settings("Stock Settings", {"allow_partial_reservation": 1}):
-			so.create_stock_reservation_entries()
-			so.load_from_db()
-			self.assertTrue(has_reserved_stock("Sales Order", so.name))
-
-			for item in so.items:
-				sre_details = get_stock_reservation_entries_for_voucher(
-					"Sales Order", so.name, item.name, fields=["reserved_qty", "status"]
-				)
-				self.assertEqual(item.stock_reserved_qty, sre_details[0].reserved_qty)
-				self.assertEqual(sre_details[0].status, "Partially Reserved")
-
-			se.cancel()
-
-			# Test - 3: Stock should be fully Reserved if the Available Qty to Reserve is greater than the Un-reserved Qty.
-			create_material_receipt(items_details, warehouse, qty=110)
-			so.create_stock_reservation_entries()
-			so.load_from_db()
-
-			reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", so.name)
-			for item in so.items:
-				reserved_qty = reserved_qty_details[item.name]
-				self.assertEqual(item.stock_reserved_qty, reserved_qty)
-				self.assertEqual(item.stock_qty, item.stock_reserved_qty)
-
-			# Test - 4: Stock should get unreserved on cancellation of Stock Reservation Entries.
-			cancel_stock_reservation_entries("Sales Order", so.name)
-			so.load_from_db()
-			self.assertFalse(has_reserved_stock("Sales Order", so.name))
-
-			for item in so.items:
-				self.assertEqual(item.stock_reserved_qty, 0)
-
-			# Test - 5: Re-reserve the stock.
-			so.create_stock_reservation_entries()
-			self.assertTrue(has_reserved_stock("Sales Order", so.name))
-
-			# Test - 6: Stock should get unreserved on cancellation of Sales Order.
-			so.cancel()
-			so.load_from_db()
-			self.assertFalse(has_reserved_stock("Sales Order", so.name))
-
-			for item in so.items:
-				self.assertEqual(item.stock_reserved_qty, 0)
-
-			# Create Sales Order and Reserve Stock.
-			so = make_sales_order(
-				item_list=item_list,
-				warehouse="_Test Warehouse - _TC",
-			)
-			so.create_stock_reservation_entries()
-
-			# Test - 7: Partial Delivery against Sales Order.
-			dn1 = make_delivery_note(so.name)
-
-			for item in dn1.items:
-				item.qty = flt(uniform(1, 10), 0 if item.stock_uom == "Nos" else 3)
-
-			dn1.save()
-			dn1.submit()
-
-			for item in so.items:
-				sre_details = get_stock_reservation_entries_for_voucher(
-					"Sales Order", so.name, item.name, fields=["delivered_qty", "status"]
-				)
-				self.assertGreater(sre_details[0].delivered_qty, 0)
-				self.assertEqual(sre_details[0].status, "Partially Delivered")
-
-			# Test - 8: Over Delivery against Sales Order, SRE Delivered Qty should not be greater than the SRE Reserved Qty.
-			with change_settings("Stock Settings", {"over_delivery_receipt_allowance": 100}):
-				dn2 = make_delivery_note(so.name)
-
-				for item in dn2.items:
-					item.qty += flt(uniform(1, 10), 0 if item.stock_uom == "Nos" else 3)
-
-				dn2.save()
-				dn2.submit()
-
-			for item in so.items:
-				sre_details = frappe.db.get_all(
-					"Stock Reservation Entry",
-					filters={
-						"voucher_type": "Sales Order",
-						"voucher_no": so.name,
-						"voucher_detail_no": item.name,
-					},
-					fields=["reserved_qty", "delivered_qty"],
-				)
-
-				for sre_detail in sre_details:
-					self.assertEqual(sre_detail.reserved_qty, sre_detail.delivered_qty)
-
 	def test_delivered_item_material_request(self):
 		"SO -> MR (Manufacture) -> WO. Test if WO Qty is updated in SO."
 		from erpnext.manufacturing.doctype.work_order.work_order import (
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 8fbb56c..62bd61f 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -2,6 +2,10 @@
 from frappe.model.db_query import DatabaseQuery
 from frappe.utils import cint, flt
 
+from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+	get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock,
+)
+
 
 @frappe.whitelist()
 def get_data(
@@ -57,6 +61,7 @@
 		limit_page_length=21,
 	)
 
+	sre_reserved_stock_details = get_reserved_stock(item_code, warehouse)
 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
 
 	for item in items:
@@ -70,6 +75,7 @@
 				"reserved_qty_for_production": flt(item.reserved_qty_for_production, precision),
 				"reserved_qty_for_sub_contract": flt(item.reserved_qty_for_sub_contract, precision),
 				"actual_qty": flt(item.actual_qty, precision),
+				"reserved_stock": sre_reserved_stock_details.get((item.item_code, item.warehouse), 0),
 			}
 		)
 	return items
diff --git a/erpnext/stock/dashboard/item_dashboard_list.html b/erpnext/stock/dashboard/item_dashboard_list.html
index 0c10be4..3b26191 100644
--- a/erpnext/stock/dashboard/item_dashboard_list.html
+++ b/erpnext/stock/dashboard/item_dashboard_list.html
@@ -12,7 +12,10 @@
 					</a>
 				{% endif %}
 			</div>
-			<div class="col-sm-4">
+			<div class="col-sm-1" style="margin-top: 8px;" title="{{ __("Reserved Stock") }}">
+				<a data-name="{{ d.reserved_stock }}">{{ d.reserved_stock }}</a>
+			</div>
+			<div class="col-sm-3">
 				<span class="inline-graph">
 					<span class="inline-graph-half" title="{{ __("Reserved Qty") }}">
 						<span class="inline-graph-count">{{ d.total_reserved }}</span>
diff --git a/erpnext/stock/doctype/batch/batch.js b/erpnext/stock/doctype/batch/batch.js
index 3b07e4e..7bf7a1f 100644
--- a/erpnext/stock/doctype/batch/batch.js
+++ b/erpnext/stock/doctype/batch/batch.js
@@ -41,7 +41,7 @@
 		if(!frm.is_new()) {
 			frappe.call({
 				method: 'erpnext.stock.doctype.batch.batch.get_batch_qty',
-				args: {batch_no: frm.doc.name},
+				args: {batch_no: frm.doc.name, item_code: frm.doc.item},
 				callback: (r) => {
 					if(!r.message) {
 						return;
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.js b/erpnext/stock/doctype/delivery_note/delivery_note.js
index 7ef1c9b..ec68549 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.js
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.js
@@ -150,6 +150,9 @@
 						}
 						erpnext.utils.map_current_doc({
 							method: "erpnext.selling.doctype.sales_order.sales_order.make_delivery_note",
+							args: {
+								for_reserved_stock: 1
+							},
 							source_doctype: "Sales Order",
 							target: me.frm,
 							setters: {
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index ea20a26..190575e 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -279,6 +279,8 @@
 		self.update_prevdoc_status()
 		self.update_billing_status()
 
+		self.update_stock_reservation_entries()
+
 		# Updating stock ledger should always be called after updating prevdoc status,
 		# because updating reserved qty in bin depends upon updated delivered qty in SO
 		self.update_stock_ledger()
@@ -297,55 +299,141 @@
 	def update_stock_reservation_entries(self) -> None:
 		"""Updates Delivered Qty in Stock Reservation Entries."""
 
-		# Don't update Delivered Qty on Return or Cancellation.
-		if self.is_return or self._action == "cancel":
+		# Don't update Delivered Qty on Return.
+		if self.is_return:
 			return
 
-		for item in self.get("items"):
-			# Skip if `Sales Order` or `Sales Order Item` reference is not set.
-			if not item.against_sales_order or not item.so_detail:
-				continue
+		if self._action == "submit":
+			for item in self.get("items"):
+				# Skip if `Sales Order` or `Sales Order Item` reference is not set.
+				if not item.against_sales_order or not item.so_detail:
+					continue
 
-			sre_list = frappe.db.get_all(
-				"Stock Reservation Entry",
-				{
-					"docstatus": 1,
-					"voucher_type": "Sales Order",
-					"voucher_no": item.against_sales_order,
-					"voucher_detail_no": item.so_detail,
-					"warehouse": item.warehouse,
-					"status": ["not in", ["Delivered", "Cancelled"]],
-				},
-				order_by="creation",
-			)
+				sre_list = frappe.db.get_all(
+					"Stock Reservation Entry",
+					{
+						"docstatus": 1,
+						"voucher_type": "Sales Order",
+						"voucher_no": item.against_sales_order,
+						"voucher_detail_no": item.so_detail,
+						"warehouse": item.warehouse,
+						"status": ["not in", ["Delivered", "Cancelled"]],
+					},
+					order_by="creation",
+				)
 
-			# Skip if no Stock Reservation Entries.
-			if not sre_list:
-				continue
+				# Skip if no Stock Reservation Entries.
+				if not sre_list:
+					continue
 
-			available_qty_to_deliver = item.stock_qty
-			for sre in sre_list:
-				if available_qty_to_deliver <= 0:
-					break
+				qty_to_deliver = item.stock_qty
+				for sre in sre_list:
+					if qty_to_deliver <= 0:
+						break
 
-				sre_doc = frappe.get_doc("Stock Reservation Entry", sre)
+					sre_doc = frappe.get_doc("Stock Reservation Entry", sre)
 
-				# `Delivered Qty` should be less than or equal to `Reserved Qty`.
-				qty_to_be_deliver = min(sre_doc.reserved_qty - sre_doc.delivered_qty, available_qty_to_deliver)
+					qty_can_be_deliver = 0
+					if sre_doc.reservation_based_on == "Serial and Batch":
+						sbb = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
+						if sre_doc.has_serial_no:
+							delivered_serial_nos = [d.serial_no for d in sbb.entries]
+							for entry in sre_doc.sb_entries:
+								if entry.serial_no in delivered_serial_nos:
+									entry.delivered_qty = 1  # Qty will always be 0 or 1 for Serial No.
+									entry.db_update()
+									qty_can_be_deliver += 1
+									delivered_serial_nos.remove(entry.serial_no)
+						else:
+							delivered_batch_qty = {d.batch_no: -1 * d.qty for d in sbb.entries}
+							for entry in sre_doc.sb_entries:
+								if entry.batch_no in delivered_batch_qty:
+									delivered_qty = min(
+										(entry.qty - entry.delivered_qty), delivered_batch_qty[entry.batch_no]
+									)
+									entry.delivered_qty += delivered_qty
+									entry.db_update()
+									qty_can_be_deliver += delivered_qty
+									delivered_batch_qty[entry.batch_no] -= delivered_qty
+					else:
+						# `Delivered Qty` should be less than or equal to `Reserved Qty`.
+						qty_can_be_deliver = min((sre_doc.reserved_qty - sre_doc.delivered_qty), qty_to_deliver)
 
-				sre_doc.delivered_qty += qty_to_be_deliver
-				sre_doc.db_update()
+					sre_doc.delivered_qty += qty_can_be_deliver
+					sre_doc.db_update()
 
-				# Update Stock Reservation Entry `Status` based on `Delivered Qty`.
-				sre_doc.update_status()
+					# Update Stock Reservation Entry `Status` based on `Delivered Qty`.
+					sre_doc.update_status()
 
-				available_qty_to_deliver -= qty_to_be_deliver
+					qty_to_deliver -= qty_can_be_deliver
+
+		if self._action == "cancel":
+			for item in self.get("items"):
+				# Skip if `Sales Order` or `Sales Order Item` reference is not set.
+				if not item.against_sales_order or not item.so_detail:
+					continue
+
+				sre_list = frappe.db.get_all(
+					"Stock Reservation Entry",
+					{
+						"docstatus": 1,
+						"voucher_type": "Sales Order",
+						"voucher_no": item.against_sales_order,
+						"voucher_detail_no": item.so_detail,
+						"warehouse": item.warehouse,
+						"status": ["in", ["Partially Delivered", "Delivered"]],
+					},
+					order_by="creation",
+				)
+
+				# Skip if no Stock Reservation Entries.
+				if not sre_list:
+					continue
+
+				qty_to_undelivered = item.stock_qty
+				for sre in sre_list:
+					if qty_to_undelivered <= 0:
+						break
+
+					sre_doc = frappe.get_doc("Stock Reservation Entry", sre)
+
+					qty_can_be_undelivered = 0
+					if sre_doc.reservation_based_on == "Serial and Batch":
+						sbb = frappe.get_doc("Serial and Batch Bundle", item.serial_and_batch_bundle)
+						if sre_doc.has_serial_no:
+							serial_nos_to_undelivered = [d.serial_no for d in sbb.entries]
+							for entry in sre_doc.sb_entries:
+								if entry.serial_no in serial_nos_to_undelivered:
+									entry.delivered_qty = 0  # Qty will always be 0 or 1 for Serial No.
+									entry.db_update()
+									qty_can_be_undelivered += 1
+									serial_nos_to_undelivered.remove(entry.serial_no)
+						else:
+							batch_qty_to_undelivered = {d.batch_no: -1 * d.qty for d in sbb.entries}
+							for entry in sre_doc.sb_entries:
+								if entry.batch_no in batch_qty_to_undelivered:
+									undelivered_qty = min(entry.delivered_qty, batch_qty_to_undelivered[entry.batch_no])
+									entry.delivered_qty -= undelivered_qty
+									entry.db_update()
+									qty_can_be_undelivered += undelivered_qty
+									batch_qty_to_undelivered[entry.batch_no] -= undelivered_qty
+					else:
+						# `Qty to Undelivered` should be less than or equal to `Delivered Qty`.
+						qty_can_be_undelivered = min(sre_doc.delivered_qty, qty_to_undelivered)
+
+					sre_doc.delivered_qty -= qty_can_be_undelivered
+					sre_doc.db_update()
+
+					# Update Stock Reservation Entry `Status` based on `Delivered Qty`.
+					sre_doc.update_status()
+
+					qty_to_undelivered -= qty_can_be_undelivered
 
 	def validate_against_stock_reservation_entries(self):
 		"""Validates if Stock Reservation Entries are available for the Sales Order Item reference."""
 
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-			get_sre_reserved_qty_details_for_voucher_detail_no,
+			get_sre_reserved_warehouses_for_voucher,
 		)
 
 		# Don't validate if Return
@@ -357,26 +445,30 @@
 			if not item.against_sales_order or not item.so_detail:
 				continue
 
-			sre_data = get_sre_reserved_qty_details_for_voucher_detail_no(
+			reserved_warehouses = get_sre_reserved_warehouses_for_voucher(
 				"Sales Order", item.against_sales_order, item.so_detail
 			)
 
 			# Skip if stock is not reserved.
-			if not sre_data:
+			if not reserved_warehouses:
 				continue
 
 			# Set `Warehouse` from SRE if not set.
 			if not item.warehouse:
-				item.warehouse = sre_data[0]
+				item.warehouse = reserved_warehouses[0]
 			else:
-				# Throw if `Warehouse` is different from SRE.
-				if item.warehouse != sre_data[0]:
-					frappe.throw(
-						_("Row #{0}: Stock is reserved for Item {1} in Warehouse {2}.").format(
-							item.idx, frappe.bold(item.item_code), frappe.bold(sre_data[0])
+				# Throw if `Warehouse` not in Reserved Warehouses.
+				if item.warehouse not in reserved_warehouses:
+					msg = _("Row #{0}: Stock is reserved for item {1} in warehouse {2}.").format(
+						item.idx,
+						frappe.bold(item.item_code),
+						frappe.bold(reserved_warehouses[0])
+						if len(reserved_warehouses) == 1
+						else _("{0} and {1}").format(
+							frappe.bold(", ".join(reserved_warehouses[:-1])), frappe.bold(reserved_warehouses[-1])
 						),
-						title=_("Stock Reservation Warehouse Mismatch"),
 					)
+					frappe.throw(msg, title=_("Stock Reservation Warehouse Mismatch"))
 
 	def check_credit_limit(self):
 		from erpnext.selling.doctype.customer.customer import check_credit_limit
diff --git a/erpnext/stock/doctype/item/item.js b/erpnext/stock/doctype/item/item.js
index 31a3ecb..76e8866 100644
--- a/erpnext/stock/doctype/item/item.js
+++ b/erpnext/stock/doctype/item/item.js
@@ -3,6 +3,9 @@
 
 frappe.provide("erpnext.item");
 
+const SALES_DOCTYPES = ['Quotation', 'Sales Order', 'Delivery Note', 'Sales Invoice'];
+const PURCHASE_DOCTYPES = ['Purchase Order', 'Purchase Receipt', 'Purchase Invoice'];
+
 frappe.ui.form.on("Item", {
 	setup: function(frm) {
 		frm.add_fetch('attribute', 'numeric_values', 'numeric_values');
@@ -888,7 +891,13 @@
 		let new_child_doc = frappe.model.add_child(new_doc, child_doctype, parentfield);
 		new_child_doc.item_code = frm.doc.name;
 		new_child_doc.item_name = frm.doc.item_name;
-		new_child_doc.uom = frm.doc.stock_uom;
+		if (in_list(SALES_DOCTYPES, doctype) && frm.doc.sales_uom) {
+			new_child_doc.uom = frm.doc.sales_uom;
+		} else if (in_list(PURCHASE_DOCTYPES, doctype) && frm.doc.purchase_uom) {
+			new_child_doc.uom = frm.doc.purchase_uom;
+		} else {
+			new_child_doc.uom = frm.doc.stock_uom;
+		}
 		new_child_doc.description = frm.doc.description;
 		if (!new_child_doc.qty) {
 			new_child_doc.qty = 1.0;
diff --git a/erpnext/stock/doctype/pick_list/pick_list.js b/erpnext/stock/doctype/pick_list/pick_list.js
index 35c35a6..4eed285 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.js
+++ b/erpnext/stock/doctype/pick_list/pick_list.js
@@ -115,6 +115,22 @@
 					frm.add_custom_button(__('Stock Entry'), () => frm.trigger('create_stock_entry'), __('Create'));
 				}
 			});
+
+			if (frm.doc.purpose === 'Delivery' && frm.doc.status === 'Open') {
+				if (frm.doc.__onload && frm.doc.__onload.has_unreserved_stock) {
+					frm.add_custom_button(__('Reserve'), () => frm.events.create_stock_reservation_entries(frm), __('Stock Reservation'));
+				}
+
+				if (frm.doc.__onload && frm.doc.__onload.has_reserved_stock) {
+					frm.add_custom_button(__('Unreserve'), () => {
+						frappe.confirm(
+							__('The reserved stock will be released. Are you certain you wish to proceed?'),
+							() => frm.events.cancel_stock_reservation_entries(frm)
+						)
+					}, __('Stock Reservation'));
+					frm.add_custom_button(__('Reserved Stock'), () => frm.events.show_reserved_stock(frm), __('Stock Reservation'));
+				}
+			}
 		}
 	},
 	work_order: (frm) => {
@@ -209,6 +225,49 @@
 		};
 		const barcode_scanner = new erpnext.utils.BarcodeScanner(opts);
 		barcode_scanner.process_scan();
+	},
+	create_stock_reservation_entries: (frm) => {
+		frappe.call({
+			doc: frm.doc,
+			method: "create_stock_reservation_entries",
+			args: {
+				notify: true
+			},
+			freeze: true,
+			freeze_message: __("Reserving Stock..."),
+			callback: (r) => {
+				frm.doc.__onload.has_unreserved_stock = false;
+				frm.reload_doc();
+			}
+		});
+	},
+	cancel_stock_reservation_entries: (frm) => {
+		frappe.call({
+			doc: frm.doc,
+			method: "cancel_stock_reservation_entries",
+			args: {
+				notify: true
+			},
+			freeze: true,
+			freeze_message: __('Unreserving Stock...'),
+			callback: (r) => {
+				frm.doc.__onload.has_reserved_stock = false;
+				frm.reload_doc();
+			}
+		});
+	},
+	show_reserved_stock(frm) {
+		// Get the latest modified date from the locations table.
+		var to_date = moment(new Date(Math.max(...frm.doc.locations.map(e => new Date(e.modified))))).format('YYYY-MM-DD');
+
+		frappe.route_options = {
+			company: frm.doc.company,
+			from_date: moment(frm.doc.creation).format('YYYY-MM-DD'),
+			to_date: to_date,
+			voucher_type: "Sales Order",
+			against_pick_list: frm.doc.name,
+		}
+		frappe.set_route("query-report", "Reserved Stock");
 	}
 });
 
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index 922f76c..2fcd102 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -29,6 +29,14 @@
 
 
 class PickList(Document):
+	def onload(self) -> None:
+		if frappe.get_cached_value("Stock Settings", None, "enable_stock_reservation"):
+			if self.has_unreserved_stock():
+				self.set_onload("has_unreserved_stock", True)
+
+		if self.has_reserved_stock():
+			self.set_onload("has_reserved_stock", True)
+
 	def validate(self):
 		self.validate_for_qty()
 
@@ -47,8 +55,28 @@
 				)
 
 	def before_submit(self):
+		self.validate_sales_order()
 		self.validate_picked_items()
 
+	def validate_sales_order(self):
+		"""Raises an exception if the `Sales Order` has reserved stock."""
+
+		if self.purpose != "Delivery":
+			return
+
+		so_list = set(location.sales_order for location in self.locations if location.sales_order)
+
+		if so_list:
+			for so in so_list:
+				so_doc = frappe.get_doc("Sales Order", so)
+				for item in so_doc.items:
+					if item.stock_reserved_qty > 0:
+						frappe.throw(
+							_(
+								"Cannot create a pick list for Sales Order {0} because it has reserved stock. Please unreserve the stock in order to create a pick list."
+							).format(frappe.bold(so))
+						)
+
 	def validate_picked_items(self):
 		for item in self.locations:
 			if self.scan_mode and item.picked_qty < item.stock_qty:
@@ -70,8 +98,19 @@
 		self.update_reference_qty()
 		self.update_sales_order_picking_status()
 
+	def on_update_after_submit(self) -> None:
+		if self.has_reserved_stock():
+			msg = _(
+				"The Pick List having Stock Reservation Entries cannot be updated. If you need to make changes, we recommend canceling the existing Stock Reservation Entries before updating the Pick List."
+			)
+			frappe.throw(msg)
+
 	def on_cancel(self):
-		self.ignore_linked_doctypes = "Serial and Batch Bundle"
+		self.ignore_linked_doctypes = [
+			"Serial and Batch Bundle",
+			"Stock Reservation Entry",
+			"Delivery Note",
+		]
 
 		self.update_status()
 		self.update_bundle_picked_qty()
@@ -186,6 +225,36 @@
 		for sales_order in sales_orders:
 			frappe.get_doc("Sales Order", sales_order, for_update=True).update_picking_status()
 
+	@frappe.whitelist()
+	def create_stock_reservation_entries(self, notify=True) -> None:
+		"""Creates Stock Reservation Entries for Sales Order Items against Pick List."""
+
+		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+			create_stock_reservation_entries_for_so_items,
+		)
+
+		so_details = {}
+		for location in self.locations:
+			if location.warehouse and location.sales_order and location.sales_order_item:
+				so_details.setdefault(location.sales_order, []).append(location)
+
+		if so_details:
+			for so, locations in so_details.items():
+				so_doc = frappe.get_doc("Sales Order", so)
+				create_stock_reservation_entries_for_so_items(
+					so=so_doc, items_details=locations, against_pick_list=True, notify=notify
+				)
+
+	@frappe.whitelist()
+	def cancel_stock_reservation_entries(self, notify=True) -> None:
+		"""Cancel Stock Reservation Entries for Sales Order Items created against Pick List."""
+
+		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+			cancel_stock_reservation_entries,
+		)
+
+		cancel_stock_reservation_entries(against_pick_list=self.name, notify=notify)
+
 	def validate_picked_qty(self, data):
 		over_delivery_receipt_allowance = 100 + flt(
 			frappe.db.get_single_value("Stock Settings", "over_delivery_receipt_allowance")
@@ -448,6 +517,26 @@
 				possible_bundles.append(0)
 		return int(flt(min(possible_bundles), precision or 6))
 
+	def has_unreserved_stock(self):
+		if self.purpose == "Delivery":
+			for location in self.locations:
+				if (
+					location.sales_order
+					and location.sales_order_item
+					and (flt(location.picked_qty) - flt(location.stock_reserved_qty)) > 0
+				):
+					return True
+
+		return False
+
+	def has_reserved_stock(self):
+		if self.purpose == "Delivery":
+			for location in self.locations:
+				if location.sales_order and location.sales_order_item and flt(location.stock_reserved_qty) > 0:
+					return True
+
+		return False
+
 
 def update_pick_list_status(pick_list):
 	if pick_list:
@@ -781,7 +870,8 @@
 	for customer in sales_dict:
 		for so in sales_dict[customer]:
 			delivery_note = None
-			delivery_note = create_delivery_note_from_sales_order(so, delivery_note, skip_item_mapping=True)
+			kwargs = {"skip_item_mapping": True}
+			delivery_note = create_delivery_note_from_sales_order(so, delivery_note, kwargs=kwargs)
 			break
 		if delivery_note:
 			# map all items of all sales orders of that customer
diff --git a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
index 7fbcbaf..0830fa2 100644
--- a/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
+++ b/erpnext/stock/doctype/pick_list/pick_list_dashboard.py
@@ -1,10 +1,13 @@
 def get_data():
 	return {
 		"fieldname": "pick_list",
+		"non_standard_fieldnames": {
+			"Stock Reservation Entry": "against_pick_list",
+		},
 		"internal_links": {
 			"Sales Order": ["locations", "sales_order"],
 		},
 		"transactions": [
-			{"items": ["Stock Entry", "Sales Order", "Delivery Note"]},
+			{"items": ["Stock Entry", "Sales Order", "Delivery Note", "Stock Reservation Entry"]},
 		],
 	}
diff --git a/erpnext/stock/doctype/pick_list_item/pick_list_item.json b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
index 2e56af3..e8e4afc 100644
--- a/erpnext/stock/doctype/pick_list_item/pick_list_item.json
+++ b/erpnext/stock/doctype/pick_list_item/pick_list_item.json
@@ -16,6 +16,7 @@
   "qty",
   "stock_qty",
   "picked_qty",
+  "stock_reserved_qty",
   "column_break_11",
   "uom",
   "conversion_factor",
@@ -46,7 +47,7 @@
    "fieldname": "picked_qty",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Picked Qty"
+   "label": "Picked Qty (in Stock UOM)"
   },
   {
    "fieldname": "warehouse",
@@ -154,8 +155,7 @@
    "fieldtype": "Data",
    "hidden": 1,
    "label": "Sales Order Item",
-   "read_only": 1,
-   "search_index": 1
+   "read_only": 1
   },
   {
    "fieldname": "serial_no_and_batch_section",
@@ -207,6 +207,17 @@
    "fieldname": "pick_serial_and_batch",
    "fieldtype": "Button",
    "label": "Pick Serial / Batch No"
+  },
+  {
+   "default": "0",
+   "fieldname": "stock_reserved_qty",
+   "fieldtype": "Float",
+   "label": "Stock Reserved Qty (in Stock UOM)",
+   "no_copy": 1,
+   "non_negative": 1,
+   "print_hide": 1,
+   "read_only": 1,
+   "report_hide": 1
   }
  ],
  "istable": 1,
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 1f90c5b..96e4a55 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
@@ -66,7 +66,7 @@
 		serial_nos = [d.serial_no for d in self.entries if d.serial_no]
 		kwargs = {"item_code": self.item_code, "warehouse": self.warehouse}
 		if self.voucher_type == "POS Invoice":
-			kwargs["ignore_voucher_no"] = self.voucher_no
+			kwargs["ignore_voucher_nos"] = [self.voucher_no]
 
 		available_serial_nos = get_available_serial_nos(frappe._dict(kwargs))
 
@@ -1098,8 +1098,8 @@
 	if kwargs.warehouse:
 		filters["warehouse"] = kwargs.warehouse
 
-	# Since SLEs are not present against POS invoices, need to ignore serial nos present in the POS invoice
-	ignore_serial_nos = get_reserved_serial_nos_for_pos(kwargs)
+	# Since SLEs are not present against Reserved Stock [POS invoices, SRE], need to ignore reserved serial nos.
+	ignore_serial_nos = get_reserved_serial_nos(kwargs)
 
 	# To ignore serial nos in the same record for the draft state
 	if kwargs.get("ignore_serial_nos"):
@@ -1180,6 +1180,20 @@
 	return serial_nos
 
 
+def get_reserved_serial_nos(kwargs) -> list:
+	"""Returns a list of `Serial No` reserved in POS Invoice and Stock Reservation Entry."""
+
+	ignore_serial_nos = []
+
+	# Extend the list by serial nos reserved in POS Invoice
+	ignore_serial_nos.extend(get_reserved_serial_nos_for_pos(kwargs))
+
+	# Extend the list by serial nos reserved via SRE
+	ignore_serial_nos.extend(get_reserved_serial_nos_for_sre(kwargs))
+
+	return ignore_serial_nos
+
+
 def get_reserved_serial_nos_for_pos(kwargs):
 	from erpnext.controllers.sales_and_purchase_return import get_returned_serial_nos
 	from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -1199,7 +1213,7 @@
 			["POS Invoice", "docstatus", "=", 1],
 			["POS Invoice", "is_return", "=", 0],
 			["POS Invoice Item", "item_code", "=", kwargs.item_code],
-			["POS Invoice", "name", "!=", kwargs.ignore_voucher_no],
+			["POS Invoice", "name", "not in", kwargs.ignore_voucher_nos],
 		],
 	)
 
@@ -1251,7 +1265,37 @@
 	return list(ignore_serial_nos_counter - returned_serial_nos_counter)
 
 
-def get_reserved_batches_for_pos(kwargs):
+def get_reserved_serial_nos_for_sre(kwargs) -> list:
+	"""Returns a list of `Serial No` reserved in Stock Reservation Entry."""
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+	query = (
+		frappe.qb.from_(sre)
+		.inner_join(sb_entry)
+		.on(sre.name == sb_entry.parent)
+		.select(sb_entry.serial_no)
+		.where(
+			(sre.docstatus == 1)
+			& (sre.item_code == kwargs.item_code)
+			& (sre.reserved_qty >= sre.delivered_qty)
+			& (sre.status.notin(["Delivered", "Cancelled"]))
+			& (sre.reservation_based_on == "Serial and Batch")
+		)
+	)
+
+	if kwargs.warehouse:
+		query = query.where(sre.warehouse == kwargs.warehouse)
+
+	if kwargs.ignore_voucher_nos:
+		query = query.where(sre.name.notin(kwargs.ignore_voucher_nos))
+
+	return [row[0] for row in query.run()]
+
+
+def get_reserved_batches_for_pos(kwargs) -> dict:
+	"""Returns a dict of `Batch No` followed by the `Qty` reserved in POS Invoices."""
+
 	pos_batches = frappe._dict()
 	pos_invoices = frappe.get_all(
 		"POS Invoice",
@@ -1267,7 +1311,7 @@
 			["POS Invoice", "consolidated_invoice", "is", "not set"],
 			["POS Invoice", "docstatus", "=", 1],
 			["POS Invoice Item", "item_code", "=", kwargs.item_code],
-			["POS Invoice", "name", "!=", kwargs.ignore_voucher_no],
+			["POS Invoice", "name", "not in", kwargs.ignore_voucher_nos],
 		],
 	)
 
@@ -1278,7 +1322,7 @@
 	]
 
 	if not ids:
-		return []
+		return {}
 
 	if ids:
 		for d in get_serial_batch_ledgers(kwargs.item_code, docstatus=1, name=ids):
@@ -1314,14 +1358,65 @@
 	return pos_batches
 
 
+def get_reserved_batches_for_sre(kwargs) -> dict:
+	"""Returns a dict of `Batch No` followed by the `Qty` reserved in Stock Reservation Entry."""
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+	query = (
+		frappe.qb.from_(sre)
+		.inner_join(sb_entry)
+		.on(sre.name == sb_entry.parent)
+		.select(
+			sb_entry.batch_no, sre.warehouse, (-1 * Sum(sb_entry.qty - sb_entry.delivered_qty)).as_("qty")
+		)
+		.where(
+			(sre.docstatus == 1)
+			& (sre.item_code == kwargs.item_code)
+			& (sre.reserved_qty >= sre.delivered_qty)
+			& (sre.status.notin(["Delivered", "Cancelled"]))
+			& (sre.reservation_based_on == "Serial and Batch")
+		)
+		.groupby(sb_entry.batch_no, sre.warehouse)
+	)
+
+	if kwargs.batch_no:
+		if isinstance(kwargs.batch_no, list):
+			query = query.where(sb_entry.batch_no.isin(kwargs.batch_no))
+		else:
+			query = query.where(sb_entry.batch_no == kwargs.batch_no)
+
+	if kwargs.warehouse:
+		query = query.where(sre.warehouse == kwargs.warehouse)
+
+	if kwargs.ignore_voucher_nos:
+		query = query.where(sre.name.notin(kwargs.ignore_voucher_nos))
+
+	data = query.run(as_dict=True)
+
+	reserved_batches_details = frappe._dict()
+	if data:
+		reserved_batches_details = frappe._dict(
+			{
+				(d.batch_no, d.warehouse): frappe._dict({"warehouse": d.warehouse, "qty": d.qty}) for d in data
+			}
+		)
+
+	return reserved_batches_details
+
+
 def get_auto_batch_nos(kwargs):
 	available_batches = get_available_batches(kwargs)
 	qty = flt(kwargs.qty)
 
-	pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
 	stock_ledgers_batches = get_stock_ledgers_batches(kwargs)
-	if stock_ledgers_batches or pos_invoice_batches:
-		update_available_batches(available_batches, stock_ledgers_batches, pos_invoice_batches)
+	pos_invoice_batches = get_reserved_batches_for_pos(kwargs)
+	sre_reserved_batches = get_reserved_batches_for_sre(kwargs)
+
+	if stock_ledgers_batches or pos_invoice_batches or sre_reserved_batches:
+		update_available_batches(
+			available_batches, stock_ledgers_batches, pos_invoice_batches, sre_reserved_batches
+		)
 
 	available_batches = list(filter(lambda x: x.qty > 0, available_batches))
 
@@ -1364,8 +1459,8 @@
 	return batches
 
 
-def update_available_batches(available_batches, reserved_batches=None, pos_invoice_batches=None):
-	for batches in [reserved_batches, pos_invoice_batches]:
+def update_available_batches(available_batches, *reserved_batches) -> None:
+	for batches in reserved_batches:
 		if batches:
 			for key, data in batches.items():
 				batch_no, warehouse = key
diff --git a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
index 6ec2129..09565cb 100644
--- a/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
+++ b/erpnext/stock/doctype/serial_and_batch_entry/serial_and_batch_entry.json
@@ -10,6 +10,7 @@
   "column_break_2",
   "qty",
   "warehouse",
+  "delivered_qty",
   "section_break_6",
   "incoming_rate",
   "column_break_8",
@@ -104,12 +105,24 @@
    "fieldtype": "Small Text",
    "label": "FIFO Stock Queue (qty, rate)",
    "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: parent.doctype == \"Stock Reservation Entry\"",
+   "fieldname": "delivered_qty",
+   "fieldtype": "Float",
+   "label": "Delivered Qty",
+   "no_copy": 1,
+   "non_negative": 1,
+   "print_hide": 1,
+   "read_only": 1,
+   "report_hide": 1
   }
  ],
  "index_web_pages_for_search": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-03-31 11:18:59.809486",
+ "modified": "2023-07-03 15:29:50.199075",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Serial and Batch Entry",
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index f009bd4..26ca012 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -346,7 +346,7 @@
 		"""Raises an exception if there is any reserved stock for the items in the Stock Reconciliation."""
 
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-			get_sre_reserved_qty_details_for_item_and_warehouse as get_sre_reserved_qty_details,
+			get_sre_reserved_qty_for_item_and_warehouse as get_sre_reserved_qty_details,
 		)
 
 		item_code_list, warehouse_list = [], []
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js
index 666fd24..4d96636 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js
@@ -3,6 +3,124 @@
 
 frappe.ui.form.on("Stock Reservation Entry", {
 	refresh(frm) {
-        frm.page.btn_primary.hide()
+		frm.trigger("set_queries");
+		frm.trigger("toggle_read_only_fields");
+		frm.trigger("hide_rate_related_fields");
+		frm.trigger("hide_primary_action_button");
+		frm.trigger("make_sb_entries_warehouse_read_only");
+	},
+
+	has_serial_no(frm) {
+		frm.trigger("toggle_read_only_fields");
+	},
+
+	has_batch_no(frm) {
+		frm.trigger("toggle_read_only_fields");
+	},
+
+	warehouse(frm) {
+		if (frm.doc.warehouse) {
+			frm.doc.sb_entries.forEach((row) => {
+				frappe.model.set_value(row.doctype, row.name, "warehouse", frm.doc.warehouse);
+			});
+		}
+	},
+
+	set_queries(frm) {
+		frm.set_query("warehouse", () => {
+			return {
+				filters: {
+					"is_group": 0,
+					"company": frm.doc.company,
+				}
+			};
+		});
+
+		frm.set_query("serial_no", "sb_entries", function(doc, cdt, cdn) {
+			var selected_serial_nos = doc.sb_entries.map(row => {
+				return row.serial_no;
+			});
+			var row = locals[cdt][cdn];
+			return {
+				filters: {
+					item_code: doc.item_code,
+					warehouse: row.warehouse,
+					status: "Active",
+					name: ["not in", selected_serial_nos],
+				}
+			}
+		});
+
+		frm.set_query("batch_no", "sb_entries", function(doc, cdt, cdn) {
+			let filters = {
+				item: doc.item_code,
+				batch_qty: [">", 0],
+				disabled: 0,
+			}
+
+			if (!doc.has_serial_no) {
+				var selected_batch_nos = doc.sb_entries.map(row => {
+					return row.batch_no;
+				});
+
+				filters.name = ["not in", selected_batch_nos];
+			}
+
+			return { filters: filters }
+		});
+	},
+
+	toggle_read_only_fields(frm) {
+		if (frm.doc.has_serial_no) {
+			frm.doc.sb_entries.forEach(row => {
+				if (row.qty !== 1) {
+					frappe.model.set_value(row.doctype, row.name, "qty", 1);
+				}
+			})
+		}
+
+		frm.fields_dict.sb_entries.grid.update_docfield_property(
+			"serial_no", "read_only", !frm.doc.has_serial_no
+		);
+
+		frm.fields_dict.sb_entries.grid.update_docfield_property(
+			"batch_no", "read_only", !frm.doc.has_batch_no
+		);
+
+		// Qty will always be 1 for Serial No.
+		frm.fields_dict.sb_entries.grid.update_docfield_property(
+			"qty", "read_only", frm.doc.has_serial_no
+		);
+
+		frm.set_df_property("sb_entries", "allow_on_submit", frm.doc.against_pick_list ? 0 : 1);
+	},
+
+	hide_rate_related_fields(frm) {
+		["incoming_rate", "outgoing_rate", "stock_value_difference", "is_outward", "stock_queue"].forEach(field => {
+			frm.fields_dict.sb_entries.grid.update_docfield_property(
+				field, "hidden", 1
+			);
+		});
+	},
+
+	hide_primary_action_button(frm) {
+		// Hide "Amend" button on cancelled document
+		if (frm.doc.docstatus == 2) {
+			frm.page.btn_primary.hide()
+		}
+	},
+
+	make_sb_entries_warehouse_read_only(frm) {
+		frm.fields_dict.sb_entries.grid.update_docfield_property(
+			"warehouse", "read_only", 1
+		);
 	},
 });
+
+frappe.ui.form.on("Serial and Batch Entry", {
+	sb_entries_add(frm, cdt, cdn) {
+		if (frm.doc.warehouse) {
+			frappe.model.set_value(cdt, cdn, "warehouse", frm.doc.warehouse);
+		}
+	},
+});
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
index 7c7abac..5c3018f 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.json
@@ -2,7 +2,7 @@
  "actions": [],
  "allow_copy": 1,
  "autoname": "MAT-SRE-.YYYY.-.#####",
- "creation": "2023-03-20 10:45:59.258959",
+ "creation": "2023-06-06 15:20:48.016846",
  "default_view": "List",
  "doctype": "DocType",
  "editable_grid": 1,
@@ -10,17 +10,26 @@
  "field_order": [
   "item_code",
   "warehouse",
+  "has_serial_no",
+  "has_batch_no",
   "column_break_elik",
   "voucher_type",
   "voucher_no",
   "voucher_detail_no",
+  "column_break_7dxj",
+  "against_pick_list",
+  "against_pick_list_item",
   "section_break_xt4m",
+  "stock_uom",
+  "column_break_grdt",
   "available_qty",
   "voucher_qty",
-  "stock_uom",
   "column_break_o6ex",
   "reserved_qty",
   "delivered_qty",
+  "serial_and_batch_reservation_section",
+  "reservation_based_on",
+  "sb_entries",
   "section_break_3vb3",
   "company",
   "column_break_jbyr",
@@ -36,6 +45,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Item Code",
+   "no_copy": 1,
    "oldfieldname": "item_code",
    "oldfieldtype": "Link",
    "options": "Item",
@@ -51,6 +61,7 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Warehouse",
+   "no_copy": 1,
    "oldfieldname": "warehouse",
    "oldfieldtype": "Link",
    "options": "Warehouse",
@@ -64,6 +75,7 @@
    "fieldtype": "Select",
    "in_filter": 1,
    "label": "Voucher Type",
+   "no_copy": 1,
    "oldfieldname": "voucher_type",
    "oldfieldtype": "Data",
    "options": "\nSales Order",
@@ -78,17 +90,20 @@
    "in_list_view": 1,
    "in_standard_filter": 1,
    "label": "Voucher No",
+   "no_copy": 1,
    "oldfieldname": "voucher_no",
    "oldfieldtype": "Data",
    "options": "voucher_type",
    "print_width": "150px",
    "read_only": 1,
+   "search_index": 1,
    "width": "150px"
   },
   {
    "fieldname": "voucher_detail_no",
    "fieldtype": "Data",
    "label": "Voucher Detail No",
+   "no_copy": 1,
    "oldfieldname": "voucher_detail_no",
    "oldfieldtype": "Data",
    "print_width": "150px",
@@ -100,6 +115,7 @@
    "fieldname": "stock_uom",
    "fieldtype": "Link",
    "label": "Stock UOM",
+   "no_copy": 1,
    "oldfieldname": "stock_uom",
    "oldfieldtype": "Data",
    "options": "UOM",
@@ -111,14 +127,17 @@
    "fieldname": "project",
    "fieldtype": "Link",
    "label": "Project",
+   "no_copy": 1,
    "options": "Project",
-   "read_only": 1
+   "read_only": 1,
+   "search_index": 1
   },
   {
    "fieldname": "company",
    "fieldtype": "Link",
    "in_filter": 1,
    "label": "Company",
+   "no_copy": 1,
    "oldfieldname": "company",
    "oldfieldtype": "Data",
    "options": "Company",
@@ -128,23 +147,26 @@
    "width": "150px"
   },
   {
+   "allow_on_submit": 1,
    "fieldname": "reserved_qty",
    "fieldtype": "Float",
    "in_filter": 1,
    "in_list_view": 1,
    "label": "Reserved Qty",
+   "no_copy": 1,
+   "non_negative": 1,
    "oldfieldname": "actual_qty",
    "oldfieldtype": "Currency",
    "print_width": "150px",
-   "read_only": 1,
+   "read_only_depends_on": "eval: ((doc.reservation_based_on == \"Serial and Batch\") || (doc.against_pick_list) || (doc.delivered_qty > 0))",
    "width": "150px"
   },
   {
    "default": "Draft",
    "fieldname": "status",
    "fieldtype": "Select",
-   "hidden": 1,
    "label": "Status",
+   "no_copy": 1,
    "options": "Draft\nPartially Reserved\nReserved\nPartially Delivered\nDelivered\nCancelled",
    "read_only": 1
   },
@@ -153,6 +175,8 @@
    "fieldname": "delivered_qty",
    "fieldtype": "Float",
    "label": "Delivered Qty",
+   "no_copy": 1,
+   "non_negative": 1,
    "read_only": 1
   },
   {
@@ -170,6 +194,7 @@
    "fieldtype": "Float",
    "label": "Available Qty to Reserve",
    "no_copy": 1,
+   "non_negative": 1,
    "read_only": 1
   },
   {
@@ -178,6 +203,7 @@
    "fieldtype": "Float",
    "label": "Voucher Qty",
    "no_copy": 1,
+   "non_negative": 1,
    "read_only": 1
   },
   {
@@ -193,12 +219,84 @@
    "fieldtype": "Column Break"
   },
   {
+   "collapsible": 1,
    "fieldname": "section_break_3vb3",
-   "fieldtype": "Section Break"
+   "fieldtype": "Section Break",
+   "label": "More Information"
   },
   {
    "fieldname": "column_break_jbyr",
    "fieldtype": "Column Break"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.has_serial_no",
+   "fieldname": "has_serial_no",
+   "fieldtype": "Check",
+   "label": "Has Serial No",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.has_batch_no",
+   "fieldname": "has_batch_no",
+   "fieldtype": "Check",
+   "label": "Has Batch No",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "depends_on": "eval: (doc.has_serial_no || doc.has_batch_no) && doc.reservation_based_on == \"Serial and Batch\"",
+   "fieldname": "sb_entries",
+   "fieldtype": "Table",
+   "options": "Serial and Batch Entry",
+   "read_only_depends_on": "eval: (doc.delivered_qty > 0)"
+  },
+  {
+   "fieldname": "serial_and_batch_reservation_section",
+   "fieldtype": "Section Break",
+   "label": "Serial and Batch Reservation"
+  },
+  {
+   "allow_on_submit": 1,
+   "default": "Qty",
+   "depends_on": "eval: parent.has_serial_no || parent.has_batch_no",
+   "fieldname": "reservation_based_on",
+   "fieldtype": "Select",
+   "label": "Reservation Based On",
+   "no_copy": 1,
+   "options": "Qty\nSerial and Batch",
+   "read_only_depends_on": "eval: (doc.delivered_qty > 0 || doc.against_pick_list)"
+  },
+  {
+   "fieldname": "against_pick_list",
+   "fieldtype": "Link",
+   "label": "Against Pick List",
+   "no_copy": 1,
+   "options": "Pick List",
+   "print_hide": 1,
+   "read_only": 1,
+   "report_hide": 1,
+   "search_index": 1
+  },
+  {
+   "fieldname": "against_pick_list_item",
+   "fieldtype": "Data",
+   "label": "Against Pick List Item",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1,
+   "report_hide": 1
+  },
+  {
+   "fieldname": "column_break_7dxj",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "column_break_grdt",
+   "fieldtype": "Column Break"
   }
  ],
  "hide_toolbar": 1,
@@ -206,7 +304,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-03-29 18:36:26.752872",
+ "modified": "2023-08-08 17:15:13.317706",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Reservation Entry",
@@ -230,5 +328,6 @@
  ],
  "sort_field": "modified",
  "sort_order": "DESC",
- "states": []
+ "states": [],
+ "track_changes": 1
 }
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
index 5819dd7..bd7bb66 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -5,27 +5,57 @@
 from frappe import _
 from frappe.model.document import Document
 from frappe.query_builder.functions import Sum
+from frappe.utils import cint, flt
 
 
 class StockReservationEntry(Document):
 	def validate(self) -> None:
 		from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company
 
+		self.validate_amended_doc()
 		self.validate_mandatory()
 		self.validate_for_group_warehouse()
 		validate_disabled_warehouse(self.warehouse)
 		validate_warehouse_company(self.warehouse, self.company)
+		self.validate_uom_is_integer()
+
+	def before_submit(self) -> None:
+		self.set_reservation_based_on()
+		self.validate_reservation_based_on_qty()
+		self.auto_reserve_serial_and_batch()
+		self.validate_reservation_based_on_serial_and_batch()
 
 	def on_submit(self) -> None:
 		self.update_reserved_qty_in_voucher()
+		self.update_reserved_qty_in_pick_list()
 		self.update_status()
 
+	def on_update_after_submit(self) -> None:
+		self.can_be_updated()
+		self.validate_uom_is_integer()
+		self.set_reservation_based_on()
+		self.validate_reservation_based_on_qty()
+		self.validate_reservation_based_on_serial_and_batch()
+		self.update_reserved_qty_in_voucher()
+		self.update_status()
+		self.reload()
+
 	def on_cancel(self) -> None:
 		self.update_reserved_qty_in_voucher()
+		self.update_reserved_qty_in_pick_list()
 		self.update_status()
 
+	def validate_amended_doc(self) -> None:
+		"""Raises an exception if document is amended."""
+
+		if self.amended_from:
+			msg = _("Cannot amend {0} {1}, please create a new one instead.").format(
+				self.doctype, frappe.bold(self.amended_from)
+			)
+			frappe.throw(msg)
+
 	def validate_mandatory(self) -> None:
-		"""Raises exception if mandatory fields are not set."""
+		"""Raises an exception if mandatory fields are not set."""
 
 		mandatory = [
 			"item_code",
@@ -41,36 +71,217 @@
 		]
 		for d in mandatory:
 			if not self.get(d):
-				frappe.throw(_("{0} is required").format(self.meta.get_label(d)))
+				msg = _("{0} is required").format(self.meta.get_label(d))
+				frappe.throw(msg)
 
 	def validate_for_group_warehouse(self) -> None:
-		"""Raises exception if `Warehouse` is a Group Warehouse."""
+		"""Raises an exception if `Warehouse` is a Group Warehouse."""
 
 		if frappe.get_cached_value("Warehouse", self.warehouse, "is_group"):
-			frappe.throw(
-				_("Stock cannot be reserved in group warehouse {0}.").format(frappe.bold(self.warehouse)),
-				title=_("Invalid Warehouse"),
+			msg = _("Stock cannot be reserved in group warehouse {0}.").format(frappe.bold(self.warehouse))
+			frappe.throw(msg, title=_("Invalid Warehouse"))
+
+	def validate_uom_is_integer(self) -> None:
+		"""Validates `Reserved Qty` with Stock UOM."""
+
+		if cint(frappe.db.get_value("UOM", self.stock_uom, "must_be_whole_number", cache=True)):
+			if cint(self.reserved_qty) != flt(self.reserved_qty, self.precision("reserved_qty")):
+				msg = _(
+					"Reserved Qty ({0}) cannot be a fraction. To allow this, disable '{1}' in UOM {3}."
+				).format(
+					flt(self.reserved_qty, self.precision("reserved_qty")),
+					frappe.bold(_("Must be Whole Number")),
+					frappe.bold(self.stock_uom),
+				)
+				frappe.throw(msg)
+
+	def set_reservation_based_on(self) -> None:
+		"""Sets `Reservation Based On` based on `Has Serial No` and `Has Batch No`."""
+
+		if (self.reservation_based_on == "Serial and Batch") and (
+			not self.has_serial_no and not self.has_batch_no
+		):
+			self.db_set("reservation_based_on", "Qty")
+
+	def validate_reservation_based_on_qty(self) -> None:
+		"""Validates `Reserved Qty` when `Reservation Based On` is `Qty`."""
+
+		if self.reservation_based_on == "Qty":
+			self.validate_with_max_reserved_qty(self.reserved_qty)
+
+	def auto_reserve_serial_and_batch(self, based_on: str = None) -> None:
+		"""Auto pick Serial and Batch Nos to reserve when `Reservation Based On` is `Serial and Batch`."""
+
+		if (
+			not self.against_pick_list
+			and (self.get("_action") == "submit")
+			and (self.has_serial_no or self.has_batch_no)
+			and cint(frappe.db.get_single_value("Stock Settings", "auto_reserve_serial_and_batch"))
+		):
+			from erpnext.stock.doctype.batch.batch import get_available_batches
+			from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos_for_outward
+			from erpnext.stock.serial_batch_bundle import get_serial_nos_batch
+
+			self.reservation_based_on = "Serial and Batch"
+			self.sb_entries.clear()
+			kwargs = frappe._dict(
+				{
+					"item_code": self.item_code,
+					"warehouse": self.warehouse,
+					"qty": abs(self.reserved_qty) or 0,
+					"based_on": based_on
+					or frappe.db.get_single_value("Stock Settings", "pick_serial_and_batch_based_on"),
+				}
 			)
 
-	def update_status(self, status: str = None, update_modified: bool = True) -> None:
-		"""Updates status based on Voucher Qty, Reserved Qty and Delivered Qty."""
+			serial_nos, batch_nos = [], []
+			if self.has_serial_no:
+				serial_nos = get_serial_nos_for_outward(kwargs)
+			if self.has_batch_no:
+				batch_nos = get_available_batches(kwargs)
 
-		if not status:
-			if self.docstatus == 2:
-				status = "Cancelled"
-			elif self.docstatus == 1:
-				if self.reserved_qty == self.delivered_qty:
-					status = "Delivered"
-				elif self.delivered_qty and self.delivered_qty < self.reserved_qty:
-					status = "Partially Delivered"
-				elif self.reserved_qty == self.voucher_qty:
-					status = "Reserved"
-				else:
-					status = "Partially Reserved"
-			else:
-				status = "Draft"
+			if serial_nos:
+				serial_no_wise_batch = frappe._dict({})
 
-		frappe.db.set_value(self.doctype, self.name, "status", status, update_modified=update_modified)
+				if self.has_batch_no:
+					serial_no_wise_batch = get_serial_nos_batch(serial_nos)
+
+				for serial_no in serial_nos:
+					self.append(
+						"sb_entries",
+						{
+							"serial_no": serial_no,
+							"qty": 1,
+							"batch_no": serial_no_wise_batch.get(serial_no),
+							"warehouse": self.warehouse,
+						},
+					)
+			elif batch_nos:
+				for batch_no, batch_qty in batch_nos.items():
+					self.append(
+						"sb_entries",
+						{
+							"batch_no": batch_no,
+							"qty": batch_qty,
+							"warehouse": self.warehouse,
+						},
+					)
+
+	def validate_reservation_based_on_serial_and_batch(self) -> None:
+		"""Validates `Reserved Qty`, `Serial and Batch Nos` when `Reservation Based On` is `Serial and Batch`."""
+
+		if self.reservation_based_on == "Serial and Batch":
+			allow_partial_reservation = frappe.db.get_single_value(
+				"Stock Settings", "allow_partial_reservation"
+			)
+
+			available_serial_nos = []
+			if self.has_serial_no:
+				available_serial_nos = get_available_serial_nos_to_reserve(
+					self.item_code, self.warehouse, self.has_batch_no, ignore_sre=self.name
+				)
+
+				if not available_serial_nos:
+					msg = _("Stock not available for Item {0} in Warehouse {1}.").format(
+						frappe.bold(self.item_code), frappe.bold(self.warehouse)
+					)
+					frappe.throw(msg)
+
+			qty_to_be_reserved = 0
+			selected_batch_nos, selected_serial_nos = [], []
+			for entry in self.sb_entries:
+				entry.warehouse = self.warehouse
+
+				if self.has_serial_no:
+					entry.qty = 1
+
+					key = (
+						(entry.serial_no, self.warehouse, entry.batch_no)
+						if self.has_batch_no
+						else (entry.serial_no, self.warehouse)
+					)
+					if key not in available_serial_nos:
+						msg = _(
+							"Row #{0}: Serial No {1} for Item {2} is not available in {3} {4} or might be reserved in another {5}."
+						).format(
+							entry.idx,
+							frappe.bold(entry.serial_no),
+							frappe.bold(self.item_code),
+							_("Batch {0} and Warehouse").format(frappe.bold(entry.batch_no))
+							if self.has_batch_no
+							else _("Warehouse"),
+							frappe.bold(self.warehouse),
+							frappe.bold("Stock Reservation Entry"),
+						)
+
+						frappe.throw(msg)
+
+					if entry.serial_no in selected_serial_nos:
+						msg = _("Row #{0}: Serial No {1} is already selected.").format(
+							entry.idx, frappe.bold(entry.serial_no)
+						)
+						frappe.throw(msg)
+					else:
+						selected_serial_nos.append(entry.serial_no)
+
+				elif self.has_batch_no:
+					if cint(frappe.db.get_value("Batch", entry.batch_no, "disabled")):
+						msg = _(
+							"Row #{0}: Stock cannot be reserved for Item {1} against a disabled Batch {2}."
+						).format(
+							entry.idx, frappe.bold(self.item_code), frappe.bold(entry.batch_no)
+						)
+						frappe.throw(msg)
+
+					available_qty_to_reserve = get_available_qty_to_reserve(
+						self.item_code, self.warehouse, entry.batch_no, ignore_sre=self.name
+					)
+
+					if available_qty_to_reserve <= 0:
+						msg = _(
+							"Row #{0}: Stock not availabe to reserve for Item {1} against Batch {2} in Warehouse {3}."
+						).format(
+							entry.idx,
+							frappe.bold(self.item_code),
+							frappe.bold(entry.batch_no),
+							frappe.bold(self.warehouse),
+						)
+						frappe.throw(msg)
+
+					if entry.qty > available_qty_to_reserve:
+						if allow_partial_reservation:
+							entry.qty = available_qty_to_reserve
+							if self.get("_action") == "update_after_submit":
+								entry.db_update()
+						else:
+							msg = _(
+								"Row #{0}: Qty should be less than or equal to Available Qty to Reserve (Actual Qty - Reserved Qty) {1} for Iem {2} against Batch {3} in Warehouse {4}."
+							).format(
+								entry.idx,
+								frappe.bold(available_qty_to_reserve),
+								frappe.bold(self.item_code),
+								frappe.bold(entry.batch_no),
+								frappe.bold(self.warehouse),
+							)
+							frappe.throw(msg)
+
+					if entry.batch_no in selected_batch_nos:
+						msg = _("Row #{0}: Batch No {1} is already selected.").format(
+							entry.idx, frappe.bold(entry.batch_no)
+						)
+						frappe.throw(msg)
+					else:
+						selected_batch_nos.append(entry.batch_no)
+
+				qty_to_be_reserved += entry.qty
+
+			if not qty_to_be_reserved:
+				msg = _("Please select Serial/Batch Nos to reserve or change Reservation Based On to Qty.")
+				frappe.throw(msg)
+
+			# Should be called after validating Serial and Batch Nos.
+			self.validate_with_max_reserved_qty(qty_to_be_reserved)
+			self.db_set("reserved_qty", qty_to_be_reserved)
 
 	def update_reserved_qty_in_voucher(
 		self, reserved_qty_field: str = "stock_reserved_qty", update_modified: bool = True
@@ -100,45 +311,177 @@
 				update_modified=update_modified,
 			)
 
+	def update_reserved_qty_in_pick_list(
+		self, reserved_qty_field: str = "stock_reserved_qty", update_modified: bool = True
+	) -> None:
+		"""Updates total reserved qty in the Pick List."""
+
+		if self.against_pick_list and self.against_pick_list_item:
+			sre = frappe.qb.DocType("Stock Reservation Entry")
+			reserved_qty = (
+				frappe.qb.from_(sre)
+				.select(Sum(sre.reserved_qty))
+				.where(
+					(sre.docstatus == 1)
+					& (sre.against_pick_list == self.against_pick_list)
+					& (sre.against_pick_list_item == self.against_pick_list_item)
+				)
+			).run(as_list=True)[0][0] or 0
+
+			frappe.db.set_value(
+				"Pick List Item",
+				self.against_pick_list_item,
+				reserved_qty_field,
+				reserved_qty,
+				update_modified=update_modified,
+			)
+
+	def update_status(self, status: str = None, update_modified: bool = True) -> None:
+		"""Updates status based on Voucher Qty, Reserved Qty and Delivered Qty."""
+
+		if not status:
+			if self.docstatus == 2:
+				status = "Cancelled"
+			elif self.docstatus == 1:
+				if self.reserved_qty == self.delivered_qty:
+					status = "Delivered"
+				elif self.delivered_qty and self.delivered_qty < self.reserved_qty:
+					status = "Partially Delivered"
+				elif self.reserved_qty == self.voucher_qty:
+					status = "Reserved"
+				else:
+					status = "Partially Reserved"
+			else:
+				status = "Draft"
+
+		frappe.db.set_value(self.doctype, self.name, "status", status, update_modified=update_modified)
+
+	def can_be_updated(self) -> None:
+		"""Raises an exception if `Stock Reservation Entry` is not allowed to be updated."""
+
+		if self.status in ("Partially Delivered", "Delivered"):
+			msg = _(
+				"{0} {1} cannot be updated. If you need to make changes, we recommend canceling the existing entry and creating a new one."
+			).format(self.status, self.doctype)
+			frappe.throw(msg)
+
+		if self.against_pick_list:
+			msg = _(
+				"Stock Reservation Entry created against a Pick List cannot be updated. If you need to make changes, we recommend canceling the existing entry and creating a new one."
+			)
+			frappe.throw(msg)
+
+		if self.delivered_qty > 0:
+			msg = _("Stock Reservation Entry cannot be updated as it has been delivered.")
+			frappe.throw(msg)
+
+	def validate_with_max_reserved_qty(self, qty_to_be_reserved: float) -> None:
+		"""Validates `Reserved Qty` with `Max Reserved Qty`."""
+
+		self.db_set(
+			"available_qty",
+			get_available_qty_to_reserve(self.item_code, self.warehouse, ignore_sre=self.name),
+		)
+
+		total_reserved_qty = get_sre_reserved_qty_for_voucher_detail_no(
+			self.voucher_type, self.voucher_no, self.voucher_detail_no, ignore_sre=self.name
+		)
+
+		voucher_delivered_qty = 0
+		if self.voucher_type == "Sales Order":
+			delivered_qty, conversion_factor = frappe.db.get_value(
+				"Sales Order Item", self.voucher_detail_no, ["delivered_qty", "conversion_factor"]
+			)
+			voucher_delivered_qty = flt(delivered_qty) * flt(conversion_factor)
+
+		max_reserved_qty = min(
+			self.available_qty, (self.voucher_qty - voucher_delivered_qty - total_reserved_qty)
+		)
+
+		if max_reserved_qty <= 0 and self.voucher_type == "Sales Order":
+			msg = _("Item {0} is already delivered for Sales Order {1}.").format(
+				frappe.bold(self.item_code), frappe.bold(self.voucher_no)
+			)
+
+			if self.docstatus == 1:
+				self.cancel()
+				return frappe.msgprint(msg)
+			else:
+				frappe.throw(msg)
+
+		if qty_to_be_reserved > max_reserved_qty:
+			msg = """
+				Cannot reserve more than Max Reserved Qty {0} {1}.<br /><br />
+				The <b>Max Reserved Qty</b> is calculated as follows:<br />
+				<ul>
+					<li><b>Available Qty To Reserve</b> = (Actual Stock Qty - Reserved Stock Qty)</li>
+					<li><b>Voucher Qty</b> = Voucher Item Qty</li>
+					<li><b>Delivered Qty</b> = Qty delivered against the Voucher Item</li>
+					<li><b>Total Reserved Qty</b> = Qty reserved against the Voucher Item</li>
+					<li><b>Max Reserved Qty</b> = Minimum of (Available Qty To Reserve, (Voucher Qty - Delivered Qty - Total Reserved Qty))</li>
+				</ul>
+			""".format(
+				frappe.bold(max_reserved_qty), self.stock_uom
+			)
+			frappe.throw(msg)
+
+		if qty_to_be_reserved <= self.delivered_qty:
+			msg = _("Reserved Qty should be greater than Delivered Qty.")
+			frappe.throw(msg)
+
 
 def validate_stock_reservation_settings(voucher: object) -> None:
 	"""Raises an exception if `Stock Reservation` is not enabled or `Voucher Type` is not allowed."""
 
 	if not frappe.db.get_single_value("Stock Settings", "enable_stock_reservation"):
-		frappe.throw(
-			_("Please enable {0} in the {1}.").format(
-				frappe.bold("Stock Reservation"), frappe.bold("Stock Settings")
-			)
+		msg = _("Please enable {0} in the {1}.").format(
+			frappe.bold("Stock Reservation"), frappe.bold("Stock Settings")
 		)
+		frappe.throw(msg)
 
 	# Voucher types allowed for stock reservation
 	allowed_voucher_types = ["Sales Order"]
 
 	if voucher.doctype not in allowed_voucher_types:
-		frappe.throw(
-			_("Stock Reservation can only be created against {0}.").format(", ".join(allowed_voucher_types))
+		msg = _("Stock Reservation can only be created against {0}.").format(
+			", ".join(allowed_voucher_types)
 		)
+		frappe.throw(msg)
 
 
-def get_available_qty_to_reserve(item_code: str, warehouse: str) -> float:
-	"""Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item and Warehouse combination."""
+def get_available_qty_to_reserve(
+	item_code: str, warehouse: str, batch_no: str = None, ignore_sre=None
+) -> float:
+	"""Returns `Available Qty to Reserve (Actual Qty - Reserved Qty)` for Item, Warehouse and Batch combination."""
 
+	from erpnext.stock.doctype.batch.batch import get_batch_qty
 	from erpnext.stock.utils import get_stock_balance
 
+	if batch_no:
+		return get_batch_qty(
+			item_code=item_code, warehouse=warehouse, batch_no=batch_no, ignore_voucher_nos=[ignore_sre]
+		)
+
 	available_qty = get_stock_balance(item_code, warehouse)
 
 	if available_qty:
 		sre = frappe.qb.DocType("Stock Reservation Entry")
-		reserved_qty = (
+		query = (
 			frappe.qb.from_(sre)
 			.select(Sum(sre.reserved_qty - sre.delivered_qty))
 			.where(
 				(sre.docstatus == 1)
 				& (sre.item_code == item_code)
 				& (sre.warehouse == warehouse)
+				& (sre.reserved_qty >= sre.delivered_qty)
 				& (sre.status.notin(["Delivered", "Cancelled"]))
 			)
-		).run()[0][0] or 0.0
+		)
+
+		if ignore_sre:
+			query = query.where(sre.name != ignore_sre)
+
+		reserved_qty = query.run()[0][0] or 0.0
 
 		if reserved_qty:
 			return available_qty - reserved_qty
@@ -146,93 +489,97 @@
 	return available_qty
 
 
-def get_stock_reservation_entries_for_voucher(
-	voucher_type: str, voucher_no: str, voucher_detail_no: str = None, fields: list[str] = None
-) -> list[dict]:
-	"""Returns list of Stock Reservation Entries against a Voucher."""
+def get_available_serial_nos_to_reserve(
+	item_code: str, warehouse: str, has_batch_no: bool = False, ignore_sre=None
+) -> list[tuple]:
+	"""Returns Available Serial Nos to Reserve (Available Serial Nos - Reserved Serial Nos)` for Item, Warehouse and Batch combination."""
 
-	if not fields or not isinstance(fields, list):
-		fields = [
-			"name",
-			"item_code",
-			"warehouse",
-			"voucher_detail_no",
-			"reserved_qty",
-			"delivered_qty",
-			"stock_uom",
-		]
-
-	sre = frappe.qb.DocType("Stock Reservation Entry")
-	query = (
-		frappe.qb.from_(sre)
-		.where(
-			(sre.docstatus == 1)
-			& (sre.voucher_type == voucher_type)
-			& (sre.voucher_no == voucher_no)
-			& (sre.status.notin(["Delivered", "Cancelled"]))
-		)
-		.orderby(sre.creation)
+	from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
+		get_available_serial_nos,
 	)
 
-	for field in fields:
-		query = query.select(sre[field])
+	available_serial_nos = get_available_serial_nos(
+		frappe._dict(
+			{
+				"item_code": item_code,
+				"warehouse": warehouse,
+				"has_batch_no": has_batch_no,
+				"ignore_voucher_nos": [ignore_sre],
+			}
+		)
+	)
 
-	if voucher_detail_no:
-		query = query.where(sre.voucher_detail_no == voucher_detail_no)
+	available_serial_nos_list = []
+	if available_serial_nos:
+		available_serial_nos_list = [tuple(d.values()) for d in available_serial_nos]
 
-	return query.run(as_dict=True)
-
-
-def get_sre_reserved_qty_details_for_item_and_warehouse(
-	item_code_list: list, warehouse_list: list
-) -> dict:
-	"""Returns a dict like {("item_code", "warehouse"): "reserved_qty", ... }."""
-
-	sre_details = {}
-
-	if item_code_list and warehouse_list:
 		sre = frappe.qb.DocType("Stock Reservation Entry")
-		sre_data = (
+		sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+		query = (
 			frappe.qb.from_(sre)
-			.select(
-				sre.item_code,
-				sre.warehouse,
-				Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"),
-			)
-			.where(
-				(sre.docstatus == 1)
-				& (sre.item_code.isin(item_code_list))
-				& (sre.warehouse.isin(warehouse_list))
-				& (sre.status.notin(["Delivered", "Cancelled"]))
-			)
-			.groupby(sre.item_code, sre.warehouse)
-		).run(as_dict=True)
-
-		if sre_data:
-			sre_details = {(d["item_code"], d["warehouse"]): d["reserved_qty"] for d in sre_data}
-
-	return sre_details
-
-
-def get_sre_reserved_qty_for_item_and_warehouse(item_code: str, warehouse: str) -> float:
-	"""Returns `Reserved Qty` for Item and Warehouse combination."""
-
-	reserved_qty = 0.0
-
-	if item_code and warehouse:
-		sre = frappe.qb.DocType("Stock Reservation Entry")
-		return (
-			frappe.qb.from_(sre)
-			.select(Sum(sre.reserved_qty - sre.delivered_qty))
+			.left_join(sb_entry)
+			.on(sre.name == sb_entry.parent)
+			.select(sb_entry.serial_no, sre.warehouse)
 			.where(
 				(sre.docstatus == 1)
 				& (sre.item_code == item_code)
 				& (sre.warehouse == warehouse)
+				& (sre.reserved_qty >= sre.delivered_qty)
 				& (sre.status.notin(["Delivered", "Cancelled"]))
+				& (sre.reservation_based_on == "Serial and Batch")
 			)
-		).run(as_list=True)[0][0] or 0.0
+		)
 
-	return reserved_qty
+		if has_batch_no:
+			query = query.select(sb_entry.batch_no)
+
+		if ignore_sre:
+			query = query.where(sre.name != ignore_sre)
+
+		reserved_serial_nos = query.run()
+
+		if reserved_serial_nos:
+			return list(set(available_serial_nos_list) - set(reserved_serial_nos))
+
+	return available_serial_nos_list
+
+
+def get_sre_reserved_qty_for_item_and_warehouse(
+	item_code: str | list, warehouse: str | list = None
+) -> float | dict:
+	"""Returns `Reserved Qty` for Item and Warehouse combination OR a dict like {("item_code", "warehouse"): "reserved_qty", ... }."""
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	query = (
+		frappe.qb.from_(sre)
+		.select(
+			sre.item_code,
+			sre.warehouse,
+			Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"),
+		)
+		.where((sre.docstatus == 1) & (sre.status.notin(["Delivered", "Cancelled"])))
+		.groupby(sre.item_code, sre.warehouse)
+	)
+
+	query = (
+		query.where(sre.item_code.isin(item_code))
+		if isinstance(item_code, list)
+		else query.where(sre.item_code == item_code)
+	)
+
+	if warehouse:
+		query = (
+			query.where(sre.warehouse.isin(warehouse))
+			if isinstance(warehouse, list)
+			else query.where(sre.warehouse == warehouse)
+		)
+
+	data = query.run(as_dict=True)
+
+	if isinstance(item_code, str) and isinstance(warehouse, str):
+		return data[0]["reserved_qty"] if data else 0.0
+	else:
+		return {(d["item_code"], d["warehouse"]): d["reserved_qty"] for d in data} if data else {}
 
 
 def get_sre_reserved_qty_details_for_voucher(voucher_type: str, voucher_no: str) -> dict:
@@ -257,15 +604,44 @@
 	return frappe._dict(data)
 
 
-def get_sre_reserved_qty_details_for_voucher_detail_no(
-	voucher_type: str, voucher_no: str, voucher_detail_no: str
+def get_sre_reserved_warehouses_for_voucher(
+	voucher_type: str, voucher_no: str, voucher_detail_no: str = None
 ) -> list:
-	"""Returns a list like ["warehouse", "reserved_qty"]."""
+	"""Returns a list of warehouses where the stock is reserved for the provided voucher."""
 
 	sre = frappe.qb.DocType("Stock Reservation Entry")
-	reserved_qty_details = (
+	query = (
 		frappe.qb.from_(sre)
-		.select(sre.warehouse, (Sum(sre.reserved_qty) - Sum(sre.delivered_qty)))
+		.select(sre.warehouse)
+		.distinct()
+		.where(
+			(sre.docstatus == 1)
+			& (sre.voucher_type == voucher_type)
+			& (sre.voucher_no == voucher_no)
+			& (sre.status.notin(["Delivered", "Cancelled"]))
+		)
+		.orderby(sre.creation)
+	)
+
+	if voucher_detail_no:
+		query = query.where(sre.voucher_detail_no == voucher_detail_no)
+
+	warehouses = query.run(as_list=True)
+
+	return [d[0] for d in warehouses] if warehouses else []
+
+
+def get_sre_reserved_qty_for_voucher_detail_no(
+	voucher_type: str, voucher_no: str, voucher_detail_no: str, ignore_sre=None
+) -> float:
+	"""Returns `Reserved Qty` against the Voucher."""
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	query = (
+		frappe.qb.from_(sre)
+		.select(
+			(Sum(sre.reserved_qty) - Sum(sre.delivered_qty)),
+		)
 		.where(
 			(sre.docstatus == 1)
 			& (sre.voucher_type == voucher_type)
@@ -273,40 +649,366 @@
 			& (sre.voucher_detail_no == voucher_detail_no)
 			& (sre.status.notin(["Delivered", "Cancelled"]))
 		)
+	)
+
+	if ignore_sre:
+		query = query.where(sre.name != ignore_sre)
+
+	reserved_qty = query.run(as_list=True)
+
+	return flt(reserved_qty[0][0])
+
+
+def get_sre_details_for_voucher(voucher_type: str, voucher_no: str) -> list[dict]:
+	"""Returns a list of SREs for the provided voucher."""
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	return (
+		frappe.qb.from_(sre)
+		.select(
+			sre.name,
+			sre.item_code,
+			sre.warehouse,
+			sre.voucher_type,
+			sre.voucher_no,
+			sre.voucher_detail_no,
+			(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"),
+			sre.has_serial_no,
+			sre.has_batch_no,
+			sre.reservation_based_on,
+		)
+		.where(
+			(sre.docstatus == 1)
+			& (sre.voucher_type == voucher_type)
+			& (sre.voucher_no == voucher_no)
+			& (sre.reserved_qty > sre.delivered_qty)
+			& (sre.status.notin(["Delivered", "Cancelled"]))
+		)
 		.orderby(sre.creation)
-		.groupby(sre.warehouse)
-	).run(as_list=True)
+	).run(as_dict=True)
 
-	if reserved_qty_details:
-		return reserved_qty_details[0]
 
-	return reserved_qty_details
+def get_serial_batch_entries_for_voucher(sre_name: str) -> list[dict]:
+	"""Returns a list of `Serial and Batch Entries` for the provided voucher."""
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+
+	return (
+		frappe.qb.from_(sre)
+		.inner_join(sb_entry)
+		.on(sre.name == sb_entry.parent)
+		.select(
+			sb_entry.serial_no,
+			sb_entry.batch_no,
+			(sb_entry.qty - sb_entry.delivered_qty).as_("qty"),
+		)
+		.where(
+			(sre.docstatus == 1) & (sre.name == sre_name) & (sre.status.notin(["Delivered", "Cancelled"]))
+		)
+		.where(sb_entry.qty > sb_entry.delivered_qty)
+		.orderby(sb_entry.creation)
+	).run(as_dict=True)
+
+
+def get_ssb_bundle_for_voucher(sre: dict) -> object | None:
+	"""Returns a new `Serial and Batch Bundle` against the provided SRE."""
+
+	sb_entries = get_serial_batch_entries_for_voucher(sre["name"])
+
+	if sb_entries:
+		bundle = frappe.new_doc("Serial and Batch Bundle")
+		bundle.type_of_transaction = "Outward"
+		bundle.voucher_type = "Delivery Note"
+
+		for field in ("item_code", "warehouse", "has_serial_no", "has_batch_no"):
+			setattr(bundle, field, sre[field])
+
+		for sb_entry in sb_entries:
+			bundle.append("entries", sb_entry)
+
+		bundle.save()
+
+		return bundle.name
 
 
 def has_reserved_stock(voucher_type: str, voucher_no: str, voucher_detail_no: str = None) -> bool:
 	"""Returns True if there is any Stock Reservation Entry for the given voucher."""
 
 	if get_stock_reservation_entries_for_voucher(
-		voucher_type, voucher_no, voucher_detail_no, fields=["name"]
+		voucher_type, voucher_no, voucher_detail_no, fields=["name"], ignore_status=True
 	):
 		return True
 
 	return False
 
 
-@frappe.whitelist()
-def cancel_stock_reservation_entries(
-	voucher_type: str, voucher_no: str, voucher_detail_no: str = None, notify: bool = True
+def create_stock_reservation_entries_for_so_items(
+	so: object,
+	items_details: list[dict] = None,
+	against_pick_list: bool = False,
+	notify=True,
 ) -> None:
-	"""Cancel Stock Reservation Entries for the given voucher."""
+	"""Creates Stock Reservation Entries for Sales Order Items."""
 
-	sre_list = get_stock_reservation_entries_for_voucher(
-		voucher_type, voucher_no, voucher_detail_no, fields=["name"]
+	from erpnext.selling.doctype.sales_order.sales_order import get_unreserved_qty
+
+	if not against_pick_list and (
+		so.get("_action") == "submit"
+		and so.set_warehouse
+		and cint(frappe.get_cached_value("Warehouse", so.set_warehouse, "is_group"))
+	):
+		return frappe.msgprint(
+			_("Stock cannot be reserved in the group warehouse {0}.").format(frappe.bold(so.set_warehouse))
+		)
+
+	validate_stock_reservation_settings(so)
+
+	allow_partial_reservation = frappe.db.get_single_value(
+		"Stock Settings", "allow_partial_reservation"
 	)
 
+	items = []
+	if items_details:
+		for item in items_details:
+			so_item = frappe.get_doc(
+				"Sales Order Item", item.get("sales_order_item") if against_pick_list else item.get("name")
+			)
+			so_item.reserve_stock = 1
+			so_item.warehouse = item.get("warehouse")
+			so_item.qty_to_reserve = (
+				item.get("picked_qty") - item.get("stock_reserved_qty", 0)
+				if against_pick_list
+				else (flt(item.get("qty_to_reserve")) * flt(so_item.conversion_factor, 1))
+			)
+
+			if against_pick_list:
+				so_item.pick_list = item.get("parent")
+				so_item.pick_list_item = item.get("name")
+				so_item.pick_list_sbb = item.get("serial_and_batch_bundle")
+
+			items.append(so_item)
+
+	sre_count = 0
+	reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", so.name)
+
+	for item in items if items_details else so.get("items"):
+		# Skip if `Reserved Stock` is not checked for the item.
+		if not item.get("reserve_stock"):
+			continue
+
+		# Stock should be reserved from the Pick List if has Picked Qty.
+		if not against_pick_list and flt(item.picked_qty) > 0:
+			frappe.throw(
+				_(
+					"Row #{0}: Item {1} has been picked, please create a Stock Reservation from the Pick List."
+				).format(item.idx, frappe.bold(item.item_code))
+			)
+
+		is_stock_item, has_serial_no, has_batch_no = frappe.get_cached_value(
+			"Item", item.item_code, ["is_stock_item", "has_serial_no", "has_batch_no"]
+		)
+
+		# Skip if Non-Stock Item.
+		if not is_stock_item:
+			frappe.msgprint(
+				_("Row #{0}: Stock cannot be reserved for a non-stock Item {1}").format(
+					item.idx, frappe.bold(item.item_code)
+				),
+				title=_("Stock Reservation"),
+				indicator="yellow",
+			)
+			item.db_set("reserve_stock", 0)
+			continue
+
+		# Skip if Group Warehouse.
+		if frappe.get_cached_value("Warehouse", item.warehouse, "is_group"):
+			frappe.msgprint(
+				_("Row #{0}: Stock cannot be reserved in group warehouse {1}.").format(
+					item.idx, frappe.bold(item.warehouse)
+				),
+				title=_("Stock Reservation"),
+				indicator="yellow",
+			)
+			continue
+
+		unreserved_qty = get_unreserved_qty(item, reserved_qty_details)
+
+		# Stock is already reserved for the item, notify the user and skip the item.
+		if unreserved_qty <= 0:
+			frappe.msgprint(
+				_("Row #{0}: Stock is already reserved for the Item {1}.").format(
+					item.idx, frappe.bold(item.item_code)
+				),
+				title=_("Stock Reservation"),
+				indicator="yellow",
+			)
+			continue
+
+		available_qty_to_reserve = get_available_qty_to_reserve(item.item_code, item.warehouse)
+
+		# No stock available to reserve, notify the user and skip the item.
+		if available_qty_to_reserve <= 0:
+			frappe.msgprint(
+				_("Row #{0}: No available stock to reserve for the Item {1} in Warehouse {2}.").format(
+					item.idx, frappe.bold(item.item_code), frappe.bold(item.warehouse)
+				),
+				title=_("Stock Reservation"),
+				indicator="orange",
+			)
+			continue
+
+		# The quantity which can be reserved.
+		qty_to_be_reserved = min(unreserved_qty, available_qty_to_reserve)
+
+		if hasattr(item, "qty_to_reserve"):
+			if item.qty_to_reserve <= 0:
+				frappe.msgprint(
+					_("Row #{0}: Quantity to reserve for the Item {1} should be greater than 0.").format(
+						item.idx, frappe.bold(item.item_code)
+					),
+					title=_("Stock Reservation"),
+					indicator="orange",
+				)
+				continue
+			else:
+				qty_to_be_reserved = min(qty_to_be_reserved, item.qty_to_reserve)
+
+		# Partial Reservation
+		if qty_to_be_reserved < unreserved_qty:
+			if not item.get("qty_to_reserve") or qty_to_be_reserved < flt(item.get("qty_to_reserve")):
+				msg = _("Row #{0}: Only {1} available to reserve for the Item {2}").format(
+					item.idx,
+					frappe.bold(str(qty_to_be_reserved / item.conversion_factor) + " " + item.uom),
+					frappe.bold(item.item_code),
+				)
+				frappe.msgprint(msg, title=_("Stock Reservation"), indicator="orange")
+
+			# Skip the item if `Partial Reservation` is disabled in the Stock Settings.
+			if not allow_partial_reservation:
+				if qty_to_be_reserved == flt(item.get("qty_to_reserve")):
+					msg = _("Enable Allow Partial Reservation in the Stock Settings to reserve partial stock.")
+					frappe.msgprint(msg, title=_("Partial Stock Reservation"), indicator="yellow")
+
+				continue
+
+		sre = frappe.new_doc("Stock Reservation Entry")
+
+		sre.item_code = item.item_code
+		sre.warehouse = item.warehouse
+		sre.has_serial_no = has_serial_no
+		sre.has_batch_no = has_batch_no
+		sre.voucher_type = so.doctype
+		sre.voucher_no = so.name
+		sre.voucher_detail_no = item.name
+		sre.available_qty = available_qty_to_reserve
+		sre.voucher_qty = item.stock_qty
+		sre.reserved_qty = qty_to_be_reserved
+		sre.company = so.company
+		sre.stock_uom = item.stock_uom
+		sre.project = so.project
+
+		if against_pick_list:
+			sre.against_pick_list = item.pick_list
+			sre.against_pick_list_item = item.pick_list_item
+
+			if item.pick_list_sbb:
+				sbb = frappe.get_doc("Serial and Batch Bundle", item.pick_list_sbb)
+				sre.reservation_based_on = "Serial and Batch"
+				for entry in sbb.entries:
+					sre.append(
+						"sb_entries",
+						{
+							"serial_no": entry.serial_no,
+							"batch_no": entry.batch_no,
+							"qty": 1 if has_serial_no else abs(entry.qty),
+							"warehouse": entry.warehouse,
+						},
+					)
+
+		sre.save()
+		sre.submit()
+
+		sre_count += 1
+
+	if sre_count and notify:
+		frappe.msgprint(_("Stock Reservation Entries Created"), alert=True, indicator="green")
+
+
+def cancel_stock_reservation_entries(
+	voucher_type: str = None,
+	voucher_no: str = None,
+	voucher_detail_no: str = None,
+	against_pick_list: str = None,
+	sre_list: list[dict] = None,
+	notify: bool = True,
+) -> None:
+	"""Cancel Stock Reservation Entries."""
+
+	if not sre_list and against_pick_list:
+		sre = frappe.qb.DocType("Stock Reservation Entry")
+		sre_list = (
+			frappe.qb.from_(sre)
+			.select(sre.name)
+			.where(
+				(sre.docstatus == 1)
+				& (sre.against_pick_list == against_pick_list)
+				& (sre.status.notin(["Delivered", "Cancelled"]))
+			)
+			.orderby(sre.creation)
+		).run(as_dict=True)
+
+	elif not sre_list and (voucher_type and voucher_no):
+		sre_list = get_stock_reservation_entries_for_voucher(
+			voucher_type, voucher_no, voucher_detail_no, fields=["name"]
+		)
+
 	if sre_list:
 		for sre in sre_list:
-			frappe.get_doc("Stock Reservation Entry", sre.name).cancel()
+			frappe.get_doc("Stock Reservation Entry", sre["name"]).cancel()
 
 		if notify:
-			frappe.msgprint(_("Stock Reservation Entries Cancelled"), alert=True, indicator="red")
+			msg = _("Stock Reservation Entries Cancelled")
+			frappe.msgprint(msg, alert=True, indicator="red")
+
+
+@frappe.whitelist()
+def get_stock_reservation_entries_for_voucher(
+	voucher_type: str,
+	voucher_no: str,
+	voucher_detail_no: str = None,
+	fields: list[str] = None,
+	ignore_status: bool = False,
+) -> list[dict]:
+	"""Returns list of Stock Reservation Entries against a Voucher."""
+
+	if not fields or not isinstance(fields, list):
+		fields = [
+			"name",
+			"item_code",
+			"warehouse",
+			"voucher_detail_no",
+			"reserved_qty",
+			"delivered_qty",
+			"stock_uom",
+		]
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	query = (
+		frappe.qb.from_(sre)
+		.where(
+			(sre.docstatus == 1) & (sre.voucher_type == voucher_type) & (sre.voucher_no == voucher_no)
+		)
+		.orderby(sre.creation)
+	)
+
+	for field in fields:
+		query = query.select(sre[field])
+
+	if voucher_detail_no:
+		query = query.where(sre.voucher_detail_no == voucher_detail_no)
+
+	if ignore_status:
+		query = query.where(sre.status.notin(["Delivered", "Cancelled"]))
+
+	return query.run(as_dict=True)
diff --git a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
index dff407f..1168a4e 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/test_stock_reservation_entry.py
@@ -1,23 +1,38 @@
 # Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
 # See license.txt
 
+from random import randint
+
 import frappe
 from frappe.tests.utils import FrappeTestCase, change_settings
 
+from erpnext.selling.doctype.sales_order.sales_order import create_pick_list, make_delivery_note
 from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+from erpnext.stock.doctype.item.test_item import make_item
 from erpnext.stock.doctype.stock_entry.stock_entry import StockEntry
 from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
+from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
+	cancel_stock_reservation_entries,
+	get_sre_reserved_qty_details_for_voucher,
+	get_stock_reservation_entries_for_voucher,
+	has_reserved_stock,
+)
 from erpnext.stock.utils import get_stock_balance
 
 
 class TestStockReservationEntry(FrappeTestCase):
 	def setUp(self) -> None:
-		self.items = create_items()
-		create_material_receipt(self.items)
+		self.warehouse = "_Test Warehouse - _TC"
+		self.sr_item = make_item(properties={"is_stock_item": 1, "valuation_rate": 100})
+		create_material_receipt(
+			items={self.sr_item.name: self.sr_item}, warehouse=self.warehouse, qty=100
+		)
 
 	def tearDown(self) -> None:
+		cancel_all_stock_reservation_entries()
 		return super().tearDown()
 
+	@change_settings("Stock Settings", {"allow_negative_stock": 0})
 	def test_validate_stock_reservation_settings(self) -> None:
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
 			validate_stock_reservation_settings,
@@ -47,28 +62,29 @@
 			get_available_qty_to_reserve,
 		)
 
-		item_code, warehouse = "SR Item 1", "_Test Warehouse - _TC"
-
 		# Case - 1: When `Reserved Qty` is `0`, Available Qty to Reserve = Actual Qty
-		cancel_all_stock_reservation_entries()
-		available_qty_to_reserve = get_available_qty_to_reserve(item_code, warehouse)
-		expected_available_qty_to_reserve = get_stock_balance(item_code, warehouse)
+		available_qty_to_reserve = get_available_qty_to_reserve(self.sr_item.name, self.warehouse)
+		expected_available_qty_to_reserve = get_stock_balance(self.sr_item.name, self.warehouse)
 
 		self.assertEqual(available_qty_to_reserve, expected_available_qty_to_reserve)
 
 		# Case - 2: When `Reserved Qty` is `> 0`, Available Qty to Reserve = Actual Qty - Reserved Qty
 		sre = make_stock_reservation_entry(
-			item_code=item_code,
-			warehouse=warehouse,
+			item_code=self.sr_item.name,
+			warehouse=self.warehouse,
 			ignore_validate=True,
 		)
-		available_qty_to_reserve = get_available_qty_to_reserve(item_code, warehouse)
-		expected_available_qty_to_reserve = get_stock_balance(item_code, warehouse) - sre.reserved_qty
+		available_qty_to_reserve = get_available_qty_to_reserve(self.sr_item.name, self.warehouse)
+		expected_available_qty_to_reserve = (
+			get_stock_balance(self.sr_item.name, self.warehouse) - sre.reserved_qty
+		)
 
 		self.assertEqual(available_qty_to_reserve, expected_available_qty_to_reserve)
 
 	def test_update_status(self) -> None:
 		sre = make_stock_reservation_entry(
+			item_code=self.sr_item.name,
+			warehouse=self.warehouse,
 			reserved_qty=30,
 			ignore_validate=True,
 			do_not_submit=True,
@@ -109,14 +125,12 @@
 		sre.load_from_db()
 		self.assertEqual(sre.status, "Cancelled")
 
-	@change_settings("Stock Settings", {"enable_stock_reservation": 1})
+	@change_settings("Stock Settings", {"allow_negative_stock": 0, "enable_stock_reservation": 1})
 	def test_update_reserved_qty_in_voucher(self) -> None:
-		item_code, warehouse = "SR Item 1", "_Test Warehouse - _TC"
-
 		# Step - 1: Create a `Sales Order`
 		so = make_sales_order(
-			item_code=item_code,
-			warehouse=warehouse,
+			item_code=self.sr_item.name,
+			warehouse=self.warehouse,
 			qty=50,
 			rate=100,
 			do_not_submit=True,
@@ -128,8 +142,8 @@
 
 		# Step - 2: Create a `Stock Reservation Entry[1]` for the `Sales Order Item`
 		sre1 = make_stock_reservation_entry(
-			item_code=item_code,
-			warehouse=warehouse,
+			item_code=self.sr_item.name,
+			warehouse=self.warehouse,
 			voucher_type="Sales Order",
 			voucher_no=so.name,
 			voucher_detail_no=so.items[0].name,
@@ -143,8 +157,8 @@
 
 		# Step - 3: Create a `Stock Reservation Entry[2]` for the `Sales Order Item`
 		sre2 = make_stock_reservation_entry(
-			item_code=item_code,
-			warehouse=warehouse,
+			item_code=self.sr_item.name,
+			warehouse=self.warehouse,
 			voucher_type="Sales Order",
 			voucher_no=so.name,
 			voucher_detail_no=so.items[0].name,
@@ -163,26 +177,32 @@
 		self.assertEqual(sre1.status, "Cancelled")
 		self.assertEqual(so.items[0].stock_reserved_qty, sre2.reserved_qty)
 
-		# Step - 5: Cancel `Stock Reservation Entry[2]`
+		# Step - 5: Update `Stock Reservation Entry[2]` Reserved Qty
+		sre2.reserved_qty += sre1.reserved_qty
+		sre2.save()
+		so.load_from_db()
+		sre1.load_from_db()
+		self.assertEqual(sre2.status, "Reserved")
+		self.assertEqual(so.items[0].stock_reserved_qty, sre2.reserved_qty)
+
+		# Step - 6: Cancel `Stock Reservation Entry[2]`
 		sre2.cancel()
 		so.load_from_db()
 		sre2.load_from_db()
 		self.assertEqual(sre1.status, "Cancelled")
 		self.assertEqual(so.items[0].stock_reserved_qty, 0)
 
-	@change_settings("Stock Settings", {"enable_stock_reservation": 1})
+	@change_settings("Stock Settings", {"allow_negative_stock": 0, "enable_stock_reservation": 1})
 	def test_cant_consume_reserved_stock(self) -> None:
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
 			cancel_stock_reservation_entries,
 		)
 		from erpnext.stock.stock_ledger import NegativeStockError
 
-		item_code, warehouse = "SR Item 1", "_Test Warehouse - _TC"
-
 		# Step - 1: Create a `Sales Order`
 		so = make_sales_order(
-			item_code=item_code,
-			warehouse=warehouse,
+			item_code=self.sr_item.name,
+			warehouse=self.warehouse,
 			qty=50,
 			rate=100,
 			do_not_submit=True,
@@ -192,13 +212,13 @@
 		so.save()
 		so.submit()
 
-		actual_qty = get_stock_balance(item_code, warehouse)
+		actual_qty = get_stock_balance(self.sr_item.name, self.warehouse)
 
 		# Step - 2: Try to consume (Transfer/Issue/Deliver) the Available Qty via Stock Entry or Delivery Note, should throw `NegativeStockError`.
 		se = make_stock_entry(
-			item_code=item_code,
+			item_code=self.sr_item.name,
 			qty=actual_qty,
-			from_warehouse=warehouse,
+			from_warehouse=self.warehouse,
 			rate=100,
 			purpose="Material Issue",
 			do_not_submit=True,
@@ -210,9 +230,9 @@
 		cancel_stock_reservation_entries(so.doctype, so.name)
 
 		se = make_stock_entry(
-			item_code=item_code,
+			item_code=self.sr_item.name,
 			qty=actual_qty,
-			from_warehouse=warehouse,
+			from_warehouse=self.warehouse,
 			rate=100,
 			purpose="Material Issue",
 			do_not_submit=True,
@@ -220,52 +240,369 @@
 		se.submit()
 		se.cancel()
 
+	@change_settings(
+		"Stock Settings",
+		{
+			"allow_negative_stock": 0,
+			"enable_stock_reservation": 1,
+			"auto_reserve_serial_and_batch": 0,
+			"pick_serial_and_batch_based_on": "FIFO",
+			"auto_create_serial_and_batch_bundle_for_outward": 1,
+		},
+	)
+	def test_stock_reservation_against_sales_order(self) -> None:
+		items_details = create_items()
+		se = create_material_receipt(items_details, self.warehouse, qty=10)
+
+		item_list = []
+		for item_code, properties in items_details.items():
+			item_list.append(
+				{
+					"item_code": item_code,
+					"warehouse": self.warehouse,
+					"qty": randint(11, 100),
+					"uom": properties.stock_uom,
+					"rate": randint(10, 400),
+				}
+			)
+
+		so = make_sales_order(
+			item_list=item_list,
+			warehouse=self.warehouse,
+		)
+
+		# Test - 1: Stock should not be reserved if the Available Qty to Reserve is less than the Ordered Qty and Partial Reservation is disabled in Stock Settings.
+		with change_settings("Stock Settings", {"allow_partial_reservation": 0}):
+			so.create_stock_reservation_entries()
+			self.assertFalse(has_reserved_stock("Sales Order", so.name))
+
+		# Test - 2: Stock should be Partially Reserved if the Partial Reservation is enabled in Stock Settings.
+		with change_settings("Stock Settings", {"allow_partial_reservation": 1}):
+			so.create_stock_reservation_entries()
+			so.load_from_db()
+			self.assertTrue(has_reserved_stock("Sales Order", so.name))
+
+			for item in so.items:
+				sre_details = get_stock_reservation_entries_for_voucher(
+					"Sales Order", so.name, item.name, fields=["reserved_qty", "status"]
+				)[0]
+				self.assertEqual(item.stock_reserved_qty, sre_details.reserved_qty)
+				self.assertEqual(sre_details.status, "Partially Reserved")
+
+			se.cancel()
+
+			# Test - 3: Stock should be fully Reserved if the Available Qty to Reserve is greater than the Un-reserved Qty.
+			create_material_receipt(items_details, self.warehouse, qty=110)
+			so.create_stock_reservation_entries()
+			so.load_from_db()
+
+			reserved_qty_details = get_sre_reserved_qty_details_for_voucher("Sales Order", so.name)
+			for item in so.items:
+				reserved_qty = reserved_qty_details[item.name]
+				self.assertEqual(item.stock_reserved_qty, reserved_qty)
+				self.assertEqual(item.stock_qty, item.stock_reserved_qty)
+
+			# Test - 4: Stock should get unreserved on cancellation of Stock Reservation Entries.
+			cancel_stock_reservation_entries("Sales Order", so.name)
+			so.load_from_db()
+			self.assertFalse(has_reserved_stock("Sales Order", so.name))
+
+			for item in so.items:
+				self.assertEqual(item.stock_reserved_qty, 0)
+
+			# Test - 5: Re-reserve the stock.
+			so.create_stock_reservation_entries()
+			self.assertTrue(has_reserved_stock("Sales Order", so.name))
+
+			# Test - 6: Stock should get unreserved on cancellation of Sales Order.
+			so.cancel()
+			so.load_from_db()
+			self.assertFalse(has_reserved_stock("Sales Order", so.name))
+
+			for item in so.items:
+				self.assertEqual(item.stock_reserved_qty, 0)
+
+			# Create Sales Order and Reserve Stock.
+			so = make_sales_order(
+				item_list=item_list,
+				warehouse=self.warehouse,
+			)
+			so.create_stock_reservation_entries()
+
+			# Test - 7: Partial Delivery against Sales Order.
+			dn1 = make_delivery_note(so.name)
+
+			for item in dn1.items:
+				item.qty = randint(1, 10)
+
+			dn1.save()
+			dn1.submit()
+
+			for item in so.items:
+				sre_details = get_stock_reservation_entries_for_voucher(
+					"Sales Order", so.name, item.name, fields=["delivered_qty", "status"]
+				)[0]
+				self.assertGreater(sre_details.delivered_qty, 0)
+				self.assertEqual(sre_details.status, "Partially Delivered")
+
+			# Test - 8: Over Delivery against Sales Order, SRE Delivered Qty should not be greater than the SRE Reserved Qty.
+			with change_settings("Stock Settings", {"over_delivery_receipt_allowance": 100}):
+				dn2 = make_delivery_note(so.name)
+
+				for item in dn2.items:
+					item.qty += randint(1, 10)
+
+				dn2.save()
+				dn2.submit()
+
+			for item in so.items:
+				sre_details = get_stock_reservation_entries_for_voucher(
+					"Sales Order",
+					so.name,
+					item.name,
+					fields=["reserved_qty", "delivered_qty"],
+					ignore_status=True,
+				)
+
+				for sre_detail in sre_details:
+					self.assertEqual(sre_detail.reserved_qty, sre_detail.delivered_qty)
+
+	@change_settings(
+		"Stock Settings",
+		{
+			"allow_negative_stock": 0,
+			"enable_stock_reservation": 1,
+			"auto_reserve_serial_and_batch": 1,
+			"pick_serial_and_batch_based_on": "FIFO",
+		},
+	)
+	def test_auto_reserve_serial_and_batch(self) -> None:
+		items_details = create_items()
+		create_material_receipt(items_details, self.warehouse, qty=100)
+
+		item_list = []
+		for item_code, properties in items_details.items():
+			item_list.append(
+				{
+					"item_code": item_code,
+					"warehouse": self.warehouse,
+					"qty": randint(11, 100),
+					"uom": properties.stock_uom,
+					"rate": randint(10, 400),
+				}
+			)
+
+		so = make_sales_order(
+			item_list=item_list,
+			warehouse=self.warehouse,
+		)
+		so.create_stock_reservation_entries()
+		so.load_from_db()
+
+		for item in so.items:
+			sre_details = get_stock_reservation_entries_for_voucher(
+				"Sales Order", so.name, item.name, fields=["status", "reserved_qty"]
+			)[0]
+
+			# Test - 1: SRE Reserved Qty should be updated in Sales Order Item.
+			self.assertEqual(item.stock_reserved_qty, sre_details.reserved_qty)
+
+			# Test - 2: SRE status should be `Reserved`.
+			self.assertEqual(sre_details.status, "Reserved")
+
+		dn = make_delivery_note(so.name, kwargs={"for_reserved_stock": 1})
+		dn.save()
+		dn.submit()
+
+		for item in so.items:
+			sre_details = get_stock_reservation_entries_for_voucher(
+				"Sales Order", so.name, item.name, fields=["status", "delivered_qty", "reserved_qty"]
+			)[0]
+
+			# Test - 3: After Delivery Note, SRE status should be `Delivered`.
+			self.assertEqual(sre_details.status, "Delivered")
+
+			# Test - 4: After Delivery Note, SRE Delivered Qty should be equal to SRE Reserved Qty.
+			self.assertEqual(sre_details.delivered_qty, sre_details.reserved_qty)
+
+		sre = frappe.qb.DocType("Stock Reservation Entry")
+		sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+		for item in dn.items:
+			if item.serial_and_batch_bundle:
+				reserved_sb_entries = (
+					frappe.qb.from_(sre)
+					.inner_join(sb_entry)
+					.on(sre.name == sb_entry.parent)
+					.select(sb_entry.serial_no, sb_entry.batch_no, sb_entry.qty, sb_entry.delivered_qty)
+					.where(
+						(sre.voucher_type == "Sales Order")
+						& (sre.voucher_no == item.against_sales_order)
+						& (sre.voucher_detail_no == item.so_detail)
+					)
+				).run(as_dict=True)
+
+				reserved_sb_details: set[tuple] = set()
+				for sb_details in reserved_sb_entries:
+					# Test - 5: After Delivery Note, SB Entry Delivered Qty should be equal to SB Entry Reserved Qty.
+					self.assertEqual(sb_details.qty, sb_details.delivered_qty)
+
+					reserved_sb_details.add((sb_details.serial_no, sb_details.batch_no, -1 * sb_details.qty))
+
+				delivered_sb_entries = frappe.db.get_all(
+					"Serial and Batch Entry",
+					filters={"parent": item.serial_and_batch_bundle},
+					fields=["serial_no", "batch_no", "qty"],
+					as_list=True,
+				)
+				delivered_sb_details: set[tuple] = set(delivered_sb_entries)
+
+				# Test - 6: Reserved Serial/Batch Nos should be equal to Delivered Serial/Batch Nos.
+				self.assertSetEqual(reserved_sb_details, delivered_sb_details)
+
+		dn.cancel()
+		so.load_from_db()
+
+		for item in so.items:
+			sre_details = get_stock_reservation_entries_for_voucher(
+				"Sales Order",
+				so.name,
+				item.name,
+				fields=["name", "status", "delivered_qty", "reservation_based_on"],
+			)[0]
+
+			# Test - 7: After Delivery Note cancellation, SRE status should be `Reserved`.
+			self.assertEqual(sre_details.status, "Reserved")
+
+			# Test - 8: After Delivery Note cancellation, SRE Delivered Qty should be `0`.
+			self.assertEqual(sre_details.delivered_qty, 0)
+
+			if sre_details.reservation_based_on == "Serial and Batch":
+				sb_entries = frappe.db.get_all(
+					"Serial and Batch Entry",
+					filters={"parenttype": "Stock Reservation Entry", "parent": sre_details.name},
+					fields=["delivered_qty"],
+				)
+
+				for sb_entry in sb_entries:
+					# Test - 9: After Delivery Note cancellation, SB Entry Delivered Qty should be `0`.
+					self.assertEqual(sb_entry.delivered_qty, 0)
+
+	@change_settings(
+		"Stock Settings",
+		{
+			"allow_negative_stock": 0,
+			"enable_stock_reservation": 1,
+			"auto_reserve_serial_and_batch": 1,
+			"pick_serial_and_batch_based_on": "FIFO",
+		},
+	)
+	def test_stock_reservation_from_pick_list(self):
+		items_details = create_items()
+		create_material_receipt(items_details, self.warehouse, qty=100)
+
+		item_list = []
+		for item_code, properties in items_details.items():
+			item_list.append(
+				{
+					"item_code": item_code,
+					"warehouse": self.warehouse,
+					"qty": randint(11, 100),
+					"uom": properties.stock_uom,
+					"rate": randint(10, 400),
+				}
+			)
+
+		so = make_sales_order(
+			item_list=item_list,
+			warehouse=self.warehouse,
+		)
+		pl = create_pick_list(so.name)
+		pl.save()
+		pl.submit()
+		pl.create_stock_reservation_entries()
+		pl.load_from_db()
+		so.load_from_db()
+
+		for item in so.items:
+			sre_details = get_stock_reservation_entries_for_voucher(
+				"Sales Order", so.name, item.name, fields=["reserved_qty"]
+			)[0]
+
+			# Test - 1: SRE Reserved Qty should be updated in Sales Order Item.
+			self.assertEqual(item.stock_reserved_qty, sre_details.reserved_qty)
+
+		sre = frappe.qb.DocType("Stock Reservation Entry")
+		sb_entry = frappe.qb.DocType("Serial and Batch Entry")
+		for location in pl.locations:
+			# Test - 2: Reserved Qty should be updated in Pick List Item.
+			self.assertEqual(location.stock_reserved_qty, location.qty)
+
+			if location.serial_and_batch_bundle:
+				picked_sb_entries = frappe.db.get_all(
+					"Serial and Batch Entry",
+					filters={"parent": location.serial_and_batch_bundle},
+					fields=["serial_no", "batch_no", "qty"],
+					as_list=True,
+				)
+				picked_sb_details: set[tuple] = set(picked_sb_entries)
+
+				reserved_sb_entries = (
+					frappe.qb.from_(sre)
+					.inner_join(sb_entry)
+					.on(sre.name == sb_entry.parent)
+					.select(sb_entry.serial_no, sb_entry.batch_no, sb_entry.qty)
+					.where(
+						(sre.voucher_type == "Sales Order")
+						& (sre.voucher_no == location.sales_order)
+						& (sre.voucher_detail_no == location.sales_order_item)
+						& (sre.against_pick_list == pl.name)
+						& (sre.against_pick_list_item == location.name)
+					)
+				).run(as_dict=True)
+				reserved_sb_details: set[tuple] = {
+					(sb_details.serial_no, sb_details.batch_no, -1 * sb_details.qty)
+					for sb_details in reserved_sb_entries
+				}
+
+				# Test - 3: Reserved Serial/Batch Nos should be equal to Picked Serial/Batch Nos.
+				self.assertSetEqual(picked_sb_details, reserved_sb_details)
+
 
 def create_items() -> dict:
-	from erpnext.stock.doctype.item.test_item import make_item
-
-	items_details = {
-		# Stock Items
-		"SR Item 1": {"is_stock_item": 1, "valuation_rate": 100},
-		"SR Item 2": {"is_stock_item": 1, "valuation_rate": 200, "stock_uom": "Kg"},
-		# Batch Items
-		"SR Batch Item 1": {
-			"is_stock_item": 1,
-			"valuation_rate": 100,
-			"has_batch_no": 1,
-			"create_new_batch": 1,
-			"batch_number_series": "SRBI-1-.#####.",
-		},
-		"SR Batch Item 2": {
+	items_properties = [
+		# SR STOCK ITEM
+		{"is_stock_item": 1, "valuation_rate": 100},
+		# SR SERIAL ITEM
+		{
 			"is_stock_item": 1,
 			"valuation_rate": 200,
+			"has_serial_no": 1,
+			"serial_no_series": "SRSI-.#####",
+		},
+		# SR BATCH ITEM
+		{
+			"is_stock_item": 1,
+			"valuation_rate": 300,
 			"has_batch_no": 1,
 			"create_new_batch": 1,
-			"batch_number_series": "SRBI-2-.#####.",
-			"stock_uom": "Kg",
+			"batch_number_series": "SRBI-.#####.",
 		},
-		# Serial Item
-		"SR Serial Item 1": {
+		# SR SERIAL AND BATCH ITEM
+		{
 			"is_stock_item": 1,
-			"valuation_rate": 100,
+			"valuation_rate": 400,
 			"has_serial_no": 1,
-			"serial_no_series": "SRSI-1-.#####",
-		},
-		# Batch and Serial Item
-		"SR Batch and Serial Item 1": {
-			"is_stock_item": 1,
-			"valuation_rate": 100,
+			"serial_no_series": "SRSBI-.#####",
 			"has_batch_no": 1,
 			"create_new_batch": 1,
-			"batch_number_series": "SRBSI-1-.#####.",
-			"has_serial_no": 1,
-			"serial_no_series": "SRBSI-1-.#####",
+			"batch_number_series": "SRSBI-.#####.",
 		},
-	}
+	]
 
 	items = {}
-	for item_code, properties in items_details.items():
-		items[item_code] = make_item(item_code, properties)
+	for properties in items_properties:
+		item = make_item(properties=properties)
+		items[item.name] = item
 
 	return items
 
@@ -313,7 +650,7 @@
 	doc = frappe.new_doc("Stock Reservation Entry")
 	args = frappe._dict(args)
 
-	doc.item_code = args.item_code or "SR Item 1"
+	doc.item_code = args.item_code
 	doc.warehouse = args.warehouse or "_Test Warehouse - _TC"
 	doc.voucher_type = args.voucher_type
 	doc.voucher_no = args.voucher_no
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.json b/erpnext/stock/doctype/stock_settings/stock_settings.json
index 9d67cf9..88b5575 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.json
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.json
@@ -34,8 +34,10 @@
   "stock_reservation_tab",
   "enable_stock_reservation",
   "column_break_rx3e",
-  "reserve_stock_on_sales_order_submission",
+  "auto_reserve_stock_for_sales_order",
   "allow_partial_reservation",
+  "serial_and_batch_reservation_section",
+  "auto_reserve_serial_and_batch",
   "serial_and_batch_item_settings_tab",
   "section_break_7",
   "auto_create_serial_and_batch_bundle_for_outward",
@@ -59,7 +61,8 @@
   "stock_frozen_upto_days",
   "column_break_26",
   "role_allowed_to_create_edit_back_dated_transactions",
-  "stock_auth_role"
+  "stock_auth_role",
+  "section_break_plhx"
  ],
  "fields": [
   {
@@ -337,26 +340,19 @@
   },
   {
    "default": "0",
+   "description": "Allows to keep aside a specific quantity of inventory for a particular order.",
    "fieldname": "enable_stock_reservation",
    "fieldtype": "Check",
    "label": "Enable Stock Reservation"
   },
   {
-   "default": "0",
-   "depends_on": "eval: doc.enable_stock_reservation",
-   "description": "If enabled, <b>Stock Reservation Entries</b> will be created on submission of <b>Sales Order</b>",
-   "fieldname": "reserve_stock_on_sales_order_submission",
-   "fieldtype": "Check",
-   "label": "Reserve Stock on Sales Order Submission"
-  },
-  {
    "fieldname": "column_break_rx3e",
    "fieldtype": "Column Break"
   },
   {
    "default": "1",
    "depends_on": "eval: doc.enable_stock_reservation",
-   "description": "If enabled, <b>Partial Stock Reservation Entries</b> can be created. For example, If you have a <b>Sales Order</b> of 100 units and the Available Stock is 90 units then a Stock Reservation Entry will be created for 90 units. ",
+   "description": "If enabled, <b>Partial Stock Reservation Entries</b> can be created. For example, If you have a Sales Order of 100 units and the Available Stock is 90 units then a Stock Reservation Entry will be created for 90 units. ",
    "fieldname": "allow_partial_reservation",
    "fieldtype": "Check",
    "label": "Allow Partial Reservation"
@@ -383,6 +379,27 @@
    "fieldname": "auto_create_serial_and_batch_bundle_for_outward",
    "fieldtype": "Check",
    "label": "Auto Create Serial and Batch Bundle For Outward"
+  },
+  {
+   "default": "1",
+   "depends_on": "eval: doc.enable_stock_reservation",
+   "description": "If enabled, Serial and Batch Nos will be auto-reserved based on <b>Pick Serial / Batch Based On</b>",
+   "fieldname": "auto_reserve_serial_and_batch",
+   "fieldtype": "Check",
+   "label": "Auto Reserve Serial and Batch Nos"
+  },
+  {
+   "fieldname": "serial_and_batch_reservation_section",
+   "fieldtype": "Section Break",
+   "label": "Serial and Batch Reservation"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval: doc.enable_stock_reservation",
+   "description": "If enabled, <b>Stock Reservation Entries</b> will be created on submission of <b>Sales Order</b>",
+   "fieldname": "auto_reserve_stock_for_sales_order",
+   "fieldtype": "Check",
+   "label": "Auto Reserve Stock for Sales Order"
   }
  ],
  "icon": "icon-cog",
@@ -390,7 +407,7 @@
  "index_web_pages_for_search": 1,
  "issingle": 1,
  "links": [],
- "modified": "2023-05-29 15:10:54.959411",
+ "modified": "2023-09-01 16:16:34.018947",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Stock Settings",
diff --git a/erpnext/stock/doctype/stock_settings/stock_settings.py b/erpnext/stock/doctype/stock_settings/stock_settings.py
index 3b6db64..9ad3c9d 100644
--- a/erpnext/stock/doctype/stock_settings/stock_settings.py
+++ b/erpnext/stock/doctype/stock_settings/stock_settings.py
@@ -69,9 +69,9 @@
 				)
 
 	def cant_change_valuation_method(self):
-		db_valuation_method = frappe.db.get_single_value("Stock Settings", "valuation_method")
+		previous_valuation_method = self.get_doc_before_save().get("valuation_method")
 
-		if db_valuation_method and db_valuation_method != self.valuation_method:
+		if previous_valuation_method and previous_valuation_method != self.valuation_method:
 			# check if there are any stock ledger entries against items
 			# which does not have it's own valuation method
 			sle = frappe.db.sql(
@@ -108,13 +108,8 @@
 		if frappe.flags.in_test:
 			return
 
-		db_allow_negative_stock = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
-		db_enable_stock_reservation = frappe.db.get_single_value(
-			"Stock Settings", "enable_stock_reservation"
-		)
-
 		# Change in value of `Allow Negative Stock`
-		if db_allow_negative_stock != self.allow_negative_stock:
+		if self.has_value_changed("allow_negative_stock"):
 
 			# Disable -> Enable: Don't allow if `Stock Reservation` is enabled
 			if self.allow_negative_stock and self.enable_stock_reservation:
@@ -125,7 +120,7 @@
 				)
 
 		# Change in value of `Enable Stock Reservation`
-		if db_enable_stock_reservation != self.enable_stock_reservation:
+		if self.has_value_changed("enable_stock_reservation"):
 
 			# Disable -> Enable
 			if self.enable_stock_reservation:
diff --git a/erpnext/stock/report/reserved_stock/__init__.py b/erpnext/stock/report/reserved_stock/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/__init__.py
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.js b/erpnext/stock/report/reserved_stock/reserved_stock.js
new file mode 100644
index 0000000..2199f52
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.js
@@ -0,0 +1,170 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.query_reports["Reserved Stock"] = {
+	filters: [
+		{
+			fieldname: "company",
+			label: __("Company"),
+			fieldtype: "Link",
+			options: "Company",
+			reqd: 1,
+			default: frappe.defaults.get_user_default("Company"),
+		},
+		{
+			fieldname: "from_date",
+			label: __("From Date"),
+			fieldtype: "Date",
+			default: frappe.datetime.add_months(
+				frappe.datetime.get_today(),
+				-1
+			),
+			reqd: 1,
+		},
+		{
+			fieldname: "to_date",
+			label: __("To Date"),
+			fieldtype: "Date",
+			default: frappe.datetime.get_today(),
+			reqd: 1,
+		},
+		{
+			fieldname: "item_code",
+			label: __("Item"),
+			fieldtype: "Link",
+			options: "Item",
+			get_query: () => ({
+				filters: {
+					is_stock_item: 1,
+				},
+			}),
+		},
+		{
+			fieldname: "warehouse",
+			label: __("Warehouse"),
+			fieldtype: "Link",
+			options: "Warehouse",
+			get_query: () => ({
+				filters: {
+					is_group: 0,
+					company: frappe.query_report.get_filter_value("company"),
+				},
+			}),
+		},
+		{
+			fieldname: "stock_reservation_entry",
+			label: __("Stock Reservation Entry"),
+			fieldtype: "Link",
+			options: "Stock Reservation Entry",
+			get_query: () => ({
+				filters: {
+					docstatus: 1,
+					company: frappe.query_report.get_filter_value("company"),
+				},
+			}),
+		},
+		{
+			fieldname: "voucher_type",
+			label: __("Voucher Type"),
+			fieldtype: "Link",
+			options: "DocType",
+			default: "Sales Order",
+			get_query: () => ({
+				filters: {
+					name: ["in", ["Sales Order"]],
+				}
+			}),
+		},
+		{
+			fieldname: "voucher_no",
+			label: __("Voucher No"),
+			fieldtype: "Dynamic Link",
+			options: "voucher_type",
+			get_query: () => ({
+				filters: {
+					docstatus: 1,
+					company: frappe.query_report.get_filter_value("company"),
+				},
+			}),
+			get_options: function () {
+				return frappe.query_report.get_filter_value("voucher_type");
+			},
+		},
+		{
+			fieldname: "against_pick_list",
+			label: __("Against Pick List"),
+			fieldtype: "Link",
+			options: "Pick List",
+			get_query: () => ({
+				filters: {
+					docstatus: 1,
+					company: frappe.query_report.get_filter_value("company"),
+				},
+			}),
+		},
+		{
+			fieldname: "reservation_based_on",
+			label: __("Reservation Based On"),
+			fieldtype: "Select",
+			options: ["", "Qty", "Serial and Batch"],
+		},
+		{
+			fieldname: "status",
+			label: __("Status"),
+			fieldtype: "Select",
+			options: [
+				"",
+				"Partially Reserved",
+				"Reserved",
+				"Partially Delivered",
+				"Delivered",
+			],
+		},
+		{
+			fieldname: "project",
+			label: __("Project"),
+			fieldtype: "Link",
+			options: "Project",
+			get_query: () => ({
+				filters: {
+					company: frappe.query_report.get_filter_value("company"),
+				},
+			}),
+		},
+	],
+	formatter: (value, row, column, data, default_formatter) => {
+		value = default_formatter(value, row, column, data);
+
+		if (column.fieldname == "status") {
+			switch (data.status) {
+				case "Partially Reserved":
+					value = "<span style='color:orange'>" + value + "</span>";
+					break;
+				case "Reserved":
+					value = "<span style='color:blue'>" + value + "</span>";
+					break;
+				case "Partially Delivered":
+					value = "<span style='color:purple'>" + value + "</span>";
+					break;
+				case "Delivered":
+					value = "<span style='color:green'>" + value + "</span>";
+					break;
+			}
+		}
+		else if (column.fieldname == "delivered_qty") {
+			if (data.delivered_qty > 0) {
+				if (data.reserved_qty > data.delivered_qty) {
+					value = "<span style='color:blue'>" + value + "</span>";
+				}
+				else {
+					value = "<span style='color:green'>" + value + "</span>";
+				}
+			}
+			else {
+				value = "<span style='color:red'>" + value + "</span>";
+			}
+		}
+
+		return value;
+	},
+};
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.json b/erpnext/stock/report/reserved_stock/reserved_stock.json
new file mode 100644
index 0000000..17b916a
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.json
@@ -0,0 +1,26 @@
+{
+ "add_total_row": 0,
+ "columns": [],
+ "creation": "2023-08-02 22:11:19.439620",
+ "disabled": 0,
+ "docstatus": 0,
+ "doctype": "Report",
+ "filters": [],
+ "idx": 0,
+ "is_standard": "Yes",
+ "letterhead": null,
+ "modified": "2023-08-03 12:46:33.780222",
+ "modified_by": "Administrator",
+ "module": "Stock",
+ "name": "Reserved Stock",
+ "owner": "Administrator",
+ "prepared_report": 0,
+ "ref_doctype": "Stock Reservation Entry",
+ "report_name": "Reserved Stock",
+ "report_type": "Script Report",
+ "roles": [
+  {
+   "role": "System Manager"
+  }
+ ]
+}
\ No newline at end of file
diff --git a/erpnext/stock/report/reserved_stock/reserved_stock.py b/erpnext/stock/report/reserved_stock/reserved_stock.py
new file mode 100644
index 0000000..d93ee1c
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/reserved_stock.py
@@ -0,0 +1,191 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.query_builder.functions import Date
+
+
+def execute(filters=None):
+	columns, data = [], []
+
+	validate_filters(filters)
+
+	columns = get_columns()
+	data = get_data(filters)
+
+	return columns, data
+
+
+def validate_filters(filters):
+	if not filters:
+		frappe.throw(_("Please set filters"))
+
+	for field in ["company", "from_date", "to_date"]:
+		if not filters.get(field):
+			frappe.throw(_("Please set {0}").format(field))
+
+	if filters.get("from_date") > filters.get("to_date"):
+		frappe.throw(_("From Date cannot be greater than To Date"))
+
+
+def get_data(filters):
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	query = (
+		frappe.qb.from_(sre)
+		.select(
+			sre.creation,
+			sre.warehouse,
+			sre.item_code,
+			sre.stock_uom,
+			sre.voucher_qty,
+			sre.reserved_qty,
+			sre.delivered_qty,
+			(sre.available_qty - sre.reserved_qty).as_("available_qty"),
+			sre.voucher_type,
+			sre.voucher_no,
+			sre.against_pick_list,
+			sre.name.as_("stock_reservation_entry"),
+			sre.status,
+			sre.project,
+			sre.company,
+		)
+		.where(
+			(sre.docstatus == 1)
+			& (sre.company == filters.get("company"))
+			& (
+				(Date(sre.creation) >= filters.get("from_date"))
+				& (Date(sre.creation) <= filters.get("to_date"))
+			)
+		)
+	)
+
+	for field in [
+		"item_code",
+		"warehouse",
+		"voucher_type",
+		"voucher_no",
+		"against_pick_list",
+		"reservation_based_on",
+		"status",
+		"project",
+	]:
+		if value := filters.get(field):
+			query = query.where((sre[field] == value))
+
+	if value := filters.get("stock_reservation_entry"):
+		query = query.where((sre.name == value))
+
+	data = query.run(as_list=True)
+
+	return data
+
+
+def get_columns():
+	columns = [
+		{
+			"label": _("Date"),
+			"fieldname": "date",
+			"fieldtype": "Datetime",
+			"width": 150,
+		},
+		{
+			"fieldname": "warehouse",
+			"label": _("Warehouse"),
+			"fieldtype": "Link",
+			"options": "Warehouse",
+			"width": 150,
+		},
+		{
+			"fieldname": "item_code",
+			"label": _("Item"),
+			"fieldtype": "Link",
+			"options": "Item",
+			"width": 100,
+		},
+		{
+			"fieldname": "stock_uom",
+			"label": _("Stock UOM"),
+			"fieldtype": "Link",
+			"options": "UOM",
+			"width": 100,
+		},
+		{
+			"fieldname": "voucher_qty",
+			"label": _("Voucher Qty"),
+			"fieldtype": "Float",
+			"width": 110,
+			"convertible": "qty",
+		},
+		{
+			"fieldname": "reserved_qty",
+			"label": _("Reserved Qty"),
+			"fieldtype": "Float",
+			"width": 110,
+			"convertible": "qty",
+		},
+		{
+			"fieldname": "delivered_qty",
+			"label": _("Delivered Qty"),
+			"fieldtype": "Float",
+			"width": 110,
+			"convertible": "qty",
+		},
+		{
+			"fieldname": "available_qty",
+			"label": _("Available Qty to Reserve"),
+			"fieldtype": "Float",
+			"width": 120,
+			"convertible": "qty",
+		},
+		{
+			"fieldname": "voucher_type",
+			"label": _("Voucher Type"),
+			"fieldtype": "Data",
+			"options": "Warehouse",
+			"width": 110,
+		},
+		{
+			"fieldname": "voucher_no",
+			"label": _("Voucher No"),
+			"fieldtype": "Dynamic Link",
+			"options": "voucher_type",
+			"width": 120,
+		},
+		{
+			"fieldname": "against_pick_list",
+			"label": _("Against Pick List"),
+			"fieldtype": "Link",
+			"options": "Pick List",
+			"width": 130,
+		},
+		{
+			"fieldname": "stock_reservation_entry",
+			"label": _("Stock Reservation Entry"),
+			"fieldtype": "Link",
+			"options": "Stock Reservation Entry",
+			"width": 150,
+		},
+		{
+			"fieldname": "status",
+			"label": _("Status"),
+			"fieldtype": "Data",
+			"width": 120,
+		},
+		{
+			"fieldname": "project",
+			"label": _("Project"),
+			"fieldtype": "Link",
+			"options": "Project",
+			"width": 100,
+		},
+		{
+			"fieldname": "company",
+			"label": _("Company"),
+			"fieldtype": "Link",
+			"options": "Company",
+			"width": 110,
+		},
+	]
+
+	return columns
diff --git a/erpnext/stock/report/reserved_stock/test_reserved_stock.py b/erpnext/stock/report/reserved_stock/test_reserved_stock.py
new file mode 100644
index 0000000..6ef89f9
--- /dev/null
+++ b/erpnext/stock/report/reserved_stock/test_reserved_stock.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+from random import randint
+
+import frappe
+from frappe.tests.utils import FrappeTestCase, change_settings
+from frappe.utils.data import today
+
+from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+from erpnext.stock.doctype.stock_reservation_entry.test_stock_reservation_entry import (
+	cancel_all_stock_reservation_entries,
+	create_items,
+	create_material_receipt,
+)
+from erpnext.stock.report.reserved_stock.reserved_stock import get_data as reserved_stock_report
+
+
+class TestReservedStock(FrappeTestCase):
+	def setUp(self) -> None:
+		super().setUp()
+		self.stock_qty = 100
+		self.warehouse = "_Test Warehouse - _TC"
+
+	def tearDown(self) -> None:
+		cancel_all_stock_reservation_entries()
+		return super().tearDown()
+
+	@change_settings(
+		"Stock Settings",
+		{
+			"allow_negative_stock": 0,
+			"enable_stock_reservation": 1,
+			"auto_reserve_serial_and_batch": 1,
+			"pick_serial_and_batch_based_on": "FIFO",
+		},
+	)
+	def test_reserved_stock_report(self):
+		items_details = create_items()
+		create_material_receipt(items_details, self.warehouse, qty=self.stock_qty)
+
+		for item_code, properties in items_details.items():
+			so = make_sales_order(
+				item_code=item_code, qty=randint(11, 100), warehouse=self.warehouse, uom=properties.stock_uom
+			)
+			so.create_stock_reservation_entries()
+
+		data = reserved_stock_report(
+			filters={
+				"company": so.company,
+				"from_date": today(),
+				"to_date": today(),
+			}
+		)
+		self.assertEqual(len(data), len(items_details))
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 1dafb4d..102c3e1 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -165,7 +165,7 @@
 
 	def get_sre_reserved_qty_details(self) -> dict:
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-			get_sre_reserved_qty_details_for_item_and_warehouse as get_reserved_qty_details,
+			get_sre_reserved_qty_for_item_and_warehouse as get_reserved_qty_details,
 		)
 
 		item_code_list, warehouse_list = [], []
diff --git a/erpnext/stock/serial_batch_bundle.py b/erpnext/stock/serial_batch_bundle.py
index d6c840f..5998274 100644
--- a/erpnext/stock/serial_batch_bundle.py
+++ b/erpnext/stock/serial_batch_bundle.py
@@ -862,7 +862,7 @@
 		if self.get("serial_nos"):
 			serial_no_wise_batch = frappe._dict({})
 			if self.has_batch_no:
-				serial_no_wise_batch = self.get_serial_nos_batch(self.serial_nos)
+				serial_no_wise_batch = get_serial_nos_batch(self.serial_nos)
 
 			qty = -1 if self.type_of_transaction == "Outward" else 1
 			for serial_no in self.serial_nos:
@@ -887,16 +887,6 @@
 					},
 				)
 
-	def get_serial_nos_batch(self, serial_nos):
-		return frappe._dict(
-			frappe.get_all(
-				"Serial No",
-				fields=["name", "batch_no"],
-				filters={"name": ("in", serial_nos)},
-				as_list=1,
-			)
-		)
-
 	def create_batch(self):
 		from erpnext.stock.doctype.batch.batch import make_batch
 
@@ -974,3 +964,14 @@
 		serial_or_batch_items = [d.name for d in serial_or_batch_items]
 
 	return serial_or_batch_items
+
+
+def get_serial_nos_batch(serial_nos):
+	return frappe._dict(
+		frappe.get_all(
+			"Serial No",
+			fields=["name", "batch_no"],
+			filters={"name": ("in", serial_nos)},
+			as_list=1,
+		)
+	)
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index 258a503..db71fe2 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -1214,9 +1214,15 @@
 			if msg:
 				if self.reserved_stock:
 					allowed_qty = abs(exceptions[0]["actual_qty"]) - abs(exceptions[0]["diff"])
-					msg = "{0} As {1} units are reserved, you are allowed to consume only {2} units.".format(
-						msg, frappe.bold(self.reserved_stock), frappe.bold(allowed_qty)
-					)
+
+					if allowed_qty > 0:
+						msg = "{0} As {1} units are reserved for other sales orders, you are allowed to consume only {2} units.".format(
+							msg, frappe.bold(self.reserved_stock), frappe.bold(allowed_qty)
+						)
+					else:
+						msg = "{0} As the full stock is reserved for other sales orders, you're not allowed to consume the stock.".format(
+							msg,
+						)
 
 				msg_list.append(msg)
 
diff --git a/erpnext/subcontracting/doctype/subcontracting_bom/__init__.py b/erpnext/subcontracting/doctype/subcontracting_bom/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/subcontracting/doctype/subcontracting_bom/__init__.py
diff --git a/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.js b/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.js
new file mode 100644
index 0000000..a7f0d7a
--- /dev/null
+++ b/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.js
@@ -0,0 +1,40 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Subcontracting BOM', {
+    setup: (frm) => {
+		frm.trigger('set_queries');
+	},
+
+    set_queries: (frm) => {
+        frm.set_query('finished_good', () => {
+            return {
+                filters: {
+                    disabled: 0,
+                    is_stock_item: 1,
+                    default_bom: ['!=', ''],
+                    is_sub_contracted_item: 1,
+                }
+            }
+        });
+
+        frm.set_query('finished_good_bom', () => {
+            return {
+                filters: {
+                    docstatus: 1,
+                    is_active: 1,
+                    item: frm.doc.finished_good,
+                }
+            }
+        });
+
+        frm.set_query('service_item', () => {
+            return {
+                filters: {
+                    disabled: 0,
+                    is_stock_item: 0,
+                }
+            }
+        });
+    }
+});
diff --git a/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.json b/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.json
new file mode 100644
index 0000000..b258afc
--- /dev/null
+++ b/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.json
@@ -0,0 +1,155 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "format:SB-{####}",
+ "creation": "2023-08-29 12:43:20.417184",
+ "default_view": "List",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "is_active",
+  "section_break_dsjm",
+  "finished_good",
+  "finished_good_qty",
+  "column_break_quoy",
+  "finished_good_uom",
+  "finished_good_bom",
+  "section_break_qdw9",
+  "service_item",
+  "service_item_qty",
+  "column_break_uzmw",
+  "service_item_uom",
+  "conversion_factor"
+ ],
+ "fields": [
+  {
+   "default": "1",
+   "fieldname": "is_active",
+   "fieldtype": "Check",
+   "in_list_view": 1,
+   "in_preview": 1,
+   "in_standard_filter": 1,
+   "label": "Is Active",
+   "print_hide": 1
+  },
+  {
+   "fieldname": "section_break_dsjm",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "finished_good",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_preview": 1,
+   "in_standard_filter": 1,
+   "label": "Finished Good",
+   "options": "Item",
+   "reqd": 1,
+   "search_index": 1,
+   "set_only_once": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "finished_good_qty",
+   "fieldtype": "Float",
+   "label": "Finished Good Qty",
+   "non_negative": 1,
+   "reqd": 1
+  },
+  {
+   "fetch_from": "finished_good.default_bom",
+   "fetch_if_empty": 1,
+   "fieldname": "finished_good_bom",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_preview": 1,
+   "in_standard_filter": 1,
+   "label": "Finished Good BOM",
+   "options": "BOM",
+   "reqd": 1,
+   "search_index": 1
+  },
+  {
+   "fieldname": "section_break_qdw9",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "service_item",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_preview": 1,
+   "in_standard_filter": 1,
+   "label": "Service Item",
+   "options": "Item",
+   "reqd": 1,
+   "search_index": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "service_item_qty",
+   "fieldtype": "Float",
+   "label": "Service Item Qty",
+   "non_negative": 1,
+   "reqd": 1
+  },
+  {
+   "fetch_from": "service_item.stock_uom",
+   "fetch_if_empty": 1,
+   "fieldname": "service_item_uom",
+   "fieldtype": "Link",
+   "label": "Service Item UOM",
+   "options": "UOM",
+   "reqd": 1
+  },
+  {
+   "description": "Service Item Qty / Finished Good Qty",
+   "fieldname": "conversion_factor",
+   "fieldtype": "Float",
+   "label": "Conversion Factor",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_quoy",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fetch_from": "finished_good.stock_uom",
+   "fieldname": "finished_good_uom",
+   "fieldtype": "Link",
+   "label": "Finished Good UOM",
+   "options": "UOM",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_uzmw",
+   "fieldtype": "Column Break"
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "links": [],
+ "modified": "2023-09-03 16:51:43.558295",
+ "modified_by": "Administrator",
+ "module": "Subcontracting",
+ "name": "Subcontracting BOM",
+ "naming_rule": "Expression",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "export": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "System Manager",
+   "share": 1,
+   "write": 1
+  }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.py b/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.py
new file mode 100644
index 0000000..49ba986
--- /dev/null
+++ b/erpnext/subcontracting/doctype/subcontracting_bom/subcontracting_bom.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import flt
+
+
+class SubcontractingBOM(Document):
+	def validate(self):
+		self.validate_finished_good()
+		self.validate_service_item()
+		self.validate_is_active()
+
+	def before_save(self):
+		self.set_conversion_factor()
+
+	def validate_finished_good(self):
+		disabled, is_stock_item, default_bom, is_sub_contracted_item = frappe.db.get_value(
+			"Item",
+			self.finished_good,
+			["disabled", "is_stock_item", "default_bom", "is_sub_contracted_item"],
+		)
+
+		if disabled:
+			frappe.throw(_("Finished Good {0} is disabled.").format(frappe.bold(self.finished_good)))
+		if not is_stock_item:
+			frappe.throw(
+				_("Finished Good {0} must be a stock item.").format(frappe.bold(self.finished_good))
+			)
+		if not default_bom:
+			frappe.throw(
+				_("Finished Good {0} does not have a default BOM.").format(frappe.bold(self.finished_good))
+			)
+		if not is_sub_contracted_item:
+			frappe.throw(
+				_("Finished Good {0} must be a sub-contracted item.").format(frappe.bold(self.finished_good))
+			)
+
+	def validate_service_item(self):
+		disabled, is_stock_item = frappe.db.get_value(
+			"Item", self.service_item, ["disabled", "is_stock_item"]
+		)
+
+		if disabled:
+			frappe.throw(_("Service Item {0} is disabled.").format(frappe.bold(self.service_item)))
+		if is_stock_item:
+			frappe.throw(
+				_("Service Item {0} must be a non-stock item.").format(frappe.bold(self.service_item))
+			)
+
+	def validate_is_active(self):
+		if self.is_active:
+			if sb := frappe.db.exists(
+				"Subcontracting BOM",
+				{"finished_good": self.finished_good, "is_active": 1, "name": ["!=", self.name]},
+			):
+				frappe.throw(
+					_("There is already an active Subcontracting BOM {0} for the Finished Good {1}.").format(
+						frappe.bold(sb), frappe.bold(self.finished_good)
+					)
+				)
+
+	def set_conversion_factor(self):
+		self.conversion_factor = flt(self.service_item_qty) / flt(self.finished_good_qty)
+
+
+@frappe.whitelist()
+def get_subcontracting_boms_for_finished_goods(fg_items: str | list) -> dict:
+	if fg_items:
+		filters = {"is_active": 1}
+
+		if isinstance(fg_items, list):
+			filters["finished_good"] = ["in", fg_items]
+		else:
+			filters["finished_good"] = fg_items
+
+		if subcontracting_boms := frappe.get_all("Subcontracting BOM", filters=filters, fields=["*"]):
+			if isinstance(fg_items, list):
+				return {d.finished_good: d for d in subcontracting_boms}
+			else:
+				return subcontracting_boms[0]
+
+	return {}
+
+
+@frappe.whitelist()
+def get_subcontracting_boms_for_service_item(service_item: str) -> dict:
+	if service_item:
+		filters = {"is_active": 1, "service_item": service_item}
+		Subcontracting_boms = frappe.db.get_all("Subcontracting BOM", filters=filters, fields=["*"])
+
+		if Subcontracting_boms:
+			return {d.finished_good: d for d in Subcontracting_boms}
+
+	return {}
diff --git a/erpnext/subcontracting/doctype/subcontracting_bom/test_subcontracting_bom.py b/erpnext/subcontracting/doctype/subcontracting_bom/test_subcontracting_bom.py
new file mode 100644
index 0000000..9335ac8
--- /dev/null
+++ b/erpnext/subcontracting/doctype/subcontracting_bom/test_subcontracting_bom.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+
+class TestSubcontractingBOM(FrappeTestCase):
+	pass
+
+
+def create_subcontracting_bom(**kwargs):
+	kwargs = frappe._dict(kwargs)
+
+	doc = frappe.new_doc("Subcontracting BOM")
+	doc.is_active = kwargs.is_active or 1
+	doc.finished_good = kwargs.finished_good
+	doc.finished_good_uom = kwargs.finished_good_uom
+	doc.finished_good_qty = kwargs.finished_good_qty or 1
+	doc.finished_good_bom = kwargs.finished_good_bom
+	doc.service_item = kwargs.service_item
+	doc.service_item_uom = kwargs.service_item_uom
+	doc.service_item_qty = kwargs.service_item_qty or 1
+	doc.save()
+
+	return doc
diff --git a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
index b7b3445..faf0cad 100644
--- a/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
+++ b/erpnext/subcontracting/doctype/subcontracting_order/subcontracting_order.py
@@ -128,8 +128,12 @@
 		for si in self.service_items:
 			if si.fg_item:
 				item = frappe.get_doc("Item", si.fg_item)
-				bom = frappe.db.get_value("BOM", {"item": item.item_code, "is_active": 1, "is_default": 1})
-
+				bom = (
+					frappe.db.get_value(
+						"Subcontracting BOM", {"finished_good": item.item_code, "is_active": 1}, "finished_good_bom"
+					)
+					or item.default_bom
+				)
 				items.append(
 					{
 						"item_code": item.item_code,
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index acf9553..dd071e6 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -239,12 +239,6 @@
 		set_missing_values(frm);
 	},
 
-	recalculate_rate(frm) {
-		if (frm.doc.recalculate_rate) {
-			set_missing_values(frm);
-		}
-	},
-
 	items_remove: (frm) => {
 		set_missing_values(frm);
 	},
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 8a12e3b..6aecaf9 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -180,7 +180,6 @@
 							"item_name": scrap_item.item_name,
 							"qty": qty,
 							"stock_uom": scrap_item.stock_uom,
-							"recalculate_rate": 0,
 							"rate": rate,
 							"rm_cost_per_qty": 0,
 							"service_cost_per_qty": 0,
@@ -277,13 +276,12 @@
 					else:
 						item.scrap_cost_per_qty = 0
 
-				if item.recalculate_rate:
-					item.rate = (
-						flt(item.rm_cost_per_qty)
-						+ flt(item.service_cost_per_qty)
-						+ flt(item.additional_cost_per_qty)
-						- flt(item.scrap_cost_per_qty)
-					)
+				item.rate = (
+					flt(item.rm_cost_per_qty)
+					+ flt(item.service_cost_per_qty)
+					+ flt(item.additional_cost_per_qty)
+					- flt(item.scrap_cost_per_qty)
+				)
 
 			item.received_qty = flt(item.qty) + flt(item.rejected_qty)
 			item.amount = flt(item.qty) * flt(item.rate)
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
index c036390..38432be 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt_item/subcontracting_receipt_item.json
@@ -28,7 +28,6 @@
   "rate_and_amount",
   "rate",
   "amount",
-  "recalculate_rate",
   "column_break_19",
   "rm_cost_per_qty",
   "service_cost_per_qty",
@@ -202,7 +201,6 @@
    "options": "currency",
    "print_width": "100px",
    "read_only": 1,
-   "read_only_depends_on": "eval: doc.recalculate_rate",
    "width": "100px"
   },
   {
@@ -476,14 +474,6 @@
    "label": "Accounting Details"
   },
   {
-   "default": "1",
-   "depends_on": "eval: !doc.is_scrap_item",
-   "fieldname": "recalculate_rate",
-   "fieldtype": "Check",
-   "label": "Recalculate Rate",
-   "read_only_depends_on": "eval: doc.is_scrap_item"
-  },
-  {
    "fieldname": "serial_and_batch_bundle",
    "fieldtype": "Link",
    "label": "Serial and Batch Bundle",
@@ -531,7 +521,7 @@
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2023-08-25 20:09:03.069417",
+ "modified": "2023-09-03 17:04:21.214316",
  "modified_by": "Administrator",
  "module": "Subcontracting",
  "name": "Subcontracting Receipt Item",