fix: batch BOM source warehouse lookups to avoid n+1 queries in production plan work order creation

This commit is contained in:
pandiyan
2026-07-14 21:44:48 +05:30
parent 04f75cc64f
commit 4705909cee

View File

@@ -38,9 +38,10 @@ class WorkOrderCreationService:
self.doc = doc self.doc = doc
def get_production_items(self): def get_production_items(self):
bom_warehouse_map = self.get_bom_source_warehouse_map(self.doc.po_items)
item_dict = {} item_dict = {}
for d in self.doc.po_items: for d in self.doc.po_items:
item_details = self._production_item_details(d) item_details = self._production_item_details(d, bom_warehouse_map)
if self.doc.get_items_from == "Material Request": if self.doc.get_items_from == "Material Request":
item_details["qty"] = d.planned_qty item_details["qty"] = d.planned_qty
key = (d.item_code, d.material_request_item, d.warehouse, d.planned_start_date) key = (d.item_code, d.material_request_item, d.warehouse, d.planned_start_date)
@@ -52,7 +53,20 @@ class WorkOrderCreationService:
item_dict[key] = item_details item_dict[key] = item_details
return item_dict return item_dict
def _production_item_details(self, d): def get_bom_source_warehouse_map(self, rows):
bom_names = {row.bom_no for row in rows if row.bom_no}
if not bom_names:
return {}
return dict(
frappe.get_all(
"BOM",
filters={"name": ["in", list(bom_names)]},
fields=["name", "default_source_warehouse"],
as_list=True,
)
)
def _production_item_details(self, d, bom_warehouse_map):
details = { details = {
"production_item": d.item_code, "production_item": d.item_code,
"use_multi_level_bom": d.include_exploded_items, "use_multi_level_bom": d.include_exploded_items,
@@ -70,7 +84,7 @@ class WorkOrderCreationService:
"product_bundle_item": d.product_bundle_item, "product_bundle_item": d.product_bundle_item,
"planned_start_date": d.planned_start_date, "planned_start_date": d.planned_start_date,
"project": self.doc.project, "project": self.doc.project,
"source_warehouse": frappe.get_value("BOM", d.bom_no, "default_source_warehouse"), "source_warehouse": bom_warehouse_map.get(d.bom_no),
} }
if not details["project"] and d.sales_order: if not details["project"] and d.sales_order:
details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project") details["project"] = frappe.get_cached_value("Sales Order", d.sales_order, "project")
@@ -112,6 +126,7 @@ class WorkOrderCreationService:
wo_list.append(work_order) wo_list.append(work_order)
def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses): def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses):
bom_warehouse_map = self.get_bom_source_warehouse_map(self.doc.sub_assembly_items)
for row in self.doc.sub_assembly_items: for row in self.doc.sub_assembly_items:
if row.type_of_manufacturing == "Subcontract": if row.type_of_manufacturing == "Subcontract":
subcontracted_po.setdefault(row.supplier, []).append(row) subcontracted_po.setdefault(row.supplier, []).append(row)
@@ -119,16 +134,16 @@ class WorkOrderCreationService:
if row.type_of_manufacturing == "Material Request": if row.type_of_manufacturing == "Material Request":
continue continue
work_order = self._sub_assembly_work_order(row, default_warehouses) work_order = self._sub_assembly_work_order(row, default_warehouses, bom_warehouse_map)
if work_order: if work_order:
wo_list.append(work_order) wo_list.append(work_order)
def _sub_assembly_work_order(self, row, default_warehouses): def _sub_assembly_work_order(self, row, default_warehouses, bom_warehouse_map):
if flt(row.qty) <= flt(row.ordered_qty): if flt(row.qty) <= flt(row.ordered_qty):
return None return None
work_order_data = { work_order_data = {
"source_warehouse": frappe.get_value("BOM", row.bom_no, "default_source_warehouse"), "source_warehouse": bom_warehouse_map.get(row.bom_no),
"wip_warehouse": default_warehouses.get("wip_warehouse"), "wip_warehouse": default_warehouses.get("wip_warehouse"),
"fg_warehouse": default_warehouses.get("fg_warehouse"), "fg_warehouse": default_warehouses.get("fg_warehouse"),
"scrap_warehouse": default_warehouses.get("scrap_warehouse"), "scrap_warehouse": default_warehouses.get("scrap_warehouse"),