@ -1,7 +1,8 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: GNU General Public License v3. See license.txt
from __future__ import unicode_literals
from typing import List
from collections import deque
import frappe , erpnext
from frappe . utils import cint , cstr , flt , today
from frappe import _
@ -16,14 +17,85 @@ from frappe.model.mapper import get_mapped_doc
import functools
from six import string_types
from operator import itemgetter
form_grid_templates = {
" items " : " templates/form_grid/item_grid.html "
}
class BOMTree :
""" Full tree representation of a BOM """
# specifying the attributes to save resources
# ref: https://docs.python.org/3/reference/datamodel.html#slots
__slots__ = [ " name " , " child_items " , " is_bom " , " item_code " , " exploded_qty " , " qty " ]
def __init__ ( self , name : str , is_bom : bool = True , exploded_qty : float = 1.0 , qty : float = 1 ) - > None :
self . name = name # name of node, BOM number if is_bom else item_code
self . child_items : List [ " BOMTree " ] = [ ] # list of child items
self . is_bom = is_bom # true if the node is a BOM and not a leaf item
self . item_code : str = None # item_code associated with node
self . qty = qty # required unit quantity to make one unit of parent item.
self . exploded_qty = exploded_qty # total exploded qty required for making root of tree.
if not self . is_bom :
self . item_code = self . name
else :
self . __create_tree ( )
def __create_tree ( self ) :
bom = frappe . get_cached_doc ( " BOM " , self . name )
self . item_code = bom . item
for item in bom . get ( " items " , [ ] ) :
qty = item . qty / bom . quantity # quantity per unit
exploded_qty = self . exploded_qty * qty
if item . bom_no :
child = BOMTree ( item . bom_no , exploded_qty = exploded_qty , qty = qty )
self . child_items . append ( child )
else :
self . child_items . append (
BOMTree ( item . item_code , is_bom = False , exploded_qty = exploded_qty , qty = qty )
)
def level_order_traversal ( self ) - > List [ " BOMTree " ] :
""" Get level order traversal of tree.
E . g . for following tree the traversal will return list of nodes in order from top to bottom .
BOM :
- SubAssy1
- item1
- item2
- SubAssy2
- item3
- item4
returns = [ SubAssy1 , item1 , item2 , SubAssy2 , item3 , item4 ]
"""
traversal = [ ]
q = deque ( )
q . append ( self )
while q :
node = q . popleft ( )
for child in node . child_items :
traversal . append ( child )
q . append ( child )
return traversal
def __str__ ( self ) - > str :
return (
f " { self . item_code } { ' - ' + self . name if self . is_bom else ' ' } qty(per unit): { self . qty } "
f " exploded_qty: { self . exploded_qty } "
)
def __repr__ ( self , level : int = 0 ) - > str :
rep = " ┃ " * ( level - 1 ) + " ┣━ " * ( level > 0 ) + str ( self ) + " \n "
for child in self . child_items :
rep + = child . __repr__ ( level = level + 1 )
return rep
class BOM ( WebsiteGenerator ) :
website = frappe . _dict (
# page_title_field = "item_name",
@ -152,7 +224,7 @@ class BOM(WebsiteGenerator):
if not args :
args = frappe . form_dict . get ( ' args ' )
if isinstance ( args , string_types ) :
if isinstance ( args , str ) :
import json
args = json . loads ( args )
@ -600,6 +672,11 @@ class BOM(WebsiteGenerator):
if not d . batch_size or d . batch_size < = 0 :
d . batch_size = 1
def get_tree_representation ( self ) - > BOMTree :
""" Get a complete tree representation preserving order of child items. """
return BOMTree ( self . name )
def get_bom_item_rate ( args , bom_doc ) :
if bom_doc . rm_cost_as_per == ' Valuation Rate ' :
rate = get_valuation_rate ( args ) * ( args . get ( " conversion_factor " ) or 1 )