Merge pull request #34022 from ruthra-kumar/gross_profit_memory_and_performance_issue

perf: Gross Profit report will fetch SLE's on demand and memoize
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index e23265b..fde4de8 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -395,6 +395,7 @@
 
 class GrossProfitGenerator(object):
 	def __init__(self, filters=None):
+		self.sle = {}
 		self.data = []
 		self.average_buying_rate = {}
 		self.filters = frappe._dict(filters)
@@ -404,7 +405,6 @@
 		if filters.group_by == "Invoice":
 			self.group_items_by_invoice()
 
-		self.load_stock_ledger_entries()
 		self.load_product_bundle()
 		self.load_non_stock_items()
 		self.get_returned_invoice_items()
@@ -633,7 +633,7 @@
 			return flt(row.qty) * item_rate
 
 		else:
-			my_sle = self.sle.get((item_code, row.warehouse))
+			my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
 			if (row.update_stock or row.dn_detail) and my_sle:
 				parenttype, parent = row.parenttype, row.parent
 				if row.dn_detail:
@@ -651,7 +651,7 @@
 					dn["item_row"],
 					dn["warehouse"],
 				)
-				my_sle = self.sle.get((item_code, warehouse))
+				my_sle = self.get_stock_ledger_entries(item_code, row.warehouse)
 				return self.calculate_buying_amount_from_sle(
 					row, my_sle, parenttype, parent, item_row, item_code
 				)
@@ -667,15 +667,12 @@
 	def get_buying_amount_from_so_dn(self, sales_order, so_detail, item_code):
 		from frappe.query_builder.functions import Sum
 
-		delivery_note = frappe.qb.DocType("Delivery Note")
 		delivery_note_item = frappe.qb.DocType("Delivery Note Item")
 
 		query = (
-			frappe.qb.from_(delivery_note)
-			.inner_join(delivery_note_item)
-			.on(delivery_note.name == delivery_note_item.parent)
+			frappe.qb.from_(delivery_note_item)
 			.select(Sum(delivery_note_item.incoming_rate * delivery_note_item.stock_qty))
-			.where(delivery_note.docstatus == 1)
+			.where(delivery_note_item.docstatus == 1)
 			.where(delivery_note_item.item_code == item_code)
 			.where(delivery_note_item.against_sales_order == sales_order)
 			.where(delivery_note_item.so_detail == so_detail)
@@ -947,24 +944,36 @@
 			"Item", item_code, ["item_name", "description", "item_group", "brand"]
 		)
 
-	def load_stock_ledger_entries(self):
-		res = frappe.db.sql(
-			"""select item_code, voucher_type, voucher_no,
-				voucher_detail_no, stock_value, warehouse, actual_qty as qty
-			from `tabStock Ledger Entry`
-			where company=%(company)s and is_cancelled = 0
-			order by
-				item_code desc, warehouse desc, posting_date desc,
-				posting_time desc, creation desc""",
-			self.filters,
-			as_dict=True,
-		)
-		self.sle = {}
-		for r in res:
-			if (r.item_code, r.warehouse) not in self.sle:
-				self.sle[(r.item_code, r.warehouse)] = []
+	def get_stock_ledger_entries(self, item_code, warehouse):
+		if item_code and warehouse:
+			if (item_code, warehouse) not in self.sle:
+				sle = qb.DocType("Stock Ledger Entry")
+				res = (
+					qb.from_(sle)
+					.select(
+						sle.item_code,
+						sle.voucher_type,
+						sle.voucher_no,
+						sle.voucher_detail_no,
+						sle.stock_value,
+						sle.warehouse,
+						sle.actual_qty.as_("qty"),
+					)
+					.where(
+						(sle.company == self.filters.company)
+						& (sle.item_code == item_code)
+						& (sle.warehouse == warehouse)
+						& (sle.is_cancelled == 0)
+					)
+					.orderby(sle.item_code)
+					.orderby(sle.warehouse, sle.posting_date, sle.posting_time, sle.creation, order=Order.desc)
+					.run(as_dict=True)
+				)
 
-			self.sle[(r.item_code, r.warehouse)].append(r)
+				self.sle[(item_code, warehouse)] = res
+
+			return self.sle[(item_code, warehouse)]
+		return []
 
 	def load_product_bundle(self):
 		self.product_bundles = {}