From 9cd33741014e2f84614df73090617a9f53294d01 Mon Sep 17 00:00:00 2001 From: rohitwaghchaure Date: Wed, 24 Jul 2024 16:38:20 +0530 Subject: [PATCH] fix: incorrect current qty for the batch in stock reco (#42434) --- .../serial_and_batch_bundle.py | 15 +++ .../stock_reconciliation.py | 25 +++-- .../test_stock_reconciliation.py | 93 +++++++++++++++++++ 3 files changed, 124 insertions(+), 9 deletions(-) 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 9b65e11f4302..615d98a448d5 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 @@ -2188,6 +2188,8 @@ def get_stock_ledgers_for_serial_nos(kwargs): def get_stock_ledgers_batches(kwargs): + from erpnext.stock.utils import get_combine_datetime + stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry") batch_table = frappe.qb.DocType("Batch") @@ -2214,6 +2216,19 @@ def get_stock_ledgers_batches(kwargs): else: query = query.where(stock_ledger_entry[field] == kwargs.get(field)) + if kwargs.get("posting_date"): + if kwargs.get("posting_time") is None: + kwargs.posting_time = nowtime() + + timestamp_condition = stock_ledger_entry.posting_datetime <= get_combine_datetime( + kwargs.posting_date, kwargs.posting_time + ) + + query = query.where(timestamp_condition) + + if kwargs.get("ignore_voucher_nos"): + query = query.where(stock_ledger_entry.voucher_no.notin(kwargs.get("ignore_voucher_nos"))) + if kwargs.based_on == "LIFO": query = query.orderby(batch_table.creation, order=frappe.qb.desc) elif kwargs.based_on == "Expiry": diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py index 674624e184b4..93e3e69729b1 100644 --- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py @@ -16,7 +16,7 @@ get_available_serial_nos, ) from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos -from erpnext.stock.utils import get_stock_balance +from erpnext.stock.utils import get_incoming_rate, get_stock_balance class OpeningEntryAccountError(frappe.ValidationError): @@ -952,14 +952,21 @@ def recalculate_current_qty(self, voucher_detail_no): precesion = row.precision("current_qty") if flt(current_qty, precesion) != flt(row.current_qty, precesion): if not row.serial_no: - val_rate = get_valuation_rate( - row.item_code, - row.warehouse, - self.doctype, - self.name, - company=self.company, - batch_no=row.batch_no, - serial_and_batch_bundle=row.current_serial_and_batch_bundle, + val_rate = get_incoming_rate( + frappe._dict( + { + "item_code": row.item_code, + "warehouse": row.warehouse, + "qty": current_qty * -1, + "serial_and_batch_bundle": row.current_serial_and_batch_bundle, + "batch_no": row.batch_no, + "voucher_type": self.doctype, + "voucher_no": self.name, + "company": self.company, + "posting_date": self.posting_date, + "posting_time": self.posting_time, + } + ) ) row.current_valuation_rate = val_rate diff --git a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py index 48d67c2cf465..9bb6ba9ec90e 100644 --- a/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py +++ b/erpnext/stock/doctype/stock_reconciliation/test_stock_reconciliation.py @@ -4,6 +4,7 @@ # ERPNext - web based ERP (http://erpnext.com) # For license information, please see license.txt +import json import frappe from frappe.tests.utils import FrappeTestCase, change_settings @@ -1182,6 +1183,98 @@ def test_not_reconcile_all_serial_nos(self): self.assertAlmostEqual(row.incoming_rate, 1000.00) self.assertEqual(row.serial_no, serial_nos[row.idx - 1]) + def test_stock_reco_with_legacy_batch(self): + from erpnext.stock.doctype.batch.batch import get_batch_qty + + batch_item_code = self.make_item( + "Test Batch Item Legacy Batch 1", + { + "is_stock_item": 1, + "has_batch_no": 1, + "create_new_batch": 1, + "batch_number_series": "BH1-NRALL-S-.###", + }, + ).name + + warehouse = "_Test Warehouse - _TC" + + frappe.flags.ignore_serial_batch_bundle_validation = True + frappe.flags.use_serial_and_batch_fields = True + + batch_id = "BH1-NRALL-S-0001" + if not frappe.db.exists("Batch", batch_id): + batch_doc = frappe.get_doc( + { + "doctype": "Batch", + "batch_id": batch_id, + "item": batch_item_code, + "use_batchwise_valuation": 0, + } + ).insert(ignore_permissions=True) + + self.assertTrue(batch_doc.use_batchwise_valuation) + + stock_queue = [] + qty_after_transaction = 0 + balance_value = 0 + i = 0 + for qty, valuation in {10: 100, 20: 200}.items(): + i += 1 + stock_queue.append([qty, valuation]) + qty_after_transaction += qty + balance_value += qty_after_transaction * valuation + + doc = frappe.get_doc( + { + "doctype": "Stock Ledger Entry", + "posting_date": add_days(nowdate(), -2 * i), + "posting_time": nowtime(), + "batch_no": batch_id, + "incoming_rate": valuation, + "qty_after_transaction": qty_after_transaction, + "stock_value_difference": valuation * qty, + "balance_value": balance_value, + "valuation_rate": balance_value / qty_after_transaction, + "actual_qty": qty, + "item_code": batch_item_code, + "warehouse": "_Test Warehouse - _TC", + "stock_queue": json.dumps(stock_queue), + } + ) + + doc.flags.ignore_permissions = True + doc.flags.ignore_mandatory = True + doc.flags.ignore_links = True + doc.flags.ignore_validate = True + doc.submit() + doc.reload() + + frappe.flags.ignore_serial_batch_bundle_validation = False + frappe.flags.use_serial_and_batch_fields = False + + batch_doc = frappe.get_doc("Batch", batch_id) + + qty = get_batch_qty(batch_id, warehouse, batch_item_code) + self.assertEqual(qty, 30) + + sr = create_stock_reconciliation( + item_code=batch_item_code, + posting_date=add_days(nowdate(), -3), + posting_time=nowtime(), + warehouse=warehouse, + qty=100, + rate=1000, + reconcile_all_serial_batch=0, + batch_no=batch_id, + use_serial_batch_fields=1, + ) + + self.assertEqual(sr.items[0].current_qty, 20) + self.assertEqual(sr.items[0].qty, 100) + + qty = get_batch_qty(batch_id, warehouse, batch_item_code) + self.assertEqual(qty, 110) + def create_batch_item_with_batch(item_name, batch_id): batch_item_doc = create_item(item_name, is_stock_item=1)