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()