From 86019559aa338925a2c9b75a8772305fce6c50a4 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 13 Oct 2023 14:45:15 +0530 Subject: [PATCH 1/3] fix: Stock Reconciliation Insufficient Stock Error --- erpnext/stock/stock_ledger.py | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 84bcb99f738e..51f7fc8381a1 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1618,26 +1618,32 @@ def is_negative_with_precision(neg_sle, is_batch=False): def get_future_sle_with_negative_qty(args): - return frappe.db.sql( - """ - select - qty_after_transaction, posting_date, posting_time, - voucher_type, voucher_no - from `tabStock Ledger Entry` - where - item_code = %(item_code)s - and warehouse = %(warehouse)s - and voucher_no != %(voucher_no)s - and timestamp(posting_date, posting_time) >= timestamp(%(posting_date)s, %(posting_time)s) - and is_cancelled = 0 - and qty_after_transaction < 0 - order by timestamp(posting_date, posting_time) asc - limit 1 - """, - args, - as_dict=1, + sle = frappe.qb.DocType("Stock Ledger Entry") + query = ( + frappe.qb.from_(sle) + .select( + sle.qty_after_transaction, sle.posting_date, sle.posting_time, sle.voucher_type, sle.voucher_no + ) + .where( + (sle.item_code == args.item_code) + & (sle.warehouse == args.warehouse) + & (sle.voucher_no != args.voucher_no) + & ( + CombineDatetime(sle.posting_date, sle.posting_time) + >= CombineDatetime(args.posting_date, args.posting_time) + ) + & (sle.is_cancelled == 0) + & (sle.qty_after_transaction < 0) + ) + .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) + .limit(1) ) + if args.voucher_type == "Stock Reconciliation" and args.batch_no: + query = query.where(sle.batch_no == args.batch_no) + + return query.run(as_dict=True) + def get_future_sle_with_negative_batch_qty(args): return frappe.db.sql( From a9c821416eec9c616e9413affed11db56460a61d Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 13 Oct 2023 15:09:19 +0530 Subject: [PATCH 2/3] fix: linter --- erpnext/stock/stock_ledger.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 51f7fc8381a1..bdae87c7ee7e 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -1617,30 +1617,30 @@ def is_negative_with_precision(neg_sle, is_batch=False): return qty_deficit < 0 and abs(qty_deficit) > 0.0001 -def get_future_sle_with_negative_qty(args): - sle = frappe.qb.DocType("Stock Ledger Entry") +def get_future_sle_with_negative_qty(sle): + SLE = frappe.qb.DocType("Stock Ledger Entry") query = ( - frappe.qb.from_(sle) + frappe.qb.from_(SLE) .select( - sle.qty_after_transaction, sle.posting_date, sle.posting_time, sle.voucher_type, sle.voucher_no + SLE.qty_after_transaction, SLE.posting_date, SLE.posting_time, SLE.voucher_type, SLE.voucher_no ) .where( - (sle.item_code == args.item_code) - & (sle.warehouse == args.warehouse) - & (sle.voucher_no != args.voucher_no) + (SLE.item_code == sle.item_code) + & (SLE.warehouse == sle.warehouse) + & (SLE.voucher_no != sle.voucher_no) & ( - CombineDatetime(sle.posting_date, sle.posting_time) - >= CombineDatetime(args.posting_date, args.posting_time) + CombineDatetime(SLE.posting_date, SLE.posting_time) + >= CombineDatetime(sle.posting_date, sle.posting_time) ) - & (sle.is_cancelled == 0) - & (sle.qty_after_transaction < 0) + & (SLE.is_cancelled == 0) + & (SLE.qty_after_transaction < 0) ) - .orderby(CombineDatetime(sle.posting_date, sle.posting_time)) + .orderby(CombineDatetime(SLE.posting_date, SLE.posting_time)) .limit(1) ) - if args.voucher_type == "Stock Reconciliation" and args.batch_no: - query = query.where(sle.batch_no == args.batch_no) + if sle.voucher_type == "Stock Reconciliation" and sle.batch_no: + query = query.where(SLE.batch_no == sle.batch_no) return query.run(as_dict=True) From f406e4aa0d157e780a0025b00a0d7a99c3ea2705 Mon Sep 17 00:00:00 2001 From: s-aga-r Date: Fri, 13 Oct 2023 20:39:16 +0530 Subject: [PATCH 3/3] test: add test case for Stock Reco Batch Item --- .../test_stock_reconciliation.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index d1e5c5d345f1..c913af3301a6 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -956,6 +956,58 @@ def test_batch_item_validation(self): self.assertRaises(frappe.ValidationError, sr.save) + @change_settings("Stock Settings", {"allow_negative_stock": 0}) + def test_backdated_stock_reco_for_batch_item_dont_have_future_sle(self): + # Step - 1: Create a Batch Item + from erpnext.stock.doctype.item.test_item import make_item + + item = make_item( + properties={ + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-.###", + } + ).name + + # Step - 2: Create Opening Stock Reconciliation + sr1 = create_stock_reconciliation( + item_code=item, + warehouse="_Test Warehouse - _TC", + qty=10, + purpose="Opening Stock", + posting_date=add_days(nowdate(), -2), + ) + + # Step - 3: Create Stock Entry (Material Receipt) + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + se1 = make_stock_entry( + item_code=item, + target="_Test Warehouse - _TC", + qty=100, + ) + + # Step - 4: Create Stock Entry (Material Issue) + make_stock_entry( + item_code=item, + source="_Test Warehouse - _TC", + qty=100, + batch_no=se1.items[0].batch_no, + purpose="Material Issue", + ) + + # Step - 5: Create Stock Reconciliation (Backdated) after the Stock Reconciliation 1 (Step - 2) + sr2 = create_stock_reconciliation( + item_code=item, + warehouse="_Test Warehouse - _TC", + qty=5, + batch_no=sr1.items[0].batch_no, + posting_date=add_days(nowdate(), -1), + ) + + self.assertEqual(sr2.docstatus, 1) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1)