chore: create SRE on SO submission
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index 1e4fabe..6e2fb2e 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -783,6 +783,75 @@
 
 		gl_entries.append(self.get_gl_dict(gl_entry, item=item))
 
+	def make_sr_entries(self):
+		if not self.get("reserve_stock"):
+			return
+
+		if self.doctype != "Sales Order":
+			frappe.throw(
+				_("Stock Reservation can only be created against a {0}.").format(frappe.bold("Sales Order"))
+			)
+
+		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")
+				)
+			)
+
+		if not frappe.db.get_single_value("Stock Settings", "reserve_stock_on_sales_order_submission"):
+			frappe.throw(
+				_("Please enable {0} in the {1}.").format(
+					frappe.bold("Reserve Stock on Sales Order Submission"), frappe.bold("Stock Settings")
+				)
+			)
+
+		for item in self.get("items"):
+			if not item.get("reserve_stock"):
+				continue
+
+			available_qty = get_available_qty_to_reserve(item.item_code, item.warehouse)
+			reserved_qty = min(item.stock_qty, available_qty)
+
+			if not reserved_qty:
+				frappe.msgprint(
+					_("Row {0}: No available stock to reserve for the Item {1}").format(
+						item.idx, frappe.bold(item.item_code)
+					),
+					title=_("Stock Reservation"),
+					indicator="orange",
+				)
+				continue
+
+			elif reserved_qty < item.stock_qty:
+				frappe.msgprint(
+					_("Row {0}: Only {1} available to reserve for the Item {2}").format(
+						item.idx,
+						frappe.bold(str(reserved_qty / item.conversion_factor) + " " + item.uom),
+						frappe.bold(item.item_code),
+					),
+					title=_("Stock Reservation"),
+					indicator="orange",
+				)
+
+				if not frappe.db.get_single_value("Stock Settings", "allow_partial_reservation"):
+					continue
+
+			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
+			sre.voucher_qty = item.stock_qty
+			sre.reserved_qty = reserved_qty
+			sre.company = self.company
+			sre.stock_uom = item.stock_uom
+			sre.project = self.project
+			sre.save()
+			sre.submit()
+
 
 def repost_required_for_queue(doc: StockController) -> bool:
 	"""check if stock document contains repeated item-warehouse with queue based valuation.
@@ -952,6 +1021,33 @@
 	return or_conditions
 
 
+@frappe.whitelist()
+def get_available_qty_to_reserve(item_code, warehouse):
+	from frappe.query_builder.functions import Sum
+
+	from erpnext.stock.utils import get_stock_balance
+
+	available_qty = get_stock_balance(item_code, warehouse)
+
+	if available_qty:
+		sre = frappe.qb.DocType("Stock Reservation Entry")
+		reserved_qty = (
+			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.status.notin(["Delivered", "Cancelled"]))
+			)
+		).run()[0][0] or 0.0
+
+		if reserved_qty:
+			return available_qty - reserved_qty
+
+	return available_qty
+
+
 def create_repost_item_valuation_entry(args):
 	args = frappe._dict(args)
 	repost_entry = frappe.new_doc("Repost Item Valuation")
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index ee9161b..d7541be 100755
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -241,6 +241,8 @@
 
 			update_coupon_code_count(self.coupon_code, "used")
 
+		self.make_sr_entries()
+
 	def on_cancel(self):
 		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
 		super(SalesOrder, self).on_cancel()