perf: get_boms_in_bottom_up_order

- Create child-parent map once and fetch value from child key to get parents
- Get parents recursively for a leaf node (get all ancestors)
- Approx. 44 secs for 4lakh 70k boms
This commit is contained in:
marination 2022-05-18 13:00:00 +05:30
parent 6d6616dbcd
commit b6e46eea80

View File

@ -1,11 +1,11 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt # License: GNU General Public License v3. See license.txt
import functools import functools
import re import re
from collections import deque from collections import defaultdict, deque
from operator import itemgetter from operator import itemgetter
from typing import List from typing import List, Optional
import frappe import frappe
from frappe import _ from frappe import _
@ -1130,35 +1130,77 @@ def get_children(parent=None, is_root=False, **filters):
return bom_items return bom_items
def get_boms_in_bottom_up_order(bom_no=None): def get_boms_in_bottom_up_order(bom_no: Optional[str] = None) -> List:
def _get_parent(bom_no): def _generate_child_parent_map():
return frappe.db.sql_list( bom = frappe.qb.DocType("BOM")
""" bom_item = frappe.qb.DocType("BOM Item")
select distinct bom_item.parent from `tabBOM Item` bom_item
where bom_item.bom_no = %s and bom_item.docstatus=1 and bom_item.parenttype='BOM'
and exists(select bom.name from `tabBOM` bom where bom.name=bom_item.parent and bom.is_active=1)
""",
bom_no,
)
count = 0 bom_parents = (
bom_list = [] frappe.qb.from_(bom_item)
if bom_no: .join(bom)
bom_list.append(bom_no) .on(bom_item.parent == bom.name)
else: .select(bom_item.bom_no, bom_item.parent)
# get all leaf BOMs .where(
bom_list = frappe.db.sql_list( (bom_item.bom_no.isnotnull())
& (bom_item.bom_no != "")
& (bom.docstatus == 1)
& (bom.is_active == 1)
& (bom_item.parenttype == "BOM")
)
).run(as_dict=True)
child_parent_map = defaultdict(list)
for bom in bom_parents:
child_parent_map[bom.bom_no].append(bom.parent)
return child_parent_map
def _get_flat_parent_map(leaf, child_parent_map):
parents_list = []
def _get_parents(node, parents_list):
"Returns updated ancestors list."
first_parents = child_parent_map.get(node) # immediate parents of node
if not first_parents: # top most node
return parents_list
parents_list.extend(first_parents)
parents_list = list(dict.fromkeys(parents_list).keys()) # remove duplicates
for nth_node in first_parents:
# recursively find parents
parents_list = _get_parents(nth_node, parents_list)
return parents_list
parents_list = _get_parents(leaf, parents_list)
return parents_list
def _get_leaf_boms():
return frappe.db.sql_list(
"""select name from `tabBOM` bom """select name from `tabBOM` bom
where docstatus=1 and is_active=1 where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item` and not exists(select bom_no from `tabBOM Item`
where parent=bom.name and ifnull(bom_no, '')!='')""" where parent=bom.name and ifnull(bom_no, '')!='')"""
) )
while count < len(bom_list): bom_list = []
for child_bom in _get_parent(bom_list[count]): if bom_no:
if child_bom not in bom_list: bom_list.append(bom_no)
bom_list.append(child_bom) else:
count += 1 bom_list = _get_leaf_boms()
child_parent_map = _generate_child_parent_map()
for leaf_bom in bom_list:
# generate list recursively bottom to top
parent_list = _get_flat_parent_map(leaf_bom, child_parent_map)
if not parent_list:
continue
bom_list.extend(parent_list)
bom_list = list(dict.fromkeys(bom_list).keys()) # remove duplicates
return bom_list return bom_list