fix: don't allow to deliver/transfer reserved stock

This commit is contained in:
s-aga-r 2023-04-12 14:13:54 +05:30
parent 56097807b4
commit f0acb2049b
2 changed files with 36 additions and 12 deletions

View File

@ -179,18 +179,13 @@ def get_stock_reservation_entries_for_voucher(
def get_sre_reserved_qty_details_for_item_and_warehouse( def get_sre_reserved_qty_details_for_item_and_warehouse(
item_code: str | list, warehouse: str | list item_code_list: list, warehouse_list: list
) -> dict: ) -> dict:
"""Returns a dict like {("item_code", "warehouse"): "reserved_qty", ... }.""" """Returns a dict like {("item_code", "warehouse"): "reserved_qty", ... }."""
sre_details = {} sre_details = {}
if item_code and warehouse: if item_code_list and warehouse_list:
if isinstance(item_code, str):
item_code = [item_code]
if isinstance(warehouse, str):
warehouse = [warehouse]
sre = frappe.qb.DocType("Stock Reservation Entry") sre = frappe.qb.DocType("Stock Reservation Entry")
sre_data = ( sre_data = (
frappe.qb.from_(sre) frappe.qb.from_(sre)
@ -201,8 +196,8 @@ def get_sre_reserved_qty_details_for_item_and_warehouse(
) )
.where( .where(
(sre.docstatus == 1) (sre.docstatus == 1)
& (sre.item_code.isin(item_code)) & (sre.item_code.isin(item_code_list))
& (sre.warehouse.isin(warehouse)) & (sre.warehouse.isin(warehouse_list))
& (sre.status.notin(["Delivered", "Cancelled"])) & (sre.status.notin(["Delivered", "Cancelled"]))
) )
.groupby(sre.item_code, sre.warehouse) .groupby(sre.item_code, sre.warehouse)
@ -214,6 +209,27 @@ def get_sre_reserved_qty_details_for_item_and_warehouse(
return sre_details 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))
.where(
(sre.docstatus == 1)
& (sre.item_code == item_code)
& (sre.warehouse == warehouse)
& (sre.status.notin(["Delivered", "Cancelled"]))
)
).run(as_list=True)[0][0] or 0.0
return reserved_qty
def get_sre_reserved_qty_details_for_voucher( def get_sre_reserved_qty_details_for_voucher(
voucher_type: str, voucher_no: str, voucher_detail_no: str = None voucher_type: str, voucher_no: str, voucher_detail_no: str = None
) -> dict: ) -> dict:

View File

@ -13,6 +13,9 @@ from frappe.utils import cint, cstr, flt, get_link_to_form, getdate, now, nowdat
import erpnext import erpnext
from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty from erpnext.stock.doctype.bin.bin import update_qty as update_bin_qty
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock,
)
from erpnext.stock.utils import ( from erpnext.stock.utils import (
get_incoming_outgoing_rate_for_cancel, get_incoming_outgoing_rate_for_cancel,
get_or_make_bin, get_or_make_bin,
@ -380,6 +383,7 @@ class update_entries_after(object):
self.new_items_found = False self.new_items_found = False
self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict()) self.distinct_item_warehouses = args.get("distinct_item_warehouses", frappe._dict())
self.affected_transactions: Set[Tuple[str, str]] = set() self.affected_transactions: Set[Tuple[str, str]] = set()
self.reserved_stock = get_reserved_stock(self.args.item_code, self.args.warehouse)
self.data = frappe._dict() self.data = frappe._dict()
self.initialize_previous_data(self.args) self.initialize_previous_data(self.args)
@ -610,7 +614,7 @@ class update_entries_after(object):
validate negative stock for entries current datetime onwards validate negative stock for entries current datetime onwards
will not consider cancelled entries will not consider cancelled entries
""" """
diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty) diff = self.wh_data.qty_after_transaction + flt(sle.actual_qty) - flt(self.reserved_stock)
diff = flt(diff, self.flt_precision) # respect system precision diff = flt(diff, self.flt_precision) # respect system precision
if diff < 0 and abs(diff) > 0.0001: if diff < 0 and abs(diff) > 0.0001:
@ -1006,6 +1010,7 @@ class update_entries_after(object):
msg_list = [] msg_list = []
for warehouse, exceptions in self.exceptions.items(): for warehouse, exceptions in self.exceptions.items():
deficiency = min(e["diff"] for e in exceptions) deficiency = min(e["diff"] for e in exceptions)
msg_prefix = _("As {} units are reserved, ").format(frappe.bold(self.reserved_stock))
if ( if (
exceptions[0]["voucher_type"], exceptions[0]["voucher_type"],
@ -1013,7 +1018,7 @@ class update_entries_after(object):
) in frappe.local.flags.currently_saving: ) in frappe.local.flags.currently_saving:
msg = _("{0} units of {1} needed in {2} to complete this transaction.").format( msg = _("{0} units of {1} needed in {2} to complete this transaction.").format(
abs(deficiency), frappe.bold(abs(deficiency)),
frappe.get_desk_link("Item", exceptions[0]["item_code"]), frappe.get_desk_link("Item", exceptions[0]["item_code"]),
frappe.get_desk_link("Warehouse", warehouse), frappe.get_desk_link("Warehouse", warehouse),
) )
@ -1021,7 +1026,7 @@ class update_entries_after(object):
msg = _( msg = _(
"{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction." "{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
).format( ).format(
abs(deficiency), frappe.bold(abs(deficiency)),
frappe.get_desk_link("Item", exceptions[0]["item_code"]), frappe.get_desk_link("Item", exceptions[0]["item_code"]),
frappe.get_desk_link("Warehouse", warehouse), frappe.get_desk_link("Warehouse", warehouse),
exceptions[0]["posting_date"], exceptions[0]["posting_date"],
@ -1030,6 +1035,9 @@ class update_entries_after(object):
) )
if msg: if msg:
if self.reserved_stock:
msg = msg_prefix + msg
msg_list.append(msg) msg_list.append(msg)
if msg_list: if msg_list: