fix(stock): handle legacy single sle recon entries

(cherry picked from commit 7e6bbcc3fb)
This commit is contained in:
kavin-114
2026-03-06 16:07:34 +05:30
committed by Rohit Waghchaure
parent d7237519a2
commit dd0613a4a8

View File

@@ -7,6 +7,7 @@ from operator import itemgetter
import frappe
from frappe import _
from frappe.query_builder.functions import Count
from frappe.utils import cint, date_diff, flt, get_datetime
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
@@ -240,9 +241,9 @@ class FIFOSlots:
Returns dict of the foll.g structure:
Key = Item A / (Item A, Warehouse A)
Key: {
'details' -> Dict: ** item details **,
'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock,
consumed/updated and maintained via FIFO. **
'details' -> Dict: ** item details **,
'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock,
consumed/updated and maintained via FIFO. **
}
"""
from erpnext.stock.serial_batch_bundle import get_serial_nos_from_bundle
@@ -253,16 +254,33 @@ class FIFOSlots:
if stock_ledger_entries is None:
bundle_wise_serial_nos = self.__get_bundle_wise_serial_nos()
# prepare single sle voucher detail lookup
self.prepare_stock_reco_voucher_wise_count()
with frappe.db.unbuffered_cursor():
if stock_ledger_entries is None:
stock_ledger_entries = self.__get_stock_ledger_entries()
for d in stock_ledger_entries:
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
if d.voucher_type == "Stock Reconciliation":
if d.voucher_type == "Stock Reconciliation" and (
not d.batch_no or d.serial_no or d.serial_and_batch_bundle
):
if d.voucher_detail_no in self.stock_reco_voucher_wise_count:
# for legacy recon with single sle has qty_after_transaction and stock_value_difference without outward entry
# for exisitng handle emptying the existing queue and details.
d.stock_value_difference = flt(d.qty_after_transaction * d.valuation_rate)
d.actual_qty = d.qty_after_transaction
self.item_details[key]["qty_after_transaction"] = 0
self.item_details[key]["total_qty"] = 0
fifo_queue.clear()
else:
d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
elif d.voucher_type == "Stock Reconciliation":
# get difference in qty shift as actual qty
prev_balance_qty = self.item_details[key].get("qty_after_transaction", 0)
d.actual_qty = flt(d.qty_after_transaction) - flt(prev_balance_qty)
serial_nos = get_serial_nos(d.serial_no) if d.serial_no else []
@@ -280,6 +298,14 @@ class FIFOSlots:
self.__update_balances(d, key)
# handle serial nos misconsumption
if d.has_serial_no:
qty_after = cint(self.item_details[key]["qty_after_transaction"])
if qty_after <= 0:
fifo_queue.clear()
elif len(fifo_queue) > qty_after:
fifo_queue[:] = fifo_queue[:qty_after]
# Note that stock_ledger_entries is an iterator, you can not reuse it like a list
del stock_ledger_entries
@@ -406,7 +432,6 @@ class FIFOSlots:
def __update_balances(self, row: dict, key: tuple | str):
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction
if "total_qty" not in self.item_details[key]:
self.item_details[key]["total_qty"] = row.actual_qty
else:
@@ -462,6 +487,7 @@ class FIFOSlots:
sle.posting_date,
sle.voucher_type,
sle.voucher_no,
sle.voucher_detail_no,
sle.serial_no,
sle.batch_no,
sle.qty_after_transaction,
@@ -558,3 +584,36 @@ class FIFOSlots:
warehouse_results = [x[0] for x in warehouse_results]
return sle_query.where(sle.warehouse.isin(warehouse_results))
def prepare_stock_reco_voucher_wise_count(self):
self.stock_reco_voucher_wise_count = frappe._dict()
doctype = frappe.qb.DocType("Stock Ledger Entry")
item = frappe.qb.DocType("Item")
query = (
frappe.qb.from_(doctype)
.inner_join(item)
.on(doctype.item_code == item.name)
.select(doctype.voucher_detail_no, Count(doctype.name).as_("count"))
.where(
(doctype.voucher_type == "Stock Reconciliation")
& (doctype.docstatus < 2)
& (doctype.is_cancelled == 0)
)
.groupby(doctype.voucher_detail_no)
)
data = query.run(as_dict=True)
if not data:
return
for row in data:
if row.count != 1:
continue
sr_item = frappe.db.get_value(
"Stock Reconciliation Item", row.voucher_detail_no, ["current_qty", "qty"], as_dict=True
)
if sr_item.qty and sr_item.current_qty:
self.stock_reco_voucher_wise_count[row.voucher_detail_no] = sr_item.current_qty