Skip to content

Commit

Permalink
perf: get_boms_in_bottom_up_order
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
marination committed May 20, 2022
1 parent f519dc6 commit 87c2b3b
Showing 1 changed file with 66 additions and 24 deletions.
90 changes: 66 additions & 24 deletions erpnext/manufacturing/doctype/bom/bom.py
Original file line number Diff line number Diff line change
@@ -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

import functools
import re
from collections import deque
from collections import defaultdict, deque
from operator import itemgetter
from typing import List
from typing import List, Optional

import frappe
from frappe import _
Expand Down Expand Up @@ -1120,35 +1120,77 @@ def get_children(doctype, parent=None, is_root=False, **filters):
return bom_items


def get_boms_in_bottom_up_order(bom_no=None):
def _get_parent(bom_no):
def get_boms_in_bottom_up_order(bom_no: Optional[str] = None) -> List:
def _generate_child_parent_map():
bom = frappe.qb.DocType("BOM")
bom_item = frappe.qb.DocType("BOM Item")

bom_parents = (
frappe.qb.from_(bom_item)
.join(bom)
.on(bom_item.parent == bom.name)
.select(bom_item.bom_no, bom_item.parent)
.where(
(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 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,
"""select name from `tabBOM` bom
where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item`
where parent=bom.name and ifnull(bom_no, '')!='')"""
)

count = 0
bom_list = []
if bom_no:
bom_list.append(bom_no)
else:
# get all leaf BOMs
bom_list = frappe.db.sql_list(
"""select name from `tabBOM` bom
where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item`
where parent=bom.name and ifnull(bom_no, '')!='')"""
)
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

while count < len(bom_list):
for child_bom in _get_parent(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
count += 1
bom_list.extend(parent_list)
bom_list = list(dict.fromkeys(bom_list).keys()) # remove duplicates

return bom_list

Expand Down

0 comments on commit 87c2b3b

Please sign in to comment.