Merge pull request #39659 from rohitwaghchaure/fixed-timeout-error-while-making-auto-mr

perf: timeout for auto material request through reorder level
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 8c43842..dc49023 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -599,7 +599,7 @@
 		if self.doctype in ["Sales Order", "Quotation"]:
 			for item in self.items:
 				item.gross_profit = flt(
-					((item.base_rate - item.valuation_rate) * item.stock_qty), self.precision("amount", item)
+					((item.base_rate - flt(item.valuation_rate)) * item.stock_qty), self.precision("amount", item)
 				)
 
 	def set_customer_address(self):
diff --git a/erpnext/stock/doctype/stock_entry/test_stock_entry.py b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
index 23dacc8..9f3435e 100644
--- a/erpnext/stock/doctype/stock_entry/test_stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/test_stock_entry.py
@@ -1785,6 +1785,48 @@
 
 		self.assertRaises(frappe.ValidationError, se1.cancel)
 
+	def test_auto_reorder_level(self):
+		from erpnext.stock.reorder_item import reorder_item
+
+		item_doc = make_item(
+			"Test Auto Reorder Item - 001",
+			properties={"stock_uom": "Kg", "purchase_uom": "Nos", "is_stock_item": 1},
+			uoms=[{"uom": "Nos", "conversion_factor": 5}],
+		)
+
+		if not frappe.db.exists("Item Reorder", {"parent": item_doc.name}):
+			item_doc.append(
+				"reorder_levels",
+				{
+					"warehouse_reorder_level": 0,
+					"warehouse_reorder_qty": 10,
+					"warehouse": "_Test Warehouse - _TC",
+					"material_request_type": "Purchase",
+				},
+			)
+
+		item_doc.save(ignore_permissions=True)
+
+		frappe.db.set_single_value("Stock Settings", "auto_indent", 1)
+
+		mr_list = reorder_item()
+
+		frappe.db.set_single_value("Stock Settings", "auto_indent", 0)
+		mrs = frappe.get_all(
+			"Material Request Item",
+			fields=["qty", "stock_uom", "stock_qty"],
+			filters={"item_code": item_doc.name, "uom": "Nos"},
+		)
+
+		for mri in mrs:
+			self.assertEqual(mri.stock_uom, "Kg")
+			self.assertEqual(mri.stock_qty, 10)
+			self.assertEqual(mri.qty, 2)
+
+		for mr in mr_list:
+			mr.cancel()
+			mr.delete()
+
 
 def make_serialized_item(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/stock/get_item_details.py b/erpnext/stock/get_item_details.py
index bed5285..1cb1057 100644
--- a/erpnext/stock/get_item_details.py
+++ b/erpnext/stock/get_item_details.py
@@ -86,7 +86,8 @@
 
 	get_party_item_code(args, item, out)
 
-	set_valuation_rate(out, args)
+	if args.get("doctype") in ["Sales Order", "Quotation"]:
+		set_valuation_rate(out, args)
 
 	update_party_blanket_order(args, out)
 
@@ -269,7 +270,9 @@
 	if not item:
 		item = frappe.get_doc("Item", args.get("item_code"))
 
-	if item.variant_of and not item.taxes:
+	if (
+		item.variant_of and not item.taxes and frappe.db.exists("Item Tax", {"parent": item.variant_of})
+	):
 		item.update_template_tables()
 
 	item_defaults = get_item_defaults(item.name, args.company)
diff --git a/erpnext/stock/reorder_item.py b/erpnext/stock/reorder_item.py
index 276531a..59f8b20 100644
--- a/erpnext/stock/reorder_item.py
+++ b/erpnext/stock/reorder_item.py
@@ -34,73 +34,157 @@
 		erpnext.get_default_company() or frappe.db.sql("""select name from tabCompany limit 1""")[0][0]
 	)
 
-	items_to_consider = frappe.db.sql_list(
-		"""select name from `tabItem` item
-		where is_stock_item=1 and has_variants=0
-			and disabled=0
-			and (end_of_life is null or end_of_life='0000-00-00' or end_of_life > %(today)s)
-			and (exists (select name from `tabItem Reorder` ir where ir.parent=item.name)
-				or (variant_of is not null and variant_of != ''
-				and exists (select name from `tabItem Reorder` ir where ir.parent=item.variant_of))
-			)""",
-		{"today": nowdate()},
-	)
+	items_to_consider = get_items_for_reorder()
 
 	if not items_to_consider:
 		return
 
 	item_warehouse_projected_qty = get_item_warehouse_projected_qty(items_to_consider)
 
-	def add_to_material_request(
-		item_code, warehouse, reorder_level, reorder_qty, material_request_type, warehouse_group=None
-	):
-		if warehouse not in warehouse_company:
+	def add_to_material_request(**kwargs):
+		if isinstance(kwargs, dict):
+			kwargs = frappe._dict(kwargs)
+
+		if kwargs.warehouse not in warehouse_company:
 			# a disabled warehouse
 			return
 
-		reorder_level = flt(reorder_level)
-		reorder_qty = flt(reorder_qty)
+		reorder_level = flt(kwargs.reorder_level)
+		reorder_qty = flt(kwargs.reorder_qty)
 
 		# projected_qty will be 0 if Bin does not exist
-		if warehouse_group:
-			projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse_group))
+		if kwargs.warehouse_group:
+			projected_qty = flt(
+				item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse_group)
+			)
 		else:
-			projected_qty = flt(item_warehouse_projected_qty.get(item_code, {}).get(warehouse))
+			projected_qty = flt(
+				item_warehouse_projected_qty.get(kwargs.item_code, {}).get(kwargs.warehouse)
+			)
 
 		if (reorder_level or reorder_qty) and projected_qty <= reorder_level:
 			deficiency = reorder_level - projected_qty
 			if deficiency > reorder_qty:
 				reorder_qty = deficiency
 
-			company = warehouse_company.get(warehouse) or default_company
+			company = warehouse_company.get(kwargs.warehouse) or default_company
 
-			material_requests[material_request_type].setdefault(company, []).append(
-				{"item_code": item_code, "warehouse": warehouse, "reorder_qty": reorder_qty}
+			material_requests[kwargs.material_request_type].setdefault(company, []).append(
+				{
+					"item_code": kwargs.item_code,
+					"warehouse": kwargs.warehouse,
+					"reorder_qty": reorder_qty,
+					"item_details": kwargs.item_details,
+				}
 			)
 
-	for item_code in items_to_consider:
-		item = frappe.get_doc("Item", item_code)
+	for item_code, reorder_levels in items_to_consider.items():
+		for d in reorder_levels:
+			if d.has_variants:
+				continue
 
-		if item.variant_of and not item.get("reorder_levels"):
-			item.update_template_tables()
-
-		if item.get("reorder_levels"):
-			for d in item.get("reorder_levels"):
-				add_to_material_request(
-					item_code,
-					d.warehouse,
-					d.warehouse_reorder_level,
-					d.warehouse_reorder_qty,
-					d.material_request_type,
-					warehouse_group=d.warehouse_group,
-				)
+			add_to_material_request(
+				item_code=item_code,
+				warehouse=d.warehouse,
+				reorder_level=d.warehouse_reorder_level,
+				reorder_qty=d.warehouse_reorder_qty,
+				material_request_type=d.material_request_type,
+				warehouse_group=d.warehouse_group,
+				item_details=frappe._dict(
+					{
+						"item_code": item_code,
+						"name": item_code,
+						"item_name": d.item_name,
+						"item_group": d.item_group,
+						"brand": d.brand,
+						"description": d.description,
+						"stock_uom": d.stock_uom,
+						"purchase_uom": d.purchase_uom,
+					}
+				),
+			)
 
 	if material_requests:
 		return create_material_request(material_requests)
 
 
+def get_items_for_reorder() -> dict[str, list]:
+	reorder_table = frappe.qb.DocType("Item Reorder")
+	item_table = frappe.qb.DocType("Item")
+
+	query = (
+		frappe.qb.from_(reorder_table)
+		.inner_join(item_table)
+		.on(reorder_table.parent == item_table.name)
+		.select(
+			reorder_table.warehouse,
+			reorder_table.warehouse_group,
+			reorder_table.material_request_type,
+			reorder_table.warehouse_reorder_level,
+			reorder_table.warehouse_reorder_qty,
+			item_table.name,
+			item_table.stock_uom,
+			item_table.purchase_uom,
+			item_table.description,
+			item_table.item_name,
+			item_table.item_group,
+			item_table.brand,
+			item_table.variant_of,
+			item_table.has_variants,
+		)
+		.where(
+			(item_table.disabled == 0)
+			& (item_table.is_stock_item == 1)
+			& (
+				(item_table.end_of_life.isnull())
+				| (item_table.end_of_life > nowdate())
+				| (item_table.end_of_life == "0000-00-00")
+			)
+		)
+	)
+
+	data = query.run(as_dict=True)
+	itemwise_reorder = frappe._dict({})
+	for d in data:
+		itemwise_reorder.setdefault(d.name, []).append(d)
+
+	itemwise_reorder = get_reorder_levels_for_variants(itemwise_reorder)
+
+	return itemwise_reorder
+
+
+def get_reorder_levels_for_variants(itemwise_reorder):
+	item_table = frappe.qb.DocType("Item")
+
+	query = (
+		frappe.qb.from_(item_table)
+		.select(
+			item_table.name,
+			item_table.variant_of,
+		)
+		.where(
+			(item_table.disabled == 0)
+			& (item_table.is_stock_item == 1)
+			& (
+				(item_table.end_of_life.isnull())
+				| (item_table.end_of_life > nowdate())
+				| (item_table.end_of_life == "0000-00-00")
+			)
+			& (item_table.variant_of.notnull())
+		)
+	)
+
+	variants_item = query.run(as_dict=True)
+	for row in variants_item:
+		if not itemwise_reorder.get(row.name) and itemwise_reorder.get(row.variant_of):
+			itemwise_reorder.setdefault(row.name, []).extend(itemwise_reorder.get(row.variant_of, []))
+
+	return itemwise_reorder
+
+
 def get_item_warehouse_projected_qty(items_to_consider):
 	item_warehouse_projected_qty = {}
+	items_to_consider = list(items_to_consider.keys())
 
 	for item_code, warehouse, projected_qty in frappe.db.sql(
 		"""select item_code, warehouse, projected_qty
@@ -164,7 +248,7 @@
 
 				for d in items:
 					d = frappe._dict(d)
-					item = frappe.get_doc("Item", d.item_code)
+					item = d.get("item_details")
 					uom = item.stock_uom
 					conversion_factor = 1.0
 
@@ -190,6 +274,7 @@
 							"item_code": d.item_code,
 							"schedule_date": add_days(nowdate(), cint(item.lead_time_days)),
 							"qty": qty,
+							"conversion_factor": conversion_factor,
 							"uom": uom,
 							"stock_uom": item.stock_uom,
 							"warehouse": d.warehouse,