From 9763a0b6bf16950276883f3ede52ecf17f6aebba Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 10 Dec 2024 15:30:53 +0530 Subject: [PATCH] perf: batchwise balance history report --- .../repost_item_valuation.py | 14 +-- .../stock_closing_entry.py | 95 +++++++++++-------- .../batch_wise_balance_history.py | 33 ++++++- 3 files changed, 90 insertions(+), 52 deletions(-) diff --git a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py index 165c883a1fe4..4656425dabd3 100644 --- a/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py +++ b/erpnext/stock/doctype/repost_item_valuation/repost_item_valuation.py @@ -100,10 +100,10 @@ def validate_period_closing_voucher(self): # Stock Closing Balance closing_stock = self.get_closing_stock_balance() if closing_stock and closing_stock[0].name: - name = get_link_to_form("Stock Closing Balance", closing_stock[0].name) + name = get_link_to_form("Stock Closing Entry", closing_stock[0].stock_closing_entry) to_date = frappe.format(closing_stock[0].to_date, "Date") frappe.throw( - _("Due to stock closing balance {0}, you cannot repost item valuation before {1}").format( + _("Due to stock closing entry {0}, you cannot repost item valuation before {1}").format( name, to_date ) ) @@ -111,16 +111,12 @@ def validate_period_closing_voucher(self): def get_closing_stock_balance(self): filters = { "company": self.company, - "status": "Completed", - "docstatus": 1, "to_date": (">=", self.posting_date), } - for field in ["warehouse", "item_code"]: - if self.get(field): - filters.update({field: ("in", ["", self.get(field)])}) - - return frappe.get_all("Stock Closing Balance", fields=["name", "posting_date"], filters=filters, limit=1) + return frappe.get_all( + "Stock Closing Balance", fields=["stock_closing_entry", "posting_date"], filters=filters, limit=1 + ) @staticmethod def get_max_period_closing_date(company): diff --git a/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py b/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py index e58ab04d2018..f3217fecc11f 100644 --- a/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py +++ b/erpnext/stock/doctype/stock_closing_entry/stock_closing_entry.py @@ -103,8 +103,7 @@ def enqueue_job(self): @frappe.whitelist() def regenerate_closing_balance(self): self.remove_stock_closing() - # self.enqueue_job() - self.create_stock_closing_balance_entries() + self.enqueue_job() def create_stock_closing_balance_entries(self): from erpnext.stock.utils import get_combine_datetime @@ -113,14 +112,14 @@ def create_stock_closing_balance_entries(self): entries = stk_cl_obj.get_stock_closing_entries() - for row in entries: - data = entries[row] + for key in entries: + row = entries[key] - if data.actual_qty == 0.0 and data.stock_value_difference == 0.0: + if row.actual_qty == 0.0 and row.stock_value_difference == 0.0: continue new_doc = frappe.new_doc("Stock Closing Balance") - new_doc.update(data) + new_doc.update(row) new_doc.posting_date = self.to_date new_doc.posting_time = nowtime() new_doc.posting_datetime = get_combine_datetime(self.to_date, new_doc.posting_time) @@ -169,10 +168,10 @@ def get_stock_closing_entries(self): closing_stock = frappe._dict() for row in sl_entries: - keys = self.get_keys(row) - for data in keys: - for fields, values in data.items(): - key = values + dimensions_keys = self.get_keys(row) + for dimension_key in dimensions_keys: + for dimension_fields, dimension_values in dimension_key.items(): + key = dimension_values if key in closing_stock: closing_stock[key].actual_qty += row.sabb_qty or row.actual_qty @@ -183,36 +182,42 @@ def get_stock_closing_entries(self): if not row.actual_qty and row.qty_after_transaction: closing_stock[key].actual_qty = row.qty_after_transaction else: - item_details = frappe.get_cached_value( - "Item", row.item_code, ["item_group", "item_name", "stock_uom"], as_dict=1 - ) + entries = self.get_initialized_entry(row, dimension_fields) + closing_stock[key] = entries - inventory_dimension_key = "" - if fields not in [("item_code", "warehouse"), ("item_code", "warehouse", "batch_no")]: - inventory_dimension_key = json.dumps(fields) - - closing_stock[key] = frappe._dict( - { - "item_code": row.item_code, - "warehouse": row.warehouse, - "actual_qty": row.sabb_qty or row.actual_qty or row.qty_after_transaction, - "stock_value_difference": ( - row.sabb_stock_value_difference or row.stock_value_difference - ), - "item_group": item_details.item_group, - "item_name": item_details.item_name, - "stock_uom": item_details.stock_uom, - "inventory_dimension_key": inventory_dimension_key, - "batch_no": row.batch_no or row.sabb_batch_no, - } - ) + return closing_stock - # To update dimensions - for field in fields: - if row.get(field): - closing_stock[key][field] = row.get(field) + def get_initialized_entry(self, row, dimension_fields): + item_details = frappe.get_cached_value( + "Item", row.item_code, ["item_group", "item_name", "stock_uom"], as_dict=1 + ) - return closing_stock + inventory_dimension_key = None + if dimension_fields not in [("item_code", "warehouse"), ("item_code", "warehouse", "batch_no")]: + inventory_dimension_key = json.dumps(dimension_fields) + + entry = frappe._dict( + { + "item_code": row.item_code, + "warehouse": row.warehouse, + "actual_qty": row.sabb_qty or row.actual_qty or row.qty_after_transaction, + "stock_value_difference": row.sabb_stock_value_difference or row.stock_value_difference, + "item_group": item_details.item_group, + "item_name": item_details.item_name, + "stock_uom": item_details.stock_uom, + "inventory_dimension_key": inventory_dimension_key, + } + ) + + if row.sabb_batch_no: + row.batch_no = row.sabb_batch_no + + # To update dimensions + for field in dimension_fields: + if row.get(field): + entry[field] = row.get(field) + + return entry def get_sle_entries(self): sl_entries = [] @@ -330,7 +335,7 @@ def get_keys(self, row): {("item_code", "warehouse", "batch_no"): (row.item_code, row.warehouse, row.sabb_batch_no)} ) - dimensions_has_value = [] + dimension_fields = [] dimension_values = [] for dimension in self.inv_dimensions: if row.get(dimension.fieldname): @@ -344,13 +349,13 @@ def get_keys(self, row): } ) - dimensions_has_value.append(dimension.fieldname) + dimension_fields.append(dimension.fieldname) dimension_values.append(row.get(dimension.fieldname)) - if dimensions_has_value and len(dimensions_has_value) > 1: + if dimension_fields and len(dimension_fields) > 1: keys.append( { - ("item_code", "warehouse", *dimensions_has_value): ( + ("item_code", "warehouse", *dimension_fields): ( row.item_code, row.warehouse, *dimension_values, @@ -360,7 +365,7 @@ def get_keys(self, row): return keys - def get_stock_closing_balance(self, kwargs): + def get_stock_closing_balance(self, kwargs, for_batch=False): if not self.last_closing_balance: return [] @@ -377,4 +382,10 @@ def get_stock_closing_balance(self, kwargs): else: query = query.where(table[key] == value) + if for_batch: + query = query.where(table.batch_no.isnotnull()) + query = query.where( + table.inventory_dimension_key.isnull() | (table.inventory_dimension_key == "") + ) + return query.run(as_dict=True) diff --git a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py index eeb331318ba2..9ae454a3a2cd 100644 --- a/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py +++ b/erpnext/stock/report/batch_wise_balance_history/batch_wise_balance_history.py @@ -8,6 +8,7 @@ from pypika import functions as fn from erpnext.deprecation_dumpster import deprecated +from erpnext.stock.doctype.stock_closing_entry.stock_closing_entry import StockClosing from erpnext.stock.doctype.warehouse.warehouse import apply_warehouse_filter SLE_COUNT_LIMIT = 10_000 @@ -94,12 +95,36 @@ def get_columns(filters): def get_stock_ledger_entries(filters): - entries = get_stock_ledger_entries_for_batch_no(filters) + entries = [] + stk_cl_obj = StockClosing(filters.company, filters.from_date, filters.from_date) + if stk_cl_obj.last_closing_balance: + entries += get_stock_closing_balance(stk_cl_obj, filters) + filters.start_from = stk_cl_obj.last_closing_balance.to_date + + entries += get_stock_ledger_entries_for_batch_no(filters) entries += get_stock_ledger_entries_for_batch_bundle(filters) + return entries +def get_stock_closing_balance(stk_cl_obj, filters): + query_filters = {} + for field in ["item_code", "warehouse", "company", "batch_no"]: + if filters.get(field): + query_filters[field] = filters.get(field) + + if filters.warehouse_type: + warehouses = frappe.get_all( + "Warehouse", + filters={"warehouse_type": filters.warehouse_type, "is_group": 0}, + pluck="name", + ) + query_filters["warehouse"] = warehouses + + return stk_cl_obj.get_stock_closing_balance(query_filters, for_batch=True) + + @deprecated(f"{__name__}.get_stock_ledger_entries_for_batch_no", "unknown", "v16", "No known instructions.") def get_stock_ledger_entries_for_batch_no(filters): if not filters.get("from_date"): @@ -144,6 +169,9 @@ def get_stock_ledger_entries_for_batch_no(filters): if filters.get(field): query = query.where(sle[field] == filters.get(field)) + if filters.start_from: + query = query.where(sle.posting_datetime > get_datetime(filters.start_from)) + return query.run(as_dict=True) or [] @@ -190,6 +218,9 @@ def get_stock_ledger_entries_for_batch_bundle(filters): else: query = query.where(sle[field] == filters.get(field)) + if filters.start_from: + query = query.where(sle.posting_date > getdate(filters.start_from)) + return query.run(as_dict=True) or []