fix: consider reserved serial nos while cancelling a stock transaction
This commit is contained in:
parent
56e9a46c17
commit
d9e284366d
@ -681,6 +681,35 @@ def get_sre_reserved_qty_for_voucher_detail_no(
|
||||
return flt(reserved_qty[0][0])
|
||||
|
||||
|
||||
def get_sre_reserved_serial_nos_details(
|
||||
item_code: str, warehouse: str, serial_nos: list = None
|
||||
) -> dict:
|
||||
"""Returns a dict of `Serial No` reserved in Stock Reservation Entry. The dict is like {serial_no: sre_name, ...}"""
|
||||
|
||||
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, sre.name)
|
||||
.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")
|
||||
)
|
||||
.orderby(sb_entry.creation)
|
||||
)
|
||||
|
||||
if serial_nos:
|
||||
query = query.where(sb_entry.serial_no.isin(serial_nos))
|
||||
|
||||
return frappe._dict(query.run())
|
||||
|
||||
|
||||
def get_sre_details_for_voucher(voucher_type: str, voucher_no: str) -> list[dict]:
|
||||
"""Returns a list of SREs for the provided voucher."""
|
||||
|
||||
|
@ -19,6 +19,9 @@ from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_in
|
||||
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.doctype.stock_reservation_entry.stock_reservation_entry import (
|
||||
get_sre_reserved_serial_nos_details,
|
||||
)
|
||||
from erpnext.stock.utils import (
|
||||
get_incoming_outgoing_rate_for_cancel,
|
||||
get_or_make_bin,
|
||||
@ -1719,9 +1722,7 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
||||
|
||||
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock"))
|
||||
|
||||
if not args.batch_no:
|
||||
return
|
||||
|
||||
if args.batch_no:
|
||||
neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
|
||||
if is_negative_with_precision(neg_batch_sle, is_batch=True):
|
||||
message = _(
|
||||
@ -1736,6 +1737,8 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
||||
)
|
||||
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
|
||||
|
||||
validate_reserved_stock(args)
|
||||
|
||||
|
||||
def is_negative_with_precision(neg_sle, is_batch=False):
|
||||
"""
|
||||
@ -1801,6 +1804,47 @@ def get_future_sle_with_negative_batch_qty(args):
|
||||
)
|
||||
|
||||
|
||||
def validate_reserved_stock(kwargs):
|
||||
if kwargs.serial_no:
|
||||
serial_nos = kwargs.serial_no.split("\n")
|
||||
validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
|
||||
|
||||
elif kwargs.serial_and_batch_bundle:
|
||||
sbb_entries = frappe.db.get_all(
|
||||
"Serial and Batch Entry",
|
||||
{
|
||||
"parenttype": "Serial and Batch Bundle",
|
||||
"parent": kwargs.serial_and_batch_bundle,
|
||||
"docstatus": 1,
|
||||
},
|
||||
["batch_no", "serial_no", "qty"],
|
||||
)
|
||||
serial_nos = [entry.serial_no for entry in sbb_entries if entry.serial_no]
|
||||
|
||||
if serial_nos:
|
||||
validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
|
||||
|
||||
|
||||
def validate_reserved_serial_nos(item_code, warehouse, serial_nos):
|
||||
if reserved_serial_nos_details := get_sre_reserved_serial_nos_details(
|
||||
item_code, warehouse, serial_nos
|
||||
):
|
||||
if common_serial_nos := list(
|
||||
set(serial_nos).intersection(set(reserved_serial_nos_details.keys()))
|
||||
):
|
||||
msg = _(
|
||||
"Serial Nos are reserved in Stock Reservation Entries, you need to unreserve them before proceeding."
|
||||
)
|
||||
msg += "<br />"
|
||||
msg += _("Example: Serial No {0} reserved in {1}.").format(
|
||||
frappe.bold(common_serial_nos[0]),
|
||||
frappe.get_desk_link(
|
||||
"Stock Reservation Entry", reserved_serial_nos_details[common_serial_nos[0]]
|
||||
),
|
||||
)
|
||||
frappe.throw(msg, title=_("Reserved Serial No."))
|
||||
|
||||
|
||||
def is_negative_stock_allowed(*, item_code: Optional[str] = None) -> bool:
|
||||
if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)):
|
||||
return True
|
||||
|
Loading…
x
Reference in New Issue
Block a user