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:
parent
b6e46eea80
commit
5932e9d78a
@ -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 = []
|
||||
|
@ -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, '')!='')"""
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user