From cccd34b06a28e52e6c9635e3478eecf3e6ade280 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Sat, 3 Jan 2026 14:56:12 +0530 Subject: [PATCH] fix: not able to submit backdated stock reco --- .../repost_item_valuation.py | 3 + .../serial_and_batch_bundle.py | 15 ++-- .../test_stock_reconciliation.py | 88 +++++++++++++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) 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 64d636a4fd1..c59ca0c2d6a 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -76,6 +76,9 @@ class RepostItemValuation(Document): def on_discard(self): self.db_set("status", "Cancelled") + def repost_now(self): + repost(self) + def validate(self): self.reset_repost_only_accounting_ledgers() self.set_company() diff --git a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py index 31d63165103..5a749b31f62 100644 --- a/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py +++ b/erpnext/stock/doctype/serial_and_batch_bundle/serial_and_batch_bundle.py @@ -554,7 +554,7 @@ class SerialandBatchBundle(Document): available_qty += flt(d.qty, precision) if not allow_negative_stock: - self.validate_negative_batch(d.batch_no, available_qty) + self.validate_negative_batch(d.batch_no, available_qty, field) d.stock_value_difference = flt(d.qty) * flt(d.incoming_rate) @@ -567,8 +567,8 @@ class SerialandBatchBundle(Document): } ) - def validate_negative_batch(self, batch_no, available_qty): - if available_qty < 0 and not self.is_stock_reco_for_valuation_adjustment(available_qty): + def validate_negative_batch(self, batch_no, available_qty, field=None): + if available_qty < 0 and not self.is_stock_reco_for_valuation_adjustment(available_qty, field=field): msg = f"""Batch No {bold(batch_no)} of an Item {bold(self.item_code)} has negative stock of quantity {bold(available_qty)} in the @@ -576,13 +576,16 @@ class SerialandBatchBundle(Document): frappe.throw(_(msg), BatchNegativeStockError) - def is_stock_reco_for_valuation_adjustment(self, available_qty): + def is_stock_reco_for_valuation_adjustment(self, available_qty, field=None): if ( self.voucher_type == "Stock Reconciliation" and self.type_of_transaction == "Outward" and self.voucher_detail_no - and abs(frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty")) - == abs(available_qty) + and ( + abs(frappe.db.get_value("Stock Reconciliation Item", self.voucher_detail_no, "qty")) + == abs(available_qty) + or field == "total_qty" + ) ): return True diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index a6f896ff385..01c2e207137 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -1569,6 +1569,94 @@ class TestStockReconciliation(IntegrationTestCase, StockTestMixin): self.assertFalse(status == "Active") + def test_change_valuation_of_batch_using_backdated_stock_reco(self): + from erpnext.stock.doctype.batch.batch import get_batch_qty + from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry + + item_code = self.make_item( + "Test Item Change Valuation of Batch", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "TEST-BATCH-CVB-.###", + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + reco = create_stock_reconciliation( + item_code=item_code, + posting_date=add_days(nowdate(), -6), + warehouse=warehouse, + qty=10, + rate=80, + use_serial_batch_fields=1, + ) + + batch_no = get_batch_from_bundle(reco.items[0].serial_and_batch_bundle) + + make_stock_entry( + item_code=item_code, + source=warehouse, + qty=2, + posting_date=add_days(nowdate(), -4), + use_serial_batch_fields=1, + batch_no=batch_no, + ) + + make_stock_entry( + item_code=item_code, + source=warehouse, + qty=2, + posting_date=add_days(nowdate(), -3), + use_serial_batch_fields=1, + batch_no=batch_no, + ) + + se4 = make_stock_entry( + item_code=item_code, + source=warehouse, + qty=2, + posting_date=add_days(nowdate(), -2), + use_serial_batch_fields=1, + batch_no=batch_no, + ) + + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": se4.name, "is_cancelled": 0}, + ["actual_qty", "stock_value_difference"], + as_dict=1, + ) + + valuation_rate = sle.stock_value_difference / sle.actual_qty + self.assertEqual(valuation_rate, 80) + + create_stock_reconciliation( + item_code=item_code, + posting_date=add_days(nowdate(), -5), + warehouse=warehouse, + qty=10, + rate=100, + use_serial_batch_fields=1, + batch_no=batch_no, + ) + + sle = frappe.db.get_value( + "Stock Ledger Entry", + {"voucher_no": se4.name, "is_cancelled": 0}, + ["actual_qty", "stock_value_difference"], + as_dict=1, + ) + + valuation_rate = sle.stock_value_difference / sle.actual_qty + + self.assertEqual(valuation_rate, 100) + + batch_qty = get_batch_qty(batch_no, warehouse, item_code) + self.assertEqual(batch_qty, 4) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1)