mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 12:49:10 +00:00
fix: consider reserved batches while cancelling a stock transaction
This commit is contained in:
@@ -710,6 +710,39 @@ def get_sre_reserved_serial_nos_details(
|
|||||||
return frappe._dict(query.run())
|
return frappe._dict(query.run())
|
||||||
|
|
||||||
|
|
||||||
|
def get_sre_reserved_batch_nos_details(
|
||||||
|
item_code: str, warehouse: str, batch_nos: list = None
|
||||||
|
) -> dict:
|
||||||
|
"""Returns a dict of `Batch Qty` reserved in Stock Reservation Entry. The dict is like {batch_no: qty, ...}"""
|
||||||
|
|
||||||
|
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.batch_no,
|
||||||
|
Sum(sb_entry.qty - sb_entry.delivered_qty),
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
(sre.docstatus == 1)
|
||||||
|
& (sre.item_code == item_code)
|
||||||
|
& (sre.warehouse == warehouse)
|
||||||
|
& ((sre.reserved_qty - sre.delivered_qty) > 0)
|
||||||
|
& (sre.status.notin(["Delivered", "Cancelled"]))
|
||||||
|
& (sre.reservation_based_on == "Serial and Batch")
|
||||||
|
)
|
||||||
|
.groupby(sb_entry.batch_no)
|
||||||
|
.orderby(sb_entry.creation)
|
||||||
|
)
|
||||||
|
|
||||||
|
if batch_nos:
|
||||||
|
query = query.where(sb_entry.batch_no.isin(batch_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."""
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,17 @@ from frappe import _, scrub
|
|||||||
from frappe.model.meta import get_field_precision
|
from frappe.model.meta import get_field_precision
|
||||||
from frappe.query_builder import Case
|
from frappe.query_builder import Case
|
||||||
from frappe.query_builder.functions import CombineDatetime, Sum
|
from frappe.query_builder.functions import CombineDatetime, Sum
|
||||||
from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, parse_json
|
from frappe.utils import cint, flt, get_link_to_form, getdate, now, nowdate, nowtime, parse_json
|
||||||
|
|
||||||
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.inventory_dimension.inventory_dimension import get_inventory_dimensions
|
from erpnext.stock.doctype.inventory_dimension.inventory_dimension import get_inventory_dimensions
|
||||||
|
from erpnext.stock.doctype.serial_and_batch_bundle.serial_and_batch_bundle import (
|
||||||
|
get_available_batches,
|
||||||
|
)
|
||||||
|
from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
|
||||||
|
get_sre_reserved_batch_nos_details,
|
||||||
|
)
|
||||||
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,
|
||||||
)
|
)
|
||||||
@@ -1809,6 +1815,9 @@ def validate_reserved_stock(kwargs):
|
|||||||
serial_nos = kwargs.serial_no.split("\n")
|
serial_nos = kwargs.serial_no.split("\n")
|
||||||
validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
|
validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
|
||||||
|
|
||||||
|
elif kwargs.batch_no:
|
||||||
|
validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, [kwargs.batch_no])
|
||||||
|
|
||||||
elif kwargs.serial_and_batch_bundle:
|
elif kwargs.serial_and_batch_bundle:
|
||||||
sbb_entries = frappe.db.get_all(
|
sbb_entries = frappe.db.get_all(
|
||||||
"Serial and Batch Entry",
|
"Serial and Batch Entry",
|
||||||
@@ -1817,12 +1826,13 @@ def validate_reserved_stock(kwargs):
|
|||||||
"parent": kwargs.serial_and_batch_bundle,
|
"parent": kwargs.serial_and_batch_bundle,
|
||||||
"docstatus": 1,
|
"docstatus": 1,
|
||||||
},
|
},
|
||||||
["batch_no", "serial_no", "qty"],
|
["batch_no", "serial_no"],
|
||||||
)
|
)
|
||||||
serial_nos = [entry.serial_no for entry in sbb_entries if entry.serial_no]
|
|
||||||
|
|
||||||
if serial_nos:
|
if serial_nos := [entry.serial_no for entry in sbb_entries if entry.serial_no]:
|
||||||
validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
|
validate_reserved_serial_nos(kwargs.item_code, kwargs.warehouse, serial_nos)
|
||||||
|
elif batch_nos := [entry.batch_no for entry in sbb_entries if entry.batch_no]:
|
||||||
|
validate_reserved_batch_nos(kwargs.item_code, kwargs.warehouse, batch_nos)
|
||||||
|
|
||||||
|
|
||||||
def validate_reserved_serial_nos(item_code, warehouse, serial_nos):
|
def validate_reserved_serial_nos(item_code, warehouse, serial_nos):
|
||||||
@@ -1845,6 +1855,36 @@ def validate_reserved_serial_nos(item_code, warehouse, serial_nos):
|
|||||||
frappe.throw(msg, title=_("Reserved Serial No."))
|
frappe.throw(msg, title=_("Reserved Serial No."))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_reserved_batch_nos(item_code, warehouse, batch_nos):
|
||||||
|
if reserved_batches_map := get_sre_reserved_batch_nos_details(item_code, warehouse, batch_nos):
|
||||||
|
available_batches = get_available_batches(
|
||||||
|
frappe._dict(
|
||||||
|
{
|
||||||
|
"item_code": item_code,
|
||||||
|
"warehouse": warehouse,
|
||||||
|
"posting_date": nowdate(),
|
||||||
|
"posting_time": nowtime(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
available_batches_map = {row.batch_no: row.qty for row in available_batches}
|
||||||
|
precision = cint(frappe.db.get_default("float_precision")) or 2
|
||||||
|
|
||||||
|
for batch_no in batch_nos:
|
||||||
|
diff = flt(
|
||||||
|
available_batches_map.get(batch_no, 0) - reserved_batches_map.get(batch_no, 0), precision
|
||||||
|
)
|
||||||
|
if diff < 0 and abs(diff) > 0.0001:
|
||||||
|
msg = _("{0} units of {1} needed in {2} on {3} {4} to complete this transaction.").format(
|
||||||
|
abs(diff),
|
||||||
|
frappe.get_desk_link("Batch", batch_no),
|
||||||
|
frappe.get_desk_link("Warehouse", warehouse),
|
||||||
|
nowdate(),
|
||||||
|
nowtime(),
|
||||||
|
)
|
||||||
|
frappe.throw(msg, title=_("Reserved Stock for Batch"))
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user