Skip to content

Commit 124ad72

Browse files
Merge pull request #46984 from frappe/mergify/bp/version-14/pr-46983
perf: stock ageing report generation (backport #46983)
2 parents 24681bd + eaa2974 commit 124ad72

2 files changed

Lines changed: 106 additions & 8 deletions

File tree

erpnext/stock/report/stock_ageing/stock_ageing.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,5 +81,11 @@ frappe.query_reports["Stock Ageing"] = {
8181
fieldtype: "Check",
8282
default: 0,
8383
},
84+
{
85+
fieldname: "ignore_closing_balance",
86+
label: __("Ignore Closing Balance"),
87+
fieldtype: "Check",
88+
default: 0,
89+
},
8490
],
8591
};

erpnext/stock/report/stock_ageing/stock_ageing.py

Lines changed: 100 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66

77
import frappe
88
from frappe import _
9-
from frappe.utils import cint, date_diff, flt, get_datetime
9+
from frappe.query_builder import Order
10+
from frappe.utils import add_days, cint, date_diff, flt, get_date_str, get_datetime, getdate
1011

1112
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
1213

@@ -49,7 +50,13 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li
4950
latest_age = date_diff(to_date, fifo_queue[-1][1])
5051
range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict)
5152

52-
row = [details.name, details.item_name, details.description, details.item_group, details.brand]
53+
row = [
54+
details.name or details.item_code,
55+
details.item_name,
56+
details.description,
57+
details.item_group,
58+
details.brand,
59+
]
5360

5461
if filters.get("show_warehouse_wise_stock"):
5562
row.append(details.warehouse)
@@ -217,6 +224,67 @@ def __init__(self, filters: dict | None = None, sle: list | None = None):
217224
self.filters = filters
218225
self.sle = sle
219226

227+
def get_closing_balance(self):
228+
if self.filters.get("ignore_closing_balance"):
229+
return []
230+
231+
if (
232+
self.filters.get("item_code")
233+
or self.filters.get("warehouse")
234+
or self.filters.get("warehouse_type")
235+
):
236+
return
237+
238+
if self.sle:
239+
return
240+
241+
table = frappe.qb.DocType("Closing Stock Balance")
242+
243+
query = (
244+
frappe.qb.from_(table)
245+
.select(table.name, table.to_date)
246+
.where(
247+
(table.docstatus == 1)
248+
& (table.company == self.filters.company)
249+
& (table.to_date < self.filters.get("to_date"))
250+
& (table.status == "Completed")
251+
)
252+
.orderby(table.to_date, order=Order.desc)
253+
.limit(1)
254+
)
255+
256+
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
257+
if self.filters.get(fieldname):
258+
query = query.where(table[fieldname] == self.filters.get(fieldname))
259+
260+
return query.run(as_dict=True)
261+
262+
def prepare_stock_ageing_from_stock_closing_balance(self):
263+
closing_balance = self.get_closing_balance()
264+
if not closing_balance:
265+
return
266+
267+
self.start_from = add_days(closing_balance[0].to_date, 1)
268+
closing_data = frappe.get_doc("Closing Stock Balance", closing_balance[0].name).get_prepared_data()
269+
stock_ledger_entries = closing_data.get("data")
270+
271+
for d in stock_ledger_entries:
272+
if isinstance(d, dict):
273+
d = frappe._dict(d)
274+
275+
d.actual_qty = d.bal_qty
276+
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
277+
serial_nos = d.serial_no if d.serial_no else []
278+
if fifo_queue and isinstance(fifo_queue[0][0], str):
279+
d.has_serial_no = 1
280+
281+
if d.actual_qty > 0:
282+
self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos)
283+
else:
284+
self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos)
285+
286+
self.__update_balances(d, key)
287+
220288
def generate(self) -> dict:
221289
"""
222290
Returns dict of the foll.g structure:
@@ -227,6 +295,9 @@ def generate(self) -> dict:
227295
consumed/updated and maintained via FIFO. **
228296
}
229297
"""
298+
self.start_from = None
299+
self.prepare_stock_ageing_from_stock_closing_balance()
300+
230301
stock_ledger_entries = self.sle
231302

232303
_system_settings = frappe.get_cached_doc("System Settings")
@@ -259,15 +330,32 @@ def generate(self) -> dict:
259330

260331
return self.item_details
261332

333+
def format_fifo_queue(self, fifo_queue: list) -> list:
334+
if not fifo_queue:
335+
return []
336+
337+
fifo_queue = [[x[0], getdate(x[1])] for x in fifo_queue]
338+
return fifo_queue
339+
262340
def __init_key_stores(self, row: dict) -> tuple:
263341
"Initialise keys and FIFO Queue."
264342

265-
key = (row.name, row.warehouse)
266-
self.item_details.setdefault(key, {"details": row, "fifo_queue": []})
343+
if not row.name:
344+
key = (row.item_code, row.warehouse)
345+
else:
346+
key = (row.name, row.warehouse)
347+
348+
if key not in self.item_details:
349+
row.fifo_queue = self.format_fifo_queue(row.fifo_queue)
350+
351+
self.item_details.setdefault(key, {"details": row, "fifo_queue": row.fifo_queue or []})
352+
267353
fifo_queue = self.item_details[key]["fifo_queue"]
354+
transferred_item_key = None
268355

269-
transferred_item_key = (row.voucher_no, row.name, row.warehouse)
270-
self.transferred_item_details.setdefault(transferred_item_key, [])
356+
if row.voucher_no:
357+
transferred_item_key = (row.voucher_no, row.name, row.warehouse)
358+
self.transferred_item_details.setdefault(transferred_item_key, [])
271359

272360
return key, fifo_queue, transferred_item_key
273361

@@ -351,10 +439,10 @@ def add_to_fifo_queue(slot):
351439
transfer_qty_to_pop = 0
352440

353441
def __update_balances(self, row: dict, key: tuple | str):
354-
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction
442+
self.item_details[key]["qty_after_transaction"] = row.qty_after_transaction or row.bal_qty
355443

356444
if "total_qty" not in self.item_details[key]:
357-
self.item_details[key]["total_qty"] = row.actual_qty
445+
self.item_details[key]["total_qty"] = row.actual_qty or row.bal_qty
358446
else:
359447
self.item_details[key]["total_qty"] += row.actual_qty
360448

@@ -417,6 +505,10 @@ def __get_stock_ledger_entries(self) -> list[dict]:
417505
)
418506
)
419507

508+
if self.start_from:
509+
from_date = get_datetime(get_date_str(self.start_from) + " 00:00:00")
510+
sle_query = sle_query.where(sle.posting_datetime >= from_date)
511+
420512
if self.filters.get("warehouse"):
421513
sle_query = self.__get_warehouse_conditions(sle, sle_query)
422514
elif self.filters.get("warehouse_type"):

0 commit comments

Comments
 (0)