mirror of
https://github.com/frappe/erpnext.git
synced 2026-05-26 16:34:46 +00:00
fix: check future negative stock for batches
batch's ledger is only maintained in form of `actual_qty` on batch's
SLEs. To validate if batch has any negative qty in future, cumulative
total of `actual_qty` is required to ensure it never goes negative.
(cherry picked from commit 5eba57528c)
This commit is contained in:
committed by
Ankush Menat
parent
341a02eaf4
commit
68a9c3e160
@@ -1082,17 +1082,36 @@ def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
|||||||
allow_negative_stock = cint(allow_negative_stock) \
|
allow_negative_stock = cint(allow_negative_stock) \
|
||||||
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
or cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
||||||
|
|
||||||
if (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock:
|
if allow_negative_stock:
|
||||||
sle = get_future_sle_with_negative_qty(args)
|
return
|
||||||
if sle:
|
if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"):
|
||||||
message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
|
return
|
||||||
abs(sle[0]["qty_after_transaction"]),
|
|
||||||
frappe.get_desk_link('Item', args.item_code),
|
neg_sle = get_future_sle_with_negative_qty(args)
|
||||||
frappe.get_desk_link('Warehouse', args.warehouse),
|
if neg_sle:
|
||||||
sle[0]["posting_date"], sle[0]["posting_time"],
|
message = _("{0} units of {1} needed in {2} on {3} {4} for {5} to complete this transaction.").format(
|
||||||
frappe.get_desk_link(sle[0]["voucher_type"], sle[0]["voucher_no"]))
|
abs(neg_sle[0]["qty_after_transaction"]),
|
||||||
|
frappe.get_desk_link('Item', args.item_code),
|
||||||
|
frappe.get_desk_link('Warehouse', args.warehouse),
|
||||||
|
neg_sle[0]["posting_date"], neg_sle[0]["posting_time"],
|
||||||
|
frappe.get_desk_link(neg_sle[0]["voucher_type"], neg_sle[0]["voucher_no"]))
|
||||||
|
|
||||||
|
frappe.throw(message, NegativeStockError, title='Insufficient Stock')
|
||||||
|
|
||||||
|
|
||||||
|
if not args.batch_no:
|
||||||
|
return
|
||||||
|
|
||||||
|
neg_batch_sle = get_future_sle_with_negative_batch_qty(args)
|
||||||
|
if neg_batch_sle:
|
||||||
|
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")
|
||||||
|
|
||||||
frappe.throw(message, NegativeStockError, title='Insufficient Stock')
|
|
||||||
|
|
||||||
def get_future_sle_with_negative_qty(args):
|
def get_future_sle_with_negative_qty(args):
|
||||||
return frappe.db.sql("""
|
return frappe.db.sql("""
|
||||||
@@ -1111,6 +1130,29 @@ def get_future_sle_with_negative_qty(args):
|
|||||||
limit 1
|
limit 1
|
||||||
""", args, as_dict=1)
|
""", args, as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
|
def get_future_sle_with_negative_batch_qty(args):
|
||||||
|
return frappe.db.sql("""
|
||||||
|
with batch_ledger as (
|
||||||
|
select
|
||||||
|
posting_date, posting_time, voucher_type, voucher_no,
|
||||||
|
sum(actual_qty) over (order by posting_date, posting_time, creation) as cumulative_total
|
||||||
|
from `tabStock Ledger Entry`
|
||||||
|
where
|
||||||
|
item_code = %(item_code)s
|
||||||
|
and warehouse = %(warehouse)s
|
||||||
|
and batch_no=%(batch_no)s
|
||||||
|
and is_cancelled = 0
|
||||||
|
order by posting_date, posting_time, creation
|
||||||
|
)
|
||||||
|
select * from batch_ledger
|
||||||
|
where
|
||||||
|
cumulative_total < 0.0
|
||||||
|
and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s)
|
||||||
|
limit 1
|
||||||
|
""", args, as_dict=1)
|
||||||
|
|
||||||
|
|
||||||
def _round_off_if_near_zero(number: float, precision: int = 6) -> float:
|
def _round_off_if_near_zero(number: float, precision: int = 6) -> float:
|
||||||
""" Rounds off the number to zero only if number is close to zero for decimal
|
""" Rounds off the number to zero only if number is close to zero for decimal
|
||||||
specified in precision. Precision defaults to 6.
|
specified in precision. Precision defaults to 6.
|
||||||
|
|||||||
Reference in New Issue
Block a user