mirror of
https://github.com/frappe/erpnext.git
synced 2026-04-12 11:25:09 +00:00
fix(stock): handle legacy single sle recon entries
(cherry picked from commit 7e6bbcc3fb)
This commit is contained in:
committed by
Rohit Waghchaure
parent
d9cd09b24a
commit
d09207ab82
@@ -7,6 +7,7 @@ from operator import itemgetter
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
|
from frappe.query_builder.functions import Count
|
||||||
from frappe.utils import cint, date_diff, flt, get_datetime
|
from frappe.utils import cint, date_diff, flt, get_datetime
|
||||||
|
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
@@ -238,9 +239,9 @@ class FIFOSlots:
|
|||||||
Returns dict of the foll.g structure:
|
Returns dict of the foll.g structure:
|
||||||
Key = Item A / (Item A, Warehouse A)
|
Key = Item A / (Item A, Warehouse A)
|
||||||
Key: {
|
Key: {
|
||||||
'details' -> Dict: ** item details **,
|
'details' -> Dict: ** item details **,
|
||||||
'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock,
|
'fifo_queue' -> List: ** list of lists containing entries/slots for existing stock,
|
||||||
consumed/updated and maintained via FIFO. **
|
consumed/updated and maintained via FIFO. **
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
from erpnext.stock.serial_batch_bundle import get_serial_nos_from_bundle
|
from erpnext.stock.serial_batch_bundle import get_serial_nos_from_bundle
|
||||||
@@ -251,16 +252,33 @@ class FIFOSlots:
|
|||||||
if stock_ledger_entries is None:
|
if stock_ledger_entries is None:
|
||||||
bundle_wise_serial_nos = self.__get_bundle_wise_serial_nos()
|
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():
|
with frappe.db.unbuffered_cursor():
|
||||||
if stock_ledger_entries is None:
|
if stock_ledger_entries is None:
|
||||||
stock_ledger_entries = self.__get_stock_ledger_entries()
|
stock_ledger_entries = self.__get_stock_ledger_entries()
|
||||||
|
|
||||||
for d in stock_ledger_entries:
|
for d in stock_ledger_entries:
|
||||||
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
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
|
# 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)
|
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 []
|
serial_nos = get_serial_nos(d.serial_no) if d.serial_no else []
|
||||||
@@ -278,6 +296,14 @@ class FIFOSlots:
|
|||||||
|
|
||||||
self.__update_balances(d, key)
|
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
|
# Note that stock_ledger_entries is an iterator, you can not reuse it like a list
|
||||||
del stock_ledger_entries
|
del stock_ledger_entries
|
||||||
|
|
||||||
@@ -404,7 +430,6 @@ class FIFOSlots:
|
|||||||
|
|
||||||
def __update_balances(self, row: dict, key: tuple | str):
|
def __update_balances(self, row: dict, key: tuple | str):
|
||||||
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction
|
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction
|
||||||
|
|
||||||
if "total_qty" not in self.item_details[key]:
|
if "total_qty" not in self.item_details[key]:
|
||||||
self.item_details[key]["total_qty"] = row.actual_qty
|
self.item_details[key]["total_qty"] = row.actual_qty
|
||||||
else:
|
else:
|
||||||
@@ -460,6 +485,7 @@ class FIFOSlots:
|
|||||||
sle.posting_date,
|
sle.posting_date,
|
||||||
sle.voucher_type,
|
sle.voucher_type,
|
||||||
sle.voucher_no,
|
sle.voucher_no,
|
||||||
|
sle.voucher_detail_no,
|
||||||
sle.serial_no,
|
sle.serial_no,
|
||||||
sle.batch_no,
|
sle.batch_no,
|
||||||
sle.qty_after_transaction,
|
sle.qty_after_transaction,
|
||||||
@@ -555,3 +581,36 @@ class FIFOSlots:
|
|||||||
warehouse_results = [x[0] for x in warehouse_results]
|
warehouse_results = [x[0] for x in warehouse_results]
|
||||||
|
|
||||||
return sle_query.where(sle.warehouse.isin(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
|
||||||
|
|||||||
Reference in New Issue
Block a user