mirror of
https://github.com/frappe/erpnext.git
synced 2026-03-03 02:25:00 +00:00
perf: stock ageing report generation
This commit is contained in:
@@ -81,5 +81,11 @@ frappe.query_reports["Stock Ageing"] = {
|
|||||||
fieldtype: "Check",
|
fieldtype: "Check",
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldname: "ignore_closing_balance",
|
||||||
|
label: __("Ignore Closing Balance"),
|
||||||
|
fieldtype: "Check",
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ from operator import itemgetter
|
|||||||
|
|
||||||
import frappe
|
import frappe
|
||||||
from frappe import _
|
from frappe import _
|
||||||
from frappe.utils import cint, date_diff, flt, get_datetime
|
from frappe.query_builder import Order
|
||||||
|
from frappe.utils import add_days, cint, date_diff, flt, get_date_str, get_datetime, getdate
|
||||||
|
|
||||||
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
|
||||||
|
|
||||||
@@ -49,7 +50,13 @@ def format_report_data(filters: Filters, item_details: dict, to_date: str) -> li
|
|||||||
latest_age = date_diff(to_date, fifo_queue[-1][1])
|
latest_age = date_diff(to_date, fifo_queue[-1][1])
|
||||||
range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict)
|
range1, range2, range3, above_range3 = get_range_age(filters, fifo_queue, to_date, item_dict)
|
||||||
|
|
||||||
row = [details.name, details.item_name, details.description, details.item_group, details.brand]
|
row = [
|
||||||
|
details.name or details.item_code,
|
||||||
|
details.item_name,
|
||||||
|
details.description,
|
||||||
|
details.item_group,
|
||||||
|
details.brand,
|
||||||
|
]
|
||||||
|
|
||||||
if filters.get("show_warehouse_wise_stock"):
|
if filters.get("show_warehouse_wise_stock"):
|
||||||
row.append(details.warehouse)
|
row.append(details.warehouse)
|
||||||
@@ -217,6 +224,67 @@ class FIFOSlots:
|
|||||||
self.filters = filters
|
self.filters = filters
|
||||||
self.sle = sle
|
self.sle = sle
|
||||||
|
|
||||||
|
def get_closing_balance(self):
|
||||||
|
if self.filters.get("ignore_closing_balance"):
|
||||||
|
return []
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.filters.get("item_code")
|
||||||
|
or self.filters.get("warehouse")
|
||||||
|
or self.filters.get("warehouse_type")
|
||||||
|
):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.sle:
|
||||||
|
return
|
||||||
|
|
||||||
|
table = frappe.qb.DocType("Closing Stock Balance")
|
||||||
|
|
||||||
|
query = (
|
||||||
|
frappe.qb.from_(table)
|
||||||
|
.select(table.name, table.to_date)
|
||||||
|
.where(
|
||||||
|
(table.docstatus == 1)
|
||||||
|
& (table.company == self.filters.company)
|
||||||
|
& (table.to_date < self.filters.get("to_date"))
|
||||||
|
& (table.status == "Completed")
|
||||||
|
)
|
||||||
|
.orderby(table.to_date, order=Order.desc)
|
||||||
|
.limit(1)
|
||||||
|
)
|
||||||
|
|
||||||
|
for fieldname in ["warehouse", "item_code", "item_group", "warehouse_type"]:
|
||||||
|
if self.filters.get(fieldname):
|
||||||
|
query = query.where(table[fieldname] == self.filters.get(fieldname))
|
||||||
|
|
||||||
|
return query.run(as_dict=True)
|
||||||
|
|
||||||
|
def prepare_stock_ageing_from_stock_closing_balance(self):
|
||||||
|
closing_balance = self.get_closing_balance()
|
||||||
|
if not closing_balance:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.start_from = add_days(closing_balance[0].to_date, 1)
|
||||||
|
closing_data = frappe.get_doc("Closing Stock Balance", closing_balance[0].name).get_prepared_data()
|
||||||
|
stock_ledger_entries = closing_data.get("data")
|
||||||
|
|
||||||
|
for d in stock_ledger_entries:
|
||||||
|
if isinstance(d, dict):
|
||||||
|
d = frappe._dict(d)
|
||||||
|
|
||||||
|
d.actual_qty = d.bal_qty
|
||||||
|
key, fifo_queue, transferred_item_key = self.__init_key_stores(d)
|
||||||
|
serial_nos = d.serial_no if d.serial_no else []
|
||||||
|
if fifo_queue and isinstance(fifo_queue[0][0], str):
|
||||||
|
d.has_serial_no = 1
|
||||||
|
|
||||||
|
if d.actual_qty > 0:
|
||||||
|
self.__compute_incoming_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||||
|
else:
|
||||||
|
self.__compute_outgoing_stock(d, fifo_queue, transferred_item_key, serial_nos)
|
||||||
|
|
||||||
|
self.__update_balances(d, key)
|
||||||
|
|
||||||
def generate(self) -> dict:
|
def generate(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Returns dict of the foll.g structure:
|
Returns dict of the foll.g structure:
|
||||||
@@ -227,6 +295,9 @@ class FIFOSlots:
|
|||||||
consumed/updated and maintained via FIFO. **
|
consumed/updated and maintained via FIFO. **
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
self.start_from = None
|
||||||
|
self.prepare_stock_ageing_from_stock_closing_balance()
|
||||||
|
|
||||||
stock_ledger_entries = self.sle
|
stock_ledger_entries = self.sle
|
||||||
|
|
||||||
_system_settings = frappe.get_cached_doc("System Settings")
|
_system_settings = frappe.get_cached_doc("System Settings")
|
||||||
@@ -259,15 +330,32 @@ class FIFOSlots:
|
|||||||
|
|
||||||
return self.item_details
|
return self.item_details
|
||||||
|
|
||||||
|
def format_fifo_queue(self, fifo_queue: list) -> list:
|
||||||
|
if not fifo_queue:
|
||||||
|
return []
|
||||||
|
|
||||||
|
fifo_queue = [[x[0], getdate(x[1])] for x in fifo_queue]
|
||||||
|
return fifo_queue
|
||||||
|
|
||||||
def __init_key_stores(self, row: dict) -> tuple:
|
def __init_key_stores(self, row: dict) -> tuple:
|
||||||
"Initialise keys and FIFO Queue."
|
"Initialise keys and FIFO Queue."
|
||||||
|
|
||||||
key = (row.name, row.warehouse)
|
if not row.name:
|
||||||
self.item_details.setdefault(key, {"details": row, "fifo_queue": []})
|
key = (row.item_code, row.warehouse)
|
||||||
fifo_queue = self.item_details[key]["fifo_queue"]
|
else:
|
||||||
|
key = (row.name, row.warehouse)
|
||||||
|
|
||||||
transferred_item_key = (row.voucher_no, row.name, row.warehouse)
|
if key not in self.item_details:
|
||||||
self.transferred_item_details.setdefault(transferred_item_key, [])
|
row.fifo_queue = self.format_fifo_queue(row.fifo_queue)
|
||||||
|
|
||||||
|
self.item_details.setdefault(key, {"details": row, "fifo_queue": row.fifo_queue or []})
|
||||||
|
|
||||||
|
fifo_queue = self.item_details[key]["fifo_queue"]
|
||||||
|
transferred_item_key = None
|
||||||
|
|
||||||
|
if row.voucher_no:
|
||||||
|
transferred_item_key = (row.voucher_no, row.name, row.warehouse)
|
||||||
|
self.transferred_item_details.setdefault(transferred_item_key, [])
|
||||||
|
|
||||||
return key, fifo_queue, transferred_item_key
|
return key, fifo_queue, transferred_item_key
|
||||||
|
|
||||||
@@ -351,10 +439,10 @@ class FIFOSlots:
|
|||||||
transfer_qty_to_pop = 0
|
transfer_qty_to_pop = 0
|
||||||
|
|
||||||
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 or row.bal_qty
|
||||||
|
|
||||||
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 or row.bal_qty
|
||||||
else:
|
else:
|
||||||
self.item_details[key]["total_qty"] += row.actual_qty
|
self.item_details[key]["total_qty"] += row.actual_qty
|
||||||
|
|
||||||
@@ -417,6 +505,10 @@ class FIFOSlots:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.start_from:
|
||||||
|
from_date = get_datetime(get_date_str(self.start_from) + " 00:00:00")
|
||||||
|
sle_query = sle_query.where(sle.posting_datetime >= from_date)
|
||||||
|
|
||||||
if self.filters.get("warehouse"):
|
if self.filters.get("warehouse"):
|
||||||
sle_query = self.__get_warehouse_conditions(sle, sle_query)
|
sle_query = self.__get_warehouse_conditions(sle, sle_query)
|
||||||
elif self.filters.get("warehouse_type"):
|
elif self.filters.get("warehouse_type"):
|
||||||
|
|||||||
Reference in New Issue
Block a user