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])
|
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]:
|
def get_sre_details_for_voucher(voucher_type: str, voucher_no: str) -> list[dict]:
|
||||||
"""Returns a list of SREs for the provided voucher."""
|
"""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 (
|
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
||||||
get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock,
|
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 (
|
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,
|
||||||
@ -1719,22 +1722,22 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
|||||||
|
|
||||||
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock"))
|
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock"))
|
||||||
|
|
||||||
if not args.batch_no:
|
if args.batch_no:
|
||||||
return
|
neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
|
||||||
|
if is_negative_with_precision(neg_batch_sle, is_batch=True):
|
||||||
|
message = _(
|
||||||
|
"{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
|
||||||
|
).format(
|
||||||
|
abs(neg_batch_sle[0]["cumulative_total"]),
|
||||||
|
frappe.get_desk_link("Batch", args.batch_no),
|
||||||
|
frappe.get_desk_link("Warehouse", args.warehouse),
|
||||||
|
neg_batch_sle[0]["posting_date"],
|
||||||
|
neg_batch_sle[0]["posting_time"],
|
||||||
|
frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]),
|
||||||
|
)
|
||||||
|
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
|
||||||
|
|
||||||
neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
|
validate_reserved_stock(args)
|
||||||
if is_negative_with_precision(neg_batch_sle, is_batch=True):
|
|
||||||
message = _(
|
|
||||||
"{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction."
|
|
||||||
).format(
|
|
||||||
abs(neg_batch_sle[0]["cumulative_total"]),
|
|
||||||
frappe.get_desk_link("Batch", args.batch_no),
|
|
||||||
frappe.get_desk_link("Warehouse", args.warehouse),
|
|
||||||
neg_batch_sle[0]["posting_date"],
|
|
||||||
neg_batch_sle[0]["posting_time"],
|
|
||||||
frappe.get_desk_link(neg_batch_sle[0]["voucher_type"], neg_batch_sle[0]["voucher_no"]),
|
|
||||||
)
|
|
||||||
frappe.throw(message, NegativeStockError, title=_("Insufficient Stock for Batch"))
|
|
||||||
|
|
||||||
|
|
||||||
def is_negative_with_precision(neg_sle, is_batch=False):
|
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:
|
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)):
|
if cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock", cache=True)):
|
||||||
return True
|
return True
|
||||||
|
Loading…
x
Reference in New Issue
Block a user