Merge pull request #29736 from rohitwaghchaure/performance-issue-while-making-wo

fix: time out error while making work orders from production plan
diff --git a/erpnext/manufacturing/doctype/production_plan/production_plan.py b/erpnext/manufacturing/doctype/production_plan/production_plan.py
index 4290ca3..55054bb 100644
--- a/erpnext/manufacturing/doctype/production_plan/production_plan.py
+++ b/erpnext/manufacturing/doctype/production_plan/production_plan.py
@@ -341,6 +341,7 @@
 
 	def get_production_items(self):
 		item_dict = {}
+
 		for d in self.po_items:
 			item_details = {
 				"production_item"		: d.item_code,
@@ -357,12 +358,12 @@
 				"production_plan"       : self.name,
 				"production_plan_item"  : d.name,
 				"product_bundle_item"	: d.product_bundle_item,
-				"planned_start_date"    : d.planned_start_date
+				"planned_start_date"    : d.planned_start_date,
+				"project"               : self.project
 			}
 
-			item_details.update({
-				"project": self.project or frappe.db.get_value("Sales Order", d.sales_order, "project")
-			})
+			if not item_details['project'] and d.sales_order:
+				item_details['project'] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
 
 			if self.get_items_from == "Material Request":
 				item_details.update({
@@ -380,39 +381,59 @@
 
 	@frappe.whitelist()
 	def make_work_order(self):
+		from erpnext.manufacturing.doctype.work_order.work_order import get_default_warehouse
+
 		wo_list, po_list = [], []
 		subcontracted_po = {}
+		default_warehouses = get_default_warehouse()
 
-		self.validate_data()
-		self.make_work_order_for_finished_goods(wo_list)
-		self.make_work_order_for_subassembly_items(wo_list, subcontracted_po)
+		self.make_work_order_for_finished_goods(wo_list, default_warehouses)
+		self.make_work_order_for_subassembly_items(wo_list, subcontracted_po, default_warehouses)
 		self.make_subcontracted_purchase_order(subcontracted_po, po_list)
 		self.show_list_created_message('Work Order', wo_list)
 		self.show_list_created_message('Purchase Order', po_list)
 
-	def make_work_order_for_finished_goods(self, wo_list):
+	def make_work_order_for_finished_goods(self, wo_list, default_warehouses):
 		items_data = self.get_production_items()
 
 		for key, item in items_data.items():
 			if self.sub_assembly_items:
 				item['use_multi_level_bom'] = 0
 
+			set_default_warehouses(item, default_warehouses)
 			work_order = self.create_work_order(item)
 			if work_order:
 				wo_list.append(work_order)
 
-	def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po):
+	def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses):
 		for row in self.sub_assembly_items:
 			if row.type_of_manufacturing == 'Subcontract':
 				subcontracted_po.setdefault(row.supplier, []).append(row)
 				continue
 
-			args = {}
-			self.prepare_args_for_sub_assembly_items(row, args)
-			work_order = self.create_work_order(args)
+			work_order_data = {
+				'wip_warehouse': default_warehouses.get('wip_warehouse'),
+				'fg_warehouse': default_warehouses.get('fg_warehouse')
+			}
+
+			self.prepare_data_for_sub_assembly_items(row, work_order_data)
+			work_order = self.create_work_order(work_order_data)
 			if work_order:
 				wo_list.append(work_order)
 
+	def prepare_data_for_sub_assembly_items(self, row, wo_data):
+		for field in ["production_item", "item_name", "qty", "fg_warehouse",
+			"description", "bom_no", "stock_uom", "bom_level",
+			"production_plan_item", "schedule_date"]:
+			if row.get(field):
+				wo_data[field] = row.get(field)
+
+		wo_data.update({
+			"use_multi_level_bom": 0,
+			"production_plan": self.name,
+			"production_plan_sub_assembly_item": row.name
+		})
+
 	def make_subcontracted_purchase_order(self, subcontracted_po, purchase_orders):
 		if not subcontracted_po:
 			return
