fix: DB update child items, remove redundancy, fix perf

- Move `get_boms_in_bottom_up_order` in bom update tool’s file
- Remove repeated rm cost update from `update_cost`. `calculate_cost` handles RM cost update
- db_update children in `calculate_cost` optionally
- Don’t call `update_exploded_items` and regenerate exploded items in `update_cost`. They will stay the same (except cost)
This commit is contained in:
marination 2022-05-19 20:22:13 +05:30
parent b6e46eea80
commit 5932e9d78a
2 changed files with 105 additions and 113 deletions

View File

@ -3,9 +3,9 @@
import functools
import re
from collections import defaultdict, deque
from collections import deque
from operator import itemgetter
from typing import List, Optional
from typing import List
import frappe
from frappe import _
@ -383,35 +383,9 @@ class BOM(WebsiteGenerator):
existing_bom_cost = self.total_cost
for d in self.get("items"):
if not d.item_code:
continue
rate = self.get_rm_rate(
{
"company": self.company,
"item_code": d.item_code,
"bom_no": d.bom_no,
"qty": d.qty,
"uom": d.uom,
"stock_uom": d.stock_uom,
"conversion_factor": d.conversion_factor,
"sourced_by_supplier": d.sourced_by_supplier,
}
)
if rate:
d.rate = rate
d.amount = flt(d.rate) * flt(d.qty)
d.base_rate = flt(d.rate) * flt(self.conversion_rate)
d.base_amount = flt(d.amount) * flt(self.conversion_rate)
if save:
d.db_update()
if self.docstatus == 1:
self.flags.ignore_validate_update_after_submit = True
self.calculate_cost(update_hour_rate)
self.calculate_cost(save_updates=save, update_hour_rate=update_hour_rate)
if save:
self.db_update()
@ -613,11 +587,11 @@ class BOM(WebsiteGenerator):
bom_list.reverse()
return bom_list
def calculate_cost(self, update_hour_rate=False):
def calculate_cost(self, save_update=False, update_hour_rate=False):
"""Calculate bom totals"""
self.calculate_op_cost(update_hour_rate)
self.calculate_rm_cost()
self.calculate_sm_cost()
self.calculate_rm_cost(save=save_update)
self.calculate_sm_cost(save=save_update)
self.total_cost = self.operating_cost + self.raw_material_cost - self.scrap_material_cost
self.base_total_cost = (
self.base_operating_cost + self.base_raw_material_cost - self.base_scrap_material_cost
@ -659,7 +633,7 @@ class BOM(WebsiteGenerator):
if update_hour_rate:
row.db_update()
def calculate_rm_cost(self):
def calculate_rm_cost(self, save=False):
"""Fetch RM rate as per today's valuation rate and calculate totals"""
total_rm_cost = 0
base_total_rm_cost = 0
@ -674,11 +648,13 @@ class BOM(WebsiteGenerator):
total_rm_cost += d.amount
base_total_rm_cost += d.base_amount
if save:
d.db_update()
self.raw_material_cost = total_rm_cost
self.base_raw_material_cost = base_total_rm_cost
def calculate_sm_cost(self):
def calculate_sm_cost(self, save=False):
"""Fetch RM rate as per today's valuation rate and calculate totals"""
total_sm_cost = 0
base_total_sm_cost = 0
@ -693,6 +669,8 @@ class BOM(WebsiteGenerator):
)
total_sm_cost += d.amount
base_total_sm_cost += d.base_amount
if save:
d.db_update()
self.scrap_material_cost = total_sm_cost
self.base_scrap_material_cost = base_total_sm_cost
@ -1130,81 +1108,6 @@ def get_children(parent=None, is_root=False, **filters):
return bom_items
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 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 = []
if bom_no:
bom_list.append(bom_no)
else:
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
def add_additional_cost(stock_entry, work_order):
# Add non stock items cost in the additional cost
stock_entry.additional_costs = []

View File

@ -2,7 +2,8 @@
# For license information, please see license.txt
import json
from typing import TYPE_CHECKING, Dict, Literal, Optional, Union
from collections import defaultdict
from typing import TYPE_CHECKING, Dict, List, Literal, Optional, Union
if TYPE_CHECKING:
from erpnext.manufacturing.doctype.bom_update_log.bom_update_log import BOMUpdateLog
@ -10,8 +11,6 @@ if TYPE_CHECKING:
import frappe
from frappe.model.document import Document
from erpnext.manufacturing.doctype.bom.bom import get_boms_in_bottom_up_order
class BOMUpdateTool(Document):
pass
@ -47,7 +46,10 @@ def update_cost() -> None:
"""Updates Cost for all BOMs from bottom to top."""
bom_list = get_boms_in_bottom_up_order()
for bom in bom_list:
frappe.get_doc("BOM", bom).update_cost(update_parent=False, from_child_bom=True)
bom_doc = frappe.get_doc("BOM", bom)
bom_doc.calculate_cost(save_updates=True, update_hour_rate=True)
# bom_doc.update_exploded_items(save=True) #TODO: edit exploded items rate
bom_doc.db_update()
def create_bom_update_log(
@ -67,3 +69,90 @@ def create_bom_update_log(
"update_type": update_type,
}
).submit()
def get_boms_in_bottom_up_order(bom_no: Optional[str] = None) -> List:
"""
Eg: Main BOM
|- Sub BOM 1
|- Leaf BOM 1
|- Sub BOM 2
|- Leaf BOM 2
Result: [Leaf BOM 1, Leaf BOM 2, Sub BOM 1, Sub BOM 2, Main BOM]
"""
leaf_boms = []
if bom_no:
leaf_boms.append(bom_no)
else:
leaf_boms = _get_leaf_boms()
child_parent_map = _generate_child_parent_map()
bom_list = leaf_boms.copy()
for leaf_bom in leaf_boms:
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
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):
"Get ancestors at all levels of a leaf BOM."
parents_list = []
def _get_parents(node, parents_list):
"Returns recursively 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
where docstatus=1 and is_active=1
and not exists(select bom_no from `tabBOM Item`
where parent=bom.name and ifnull(bom_no, '')!='')"""
)