diff --git a/erpnext/manufacturing/doctype/work_order/test_work_order.py b/erpnext/manufacturing/doctype/work_order/test_work_order.py index 3949fe34852..aa19b2f1003 100644 --- a/erpnext/manufacturing/doctype/work_order/test_work_order.py +++ b/erpnext/manufacturing/doctype/work_order/test_work_order.py @@ -196,8 +196,6 @@ class TestWorkOrder(ERPNextTestCase): # no change in reserved / projected self.assertEqual(cint(bin1_on_end_production.reserved_qty_for_production), cint(bin1_on_start_production.reserved_qty_for_production)) - self.assertEqual(cint(bin1_on_end_production.projected_qty), - cint(bin1_on_end_production.projected_qty)) def test_backflush_qty_for_overpduction_manufacture(self): cancel_stock_entry = [] diff --git a/erpnext/stock/doctype/bin/bin.py b/erpnext/stock/doctype/bin/bin.py index 27e83caf99b..11ff359b483 100644 --- a/erpnext/stock/doctype/bin/bin.py +++ b/erpnext/stock/doctype/bin/bin.py @@ -103,8 +103,8 @@ def update_stock(bin_name, args, allow_negative_stock=False, via_landed_cost_vou """WARNING: This function is deprecated. Inline this function instead of using it.""" from erpnext.stock.stock_ledger import repost_current_voucher - update_qty(bin_name, args) repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher) + update_qty(bin_name, args) def get_bin_details(bin_name): return frappe.db.get_value('Bin', bin_name, ['actual_qty', 'ordered_qty', @@ -112,13 +112,23 @@ def get_bin_details(bin_name): 'reserved_qty_for_sub_contract'], as_dict=1) def update_qty(bin_name, args): - bin_details = get_bin_details(bin_name) + from erpnext.controllers.stock_controller import future_sle_exists - # update the stock values (for current quantities) - if args.get("voucher_type")=="Stock Reconciliation": - actual_qty = args.get('qty_after_transaction') - else: - actual_qty = bin_details.actual_qty + flt(args.get("actual_qty")) + bin_details = get_bin_details(bin_name) + # actual qty is already updated by processing current voucher + actual_qty = bin_details.actual_qty + + # actual qty is not up to date in case of backdated transaction + if future_sle_exists(args): + actual_qty = frappe.db.get_value("Stock Ledger Entry", + filters={ + "item_code": args.get("item_code"), + "warehouse": args.get("warehouse"), + "is_cancelled": 0 + }, + fieldname="qty_after_transaction", + order_by="posting_date desc, posting_time desc, creation desc", + ) or 0.0 ordered_qty = flt(bin_details.ordered_qty) + flt(args.get("ordered_qty")) reserved_qty = flt(bin_details.reserved_qty) + flt(args.get("reserved_qty")) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index a61501a9cd5..5ad8f443203 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -46,7 +46,7 @@ class RepostItemValuation(Document): self.db_set('status', self.status) def on_submit(self): - if not frappe.flags.in_test or self.flags.dont_run_in_test: + if not frappe.flags.in_test or self.flags.dont_run_in_test or frappe.flags.dont_execute_stock_reposts: return frappe.enqueue(repost, timeout=1800, queue='long', @@ -97,7 +97,8 @@ def repost(doc): return doc.set_status('In Progress') - frappe.db.commit() + if not frappe.flags.in_test: + frappe.db.commit() repost_sl_entries(doc) repost_gl_entries(doc) diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 48e339ae566..c4ddc9e2d6f 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -24,11 +24,15 @@ from erpnext.tests.utils import ERPNextTestCase, change_settings class TestStockReconciliation(ERPNextTestCase): @classmethod - def setUpClass(self): + def setUpClass(cls): super().setUpClass() create_batch_or_serial_no_items() frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1) + def tearDown(self): + frappe.flags.dont_execute_stock_reposts = None + + def test_reco_for_fifo(self): self._test_reco_sle_gle("FIFO") @@ -392,6 +396,41 @@ class TestStockReconciliation(ERPNextTestCase): 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") + def test_intermediate_sr_bin_update(self): + """Bin should show correct qty even for backdated entries. + + ------------------------------------------- + | creation | Var | Doc | Qty | balance qty + ------------------------------------------- + | 1 | SR | Reco | 10 | 10 (posting date: today+10) + | 3 | SR2 | Reco | 11 | 11 (posting date: today+11) + | 2 | DN | DN | 5 | 6 <-- assert in BIN (posting date: today+12) + """ + from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note + + # repost will make this test useless, qty should update in realtime without reposts + frappe.flags.dont_execute_stock_reposts = True + frappe.db.rollback() + + 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=10, rate=100, + posting_date=add_days(nowdate(), 10)) + + dn = create_delivery_note(item_code=item_code, warehouse=warehouse, qty=5, rate=120, + posting_date=add_days(nowdate(), 12)) + old_bin_qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty") + + sr2 = create_stock_reconciliation(item_code=item_code, warehouse=warehouse, qty=11, rate=100, + posting_date=add_days(nowdate(), 11)) + new_bin_qty = frappe.db.get_value("Bin", {"item_code": item_code, "warehouse": warehouse}, "actual_qty") + + self.assertEqual(old_bin_qty + 1, new_bin_qty) + 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") diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py index 90c6ae67fb3..456cfe3d76f 100644 --- a/erpnext/stock/stock_ledger.py +++ b/erpnext/stock/stock_ledger.py @@ -65,8 +65,8 @@ def make_sl_entries(sl_entries, allow_negative_stock=False, via_landed_cost_vouc is_stock_item = frappe.get_cached_value('Item', args.get("item_code"), 'is_stock_item') if is_stock_item: bin_name = get_or_make_bin(args.get("item_code"), args.get("warehouse")) - update_bin_qty(bin_name, args) repost_current_voucher(args, allow_negative_stock, via_landed_cost_voucher) + update_bin_qty(bin_name, args) else: frappe.msgprint(_("Item {0} ignored since it is not a stock item").format(args.get("item_code")))