@@ -423,7 +444,7 @@
 			po.schedule_date = getdate(po_list[0].schedule_date) if po_list[0].schedule_date else nowdate()
 			po.is_subcontracted = 'Yes'
 			for row in po_list:
-				args = {
+				po_data = {
 					'item_code': row.production_item,
 					'warehouse': row.fg_warehouse,
 					'production_plan_sub_assembly_item': row.name,
@@ -433,9 +454,9 @@
 
 				for field in ['schedule_date', 'qty', 'uom', 'stock_uom', 'item_name',
 					'description', 'production_plan_item']:
-					args[field] = row.get(field)
+					po_data[field] = row.get(field)
 
-				po.append('items', args)
+				po.append('items', po_data)
 
 			po.set_missing_values()
 			po.flags.ignore_mandatory = True
@@ -452,24 +473,9 @@
 			doc_list = [get_link_to_form(doctype, p) for p in doc_list]
 			msgprint(_("{0} created").format(comma_and(doc_list)))
 
-	def prepare_args_for_sub_assembly_items(self, row, args):
-		for field in ["production_item", "item_name", "qty", "fg_warehouse",
-			"description", "bom_no", "stock_uom", "bom_level",
-			"production_plan_item", "schedule_date"]:
-			args[field] = row.get(field)
-
-		args.update({
-			"use_multi_level_bom": 0,
-			"production_plan": self.name,
-			"production_plan_sub_assembly_item": row.name
-		})
-
 	def create_work_order(self, item):
-		from erpnext.manufacturing.doctype.work_order.work_order import (
-			OverProductionError,
-			get_default_warehouse,
-		)
-		warehouse = get_default_warehouse()
+		from erpnext.manufacturing.doctype.work_order.work_order import OverProductionError
+
 		wo = frappe.new_doc("Work Order")
 		wo.update(item)
 		wo.planned_start_date = item.get('planned_start_date') or item.get('schedule_date')
@@ -478,11 +484,11 @@
 			wo.fg_warehouse = item.get("warehouse")
 
 		wo.set_work_order_operations()
+		wo.set_required_items()
 
-		if not wo.fg_warehouse:
-			wo.fg_warehouse = warehouse.get('fg_warehouse')
 		try:
 			wo.flags.ignore_mandatory = True
+			wo.flags.ignore_validate = True
 			wo.insert()
 			return wo.name
 		except OverProductionError:
@@ -1023,3 +1029,8 @@
 
 			if d.value:
 				get_sub_assembly_items(d.value, bom_data, stock_qty, indent=indent+1)
+
+def set_default_warehouses(row, default_warehouses):
+	for field in ['wip_warehouse', 'fg_warehouse']:
+		if not row.get(field):
+			row[field] = default_warehouses.get(field)
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/work_order/work_order.py b/erpnext/manufacturing/doctype/work_order/work_order.py
index 7315249..9dd3fa7 100644
--- a/erpnext/manufacturing/doctype/work_order/work_order.py
+++ b/erpnext/manufacturing/doctype/work_order/work_order.py
@@ -76,7 +76,6 @@
 
 		self.set_required_items(reset_only_qty = len(self.get("required_items")))
 
-
 	def validate_sales_order(self):
 		if self.sales_order:
 			self.check_sales_order_on_hold_or_close()
@@ -546,7 +545,7 @@
 				if node.is_bom:
 					operations.extend(_get_operations(node.name, qty=node.exploded_qty))
 
-		bom_qty = frappe.db.get_value("BOM", self.bom_no, "quantity")
+		bom_qty = frappe.get_cached_value("BOM", self.bom_no, "quantity")
 		operations.extend(_get_operations(self.bom_no, qty=1.0/bom_qty))
 
 		for correct_index, operation in enumerate(operations, start=1):
@@ -627,7 +626,7 @@
 			frappe.delete_doc("Job Card", d.name)
 
 	def validate_production_item(self):
-		if frappe.db.get_value("Item", self.production_item, "has_variants"):
+		if frappe.get_cached_value("Item", self.production_item, "has_variants"):
 			frappe.throw(_("Work Order cannot be raised against a Item Template"), ItemHasVariantError)
 
 		if self.production_item: