fix: negative qty validation on stock reco cancellation (#27170)
* test: negative stock validation on SR cancel * fix: negative stock setting ignored in stock reco In stock reconcilation cancellation negative stock setting is ignored as `db.get_value` is returning string `'0'` which is not casted to int/bool for further logic. This causes negative qty, which evantually gets caught by reposting but by design this should stop cancellation. * test: typo and minor refactor
This commit is contained in:
parent
e11bfe7da4
commit
e7109c18db
@ -390,7 +390,7 @@ class StockReconciliation(StockController):
|
|||||||
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
|
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
|
||||||
|
|
||||||
sl_entries.reverse()
|
sl_entries.reverse()
|
||||||
allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
|
allow_negative_stock = cint(frappe.db.get_single_value("Stock Settings", "allow_negative_stock"))
|
||||||
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
|
self.make_sl_entries(sl_entries, allow_negative_stock=allow_negative_stock)
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ from erpnext.stock.doctype.item.test_item import create_item
|
|||||||
from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
|
from erpnext.stock.utils import get_incoming_rate, get_stock_value_on, get_valuation_method
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
|
||||||
|
from erpnext.tests.utils import change_settings
|
||||||
|
|
||||||
|
|
||||||
class TestStockReconciliation(unittest.TestCase):
|
class TestStockReconciliation(unittest.TestCase):
|
||||||
@ -310,6 +311,7 @@ class TestStockReconciliation(unittest.TestCase):
|
|||||||
pr2.cancel()
|
pr2.cancel()
|
||||||
pr1.cancel()
|
pr1.cancel()
|
||||||
|
|
||||||
|
@change_settings("Stock Settings", {"allow_negative_stock": 0})
|
||||||
def test_backdated_stock_reco_future_negative_stock(self):
|
def test_backdated_stock_reco_future_negative_stock(self):
|
||||||
"""
|
"""
|
||||||
Test if a backdated stock reco causes future negative stock and is blocked.
|
Test if a backdated stock reco causes future negative stock and is blocked.
|
||||||
@ -327,8 +329,6 @@ class TestStockReconciliation(unittest.TestCase):
|
|||||||
warehouse = "_Test Warehouse - _TC"
|
warehouse = "_Test Warehouse - _TC"
|
||||||
create_item(item_code)
|
create_item(item_code)
|
||||||
|
|
||||||
negative_stock_setting = frappe.db.get_single_value("Stock Settings", "allow_negative_stock")
|
|
||||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 0)
|
|
||||||
|
|
||||||
pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
|
pr1 = make_purchase_receipt(item_code=item_code, warehouse=warehouse, qty=10, rate=100,
|
||||||
posting_date=add_days(nowdate(), -2))
|
posting_date=add_days(nowdate(), -2))
|
||||||
@ -348,11 +348,50 @@ class TestStockReconciliation(unittest.TestCase):
|
|||||||
self.assertRaises(NegativeStockError, sr3.submit)
|
self.assertRaises(NegativeStockError, sr3.submit)
|
||||||
|
|
||||||
# teardown
|
# teardown
|
||||||
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting)
|
|
||||||
sr3.cancel()
|
sr3.cancel()
|
||||||
dn2.cancel()
|
dn2.cancel()
|
||||||
pr1.cancel()
|
pr1.cancel()
|
||||||
|
|
||||||
|
|
||||||
|
@change_settings("Stock Settings", {"allow_negative_stock": 0})
|
||||||
|
def test_backdated_stock_reco_cancellation_future_negative_stock(self):
|
||||||
|
"""
|
||||||
|
Test if a backdated stock reco cancellation that causes future negative stock is blocked.
|
||||||
|
-------------------------------------------
|
||||||
|
Var | Doc | Qty | Balance
|
||||||
|
-------------------------------------------
|
||||||
|
SR | Reco | 100 | 100 (posting date: today-1) (shouldn't be cancelled after DN)
|
||||||
|
DN | DN | 100 | 0 (posting date: today)
|
||||||
|
"""
|
||||||
|
from erpnext.stock.stock_ledger import NegativeStockError
|
||||||
|
from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
|
||||||
|
frappe.db.commit()
|
||||||
|
|
||||||
|
item_code = "Backdated-Reco-Cancellation-Item"
|
||||||
|
warehouse = "_Test Warehouse - _TC"
|
||||||
|
create_item(item_code)
|
||||||
|
|
||||||
|
|
||||||
|
sr = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=100, rate=100,
|
||||||
|
posting_date=add_days(nowdate(), -1))
|
||||||
|
|
||||||
|
dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=100, rate=120,
|
||||||
|
posting_date=nowdate())
|
||||||
|
|
||||||
|
dn_balance = frappe.db.get_value("Stock Ledger Entry", {"voucher_no": dn.name, "is_cancelled": 0},
|
||||||
|
"qty_after_transaction")
|
||||||
|
self.assertEqual(dn_balance, 0)
|
||||||
|
|
||||||
|
# check if cancellation of stock reco is blocked
|
||||||
|
self.assertRaises(NegativeStockError, sr.cancel)
|
||||||
|
|
||||||
|
repost_exists = bool(frappe.db.exists("Repost Item Valuation", {"voucher_no": sr.name}))
|
||||||
|
self.assertFalse(repost_exists, msg="Negative stock validation not working on reco cancellation")
|
||||||
|
|
||||||
|
# teardown
|
||||||
|
frappe.db.rollback()
|
||||||
|
|
||||||
|
|
||||||
def test_valid_batch(self):
|
def test_valid_batch(self):
|
||||||
create_batch_item_with_batch("Testing Batch Item 1", "001")
|
create_batch_item_with_batch("Testing Batch Item 1", "001")
|
||||||
create_batch_item_with_batch("Testing Batch Item 2", "002")
|
create_batch_item_with_batch("Testing Batch Item 2", "002")
|
||||||
|
@ -955,7 +955,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
|
|||||||
|
|
||||||
return valuation_rate
|
return valuation_rate
|
||||||
|
|
||||||
def update_qty_in_future_sle(args, allow_negative_stock=None):
|
def update_qty_in_future_sle(args, allow_negative_stock=False):
|
||||||
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
|
"""Recalculate Qty after Transaction in future SLEs based on current SLE."""
|
||||||
datetime_limit_condition = ""
|
datetime_limit_condition = ""
|
||||||
qty_shift = args.actual_qty
|
qty_shift = args.actual_qty
|
||||||
@ -1044,8 +1044,8 @@ def get_datetime_limit_condition(detail):
|
|||||||
)
|
)
|
||||||
)"""
|
)"""
|
||||||
|
|
||||||
def validate_negative_qty_in_future_sle(args, allow_negative_stock=None):
|
def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
|
||||||
allow_negative_stock = 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 (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation") and not allow_negative_stock:
|
||||||
|
Loading…
Reference in New Issue
Block a user