From aee03fe2efb02536bcce8c31e5e2d669e04b3568 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 19 Apr 2024 15:11:28 +0530 Subject: [PATCH] fix: stock reco negative qty validation (cherry picked from commit 289495c30856579c47109054d33c249153c34636) # Conflicts: # erpnext/stock/stock_ledger.py --- .../test_stock_reconciliation.py | 102 ++++++++++++++++++ erpnext/stock/stock_ledger.py | 17 +++ 2 files changed, 119 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index b31ca3046bd..add8b516e83 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -986,6 +986,108 @@ class TestStockReconciliation(FrappeTestCase, StockTestMixin): active_serial_no = frappe.get_all("Serial No", filters={"status": "Active", "item_code": item_code}) self.assertEqual(len(active_serial_no), 5) + def test_balance_qty_for_batch_with_backdated_stock_reco_and_future_entries(self): + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item = self.make_item( + "Test Batch Item Original Test", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-SRWFEE-.###", + }, + ) + + warehouse = "_Test Warehouse - _TC" + se1 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=50, + basic_rate=100, + posting_date=add_days(nowdate(), -2), + ) + batch1 = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle) + + se2 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=50, + basic_rate=100, + posting_date=add_days(nowdate(), -2), + ) + batch2 = get_batch_from_bundle(se2.items[0].serial_and_batch_bundle) + + se3 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=100, + basic_rate=100, + posting_date=add_days(nowdate(), -2), + ) + batch3 = get_batch_from_bundle(se3.items[0].serial_and_batch_bundle) + + se3 = make_stock_entry( + item_code=item.name, + target=warehouse, + qty=100, + basic_rate=100, + posting_date=nowdate(), + ) + + sle = frappe.get_all( + "Stock Ledger Entry", + filters={ + "item_code": item.name, + "warehouse": warehouse, + "is_cancelled": 0, + "voucher_no": se3.name, + }, + fields=["qty_after_transaction"], + order_by="posting_time desc, creation desc", + ) + + self.assertEqual(flt(sle[0].qty_after_transaction), flt(300.0)) + + sr = create_stock_reconciliation( + item_code=item.name, + warehouse=warehouse, + qty=0, + batch_no=batch1, + posting_date=add_days(nowdate(), -1), + use_serial_batch_fields=1, + do_not_save=1, + ) + + for batch in [batch2, batch3]: + sr.append( + "items", + { + "item_code": item.name, + "warehouse": warehouse, + "qty": 0, + "batch_no": batch, + "use_serial_batch_fields": 1, + }, + ) + + sr.save() + sr.submit() + + sle = frappe.get_all( + "Stock Ledger Entry", + filters={ + "item_code": item.name, + "warehouse": warehouse, + "is_cancelled": 0, + "voucher_no": se3.name, + }, + fields=["qty_after_transaction"], + order_by="posting_time desc, creation desc", + ) + + self.assertEqual(flt(sle[0].qty_after_transaction), flt(100.0)) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index ec4c3682cb2..e42204978c3 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1728,6 +1728,10 @@ def get_stock_reco_qty_shift(args): stock_reco_qty_shift = flt(args.qty_after_transaction) - flt(last_balance) else: stock_reco_qty_shift = flt(args.actual_qty) + + elif args.get("serial_and_batch_bundle"): + stock_reco_qty_shift = flt(args.actual_qty) + else: # reco is being submitted last_balance = get_previous_sle_of_current_voucher(args, "<=", exclude_current_voucher=True).get( @@ -1799,7 +1803,20 @@ def get_datetime_limit_condition(detail): def validate_negative_qty_in_future_sle(args, allow_negative_stock=False): if allow_negative_stock or is_negative_stock_allowed(item_code=args.item_code): return +<<<<<<< HEAD if not (args.actual_qty < 0 or args.voucher_type == "Stock Reconciliation"): +======= + + if ( + args.voucher_type == "Stock Reconciliation" + and args.actual_qty < 0 + and args.get("serial_and_batch_bundle") + and frappe.db.get_value("Stock Reconciliation Item", args.voucher_detail_no, "qty") > 0 + ): + return + + if args.actual_qty >= 0 and args.voucher_type != "Stock Reconciliation": +>>>>>>> 289495c308 (fix: stock reco negative qty validation) return neg_sle = get_future_sle_with_negative_qty(args)