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:
Ankush Menat 2021-08-26 16:40:45 +05:30 committed by GitHub
parent e11bfe7da4
commit e7109c18db
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 7 deletions

View File

@ -390,7 +390,7 @@ class StockReconciliation(StockController):
sl_entries = self.merge_similar_item_serial_nos(sl_entries)
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)

View File

@ -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.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import make_purchase_receipt
from erpnext.tests.utils import change_settings
class TestStockReconciliation(unittest.TestCase):
@ -310,6 +311,7 @@ class TestStockReconciliation(unittest.TestCase):
pr2.cancel()
pr1.cancel()
@change_settings("Stock Settings", {"allow_negative_stock": 0})
def test_backdated_stock_reco_future_negative_stock(self):
"""
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"
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,
posting_date=add_days(nowdate(), -2))
@ -348,11 +348,50 @@ class TestStockReconciliation(unittest.TestCase):
self.assertRaises(NegativeStockError, sr3.submit)
# teardown
frappe.db.set_value("Stock Settings", None, "allow_negative_stock", negative_stock_setting)
sr3.cancel()
dn2.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):
create_batch_item_with_batch("Testing Batch Item 1", "001")
create_batch_item_with_batch("Testing Batch Item 2", "002")

View File

@ -955,7 +955,7 @@ def get_valuation_rate(item_code, warehouse, voucher_type, voucher_no,
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."""
datetime_limit_condition = ""
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):
allow_negative_stock = allow_negative_stock \
def validate_negative_qty_in_future_sle(args, allow_negative_stock=False):
allow_negative_stock = cint(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: