From ad600cce2222912d22db81e2276bf729ac4079d1 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Tue, 4 Jun 2013 16:56:56 +0530 Subject: [PATCH] bom exploded items grouped by items --- manufacturing/doctype/bom/bom.py | 57 ++++---- manufacturing/doctype/bom/test_bom.py | 132 +----------------- .../bom_explosion_item/bom_explosion_item.txt | 23 +-- .../bom_replace_tool/bom_replace_tool.py | 3 +- .../production_planning_tool.py | 34 ++--- patches/june_2013/__init__.py | 0 .../p01_update_bom_exploded_items.py | 28 ++++ patches/patch_list.py | 1 + stock/doctype/stock_entry/stock_entry.py | 24 ++-- 9 files changed, 86 insertions(+), 216 deletions(-) create mode 100644 patches/june_2013/__init__.py create mode 100644 patches/june_2013/p01_update_bom_exploded_items.py diff --git a/manufacturing/doctype/bom/bom.py b/manufacturing/doctype/bom/bom.py index 5a1d47fd4e7..4ce4feb1560 100644 --- a/manufacturing/doctype/bom/bom.py +++ b/manufacturing/doctype/bom/bom.py @@ -270,18 +270,23 @@ class DocType: if b[0]: bom_list.append(b[0]) - def update_cost_and_exploded_items(self): - bom_list = self.traverse_tree() + def update_cost_and_exploded_items(self, bom_list=[]): + bom_list = self.traverse_tree(bom_list) for bom in bom_list: bom_obj = get_obj("BOM", bom, with_children=1) bom_obj.on_update() - def traverse_tree(self): + return bom_list + + def traverse_tree(self, bom_list=[]): def _get_children(bom_no): return [cstr(d[0]) for d in webnotes.conn.sql("""select bom_no from `tabBOM Item` where parent = %s and ifnull(bom_no, '') != ''""", bom_no)] - bom_list, count = [self.doc.name], 0 + count = 0 + if self.doc.name not in bom_list: + bom_list.append(self.doc.name) + while(count < len(bom_list)): for child_bom in _get_children(bom_list[count]): if child_bom not in bom_list: @@ -325,52 +330,50 @@ class DocType: def get_exploded_items(self): """ Get all raw materials including items from child bom""" - self.cur_exploded_items = [] + self.cur_exploded_items = {} for d in getlist(self.doclist, 'bom_materials'): if d.bom_no: self.get_child_exploded_items(d.bom_no, d.qty) else: - self.cur_exploded_items.append({ + self.add_to_cur_exploded_items(webnotes._dict({ 'item_code' : d.item_code, 'description' : d.description, 'stock_uom' : d.stock_uom, 'qty' : flt(d.qty), - 'rate' : flt(d.rate), - 'amount' : flt(d.amount), - 'parent_bom' : d.parent, - 'mat_detail_no' : d.name, - 'qty_consumed_per_unit' : flt(d.qty_consumed_per_unit) - }) + 'rate' : flt(d.rate), + })) + + def add_to_cur_exploded_items(self, args): + if self.cur_exploded_items.get(args.item_code): + self.cur_exploded_items[args.item_code]["qty"] += args.qty + else: + self.cur_exploded_items[args.item_code] = args def get_child_exploded_items(self, bom_no, qty): """ Add all items from Flat BOM of child BOM""" child_fb_items = sql("""select item_code, description, stock_uom, qty, rate, - amount, parent_bom, mat_detail_no, qty_consumed_per_unit - from `tabBOM Explosion Item` where parent = '%s' and docstatus = 1""" % - bom_no, as_dict = 1) + qty_consumed_per_unit from `tabBOM Explosion Item` + where parent = %s and docstatus = 1""", bom_no, as_dict = 1) + for d in child_fb_items: - self.cur_exploded_items.append({ + self.add_to_cur_exploded_items(webnotes._dict({ 'item_code' : d['item_code'], 'description' : d['description'], 'stock_uom' : d['stock_uom'], 'qty' : flt(d['qty_consumed_per_unit'])*qty, - 'rate' : flt(d['rate']), - 'amount' : flt(d['amount']), - 'parent_bom' : d['parent_bom'], - 'mat_detail_no' : d['mat_detail_no'], - 'qty_consumed_per_unit' : flt(d['qty_consumed_per_unit'])*qty/flt(self.doc.quantity) - - }) + 'rate' : flt(d['rate']), + })) def add_exploded_items(self): "Add items to Flat BOM table" self.doclist = self.doc.clear_table(self.doclist, 'flat_bom_details', 1) for d in self.cur_exploded_items: - ch = addchild(self.doc, 'flat_bom_details', 'BOM Explosion Item', - self.doclist) - for i in d.keys(): - ch.fields[i] = d[i] + ch = addchild(self.doc, 'flat_bom_details', 'BOM Explosion Item', self.doclist) + for i in self.cur_exploded_items[d].keys(): + ch.fields[i] = self.cur_exploded_items[d][i] + ch.amount = flt(ch.qty) * flt(ch.rate) + ch.qty_consumed_per_unit = flt(ch.qty) / flt(self.doc.quantity) ch.docstatus = self.doc.docstatus ch.save(1) diff --git a/manufacturing/doctype/bom/test_bom.py b/manufacturing/doctype/bom/test_bom.py index e742c0c8acb..cb91e78cc55 100644 --- a/manufacturing/doctype/bom/test_bom.py +++ b/manufacturing/doctype/bom/test_bom.py @@ -48,134 +48,4 @@ test_records = [ "stock_uom": "No." } ] -] - - - -# import webnotes.model -# from webnotes.utils import nowdate, flt -# from accounts.utils import get_fiscal_year -# from webnotes.model.doclist import DocList -# import copy -# -# company = webnotes.conn.get_default("company") -# -# -# def load_data(): -# -# # create default warehouse -# if not webnotes.conn.exists("Warehouse", "Default Warehouse"): -# webnotes.insert({"doctype": "Warehouse", -# "warehouse_name": "Default Warehouse", -# "warehouse_type": "Stores"}) -# -# # create UOM: Nos. -# if not webnotes.conn.exists("UOM", "Nos"): -# webnotes.insert({"doctype": "UOM", "uom_name": "Nos"}) -# -# from webnotes.tests import insert_test_data -# # create item groups and items -# insert_test_data("Item Group", -# sort_fn=lambda ig: (ig[0].get('parent_item_group'), ig[0].get('name'))) -# insert_test_data("Item") -# -# base_bom_fg = [ -# {"doctype": "BOM", "item": "Android Jack D", "quantity": 1, -# "is_active": "Yes", "is_default": 1, "uom": "Nos"}, -# {"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations", -# "opn_description": "Development", "hour_rate": 10, "time_in_mins": 90}, -# {"doctype": "BOM Item", "item_code": "Home Desktop 300", "operation_no": 1, -# "qty": 2, "rate": 20, "stock_uom": "Nos", "parentfield": "bom_materials"}, -# {"doctype": "BOM Item", "item_code": "Home Desktop 100", "operation_no": 1, -# "qty": 1, "rate": 300, "stock_uom": "Nos", "parentfield": "bom_materials"}, -# {"doctype": "BOM Item", "item_code": "Nebula 7", "operation_no": 1, -# "qty": 5, "stock_uom": "Nos", "parentfield": "bom_materials"}, -# ] -# -# base_bom_child = [ -# {"doctype": "BOM", "item": "Nebula 7", "quantity": 5, -# "is_active": "Yes", "is_default": 1, "uom": "Nos"}, -# {"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations", -# "opn_description": "Development"}, -# {"doctype": "BOM Item", "item_code": "Android Jack S", "operation_no": 1, -# "qty": 10, "stock_uom": "Nos", "parentfield": "bom_materials"} -# ] -# -# base_bom_grandchild = [ -# {"doctype": "BOM", "item": "Android Jack S", "quantity": 1, -# "is_active": "Yes", "is_default": 1, "uom": "Nos"}, -# {"doctype": "BOM Operation", "operation_no": 1, "parentfield": "bom_operations", -# "opn_description": "Development"}, -# {"doctype": "BOM Item", "item_code": "Home Desktop 300", "operation_no": 1, -# "qty": 3, "rate": 10, "stock_uom": "Nos", "parentfield": "bom_materials"} -# ] -# -# -# class TestPurchaseReceipt(unittest.TestCase): -# def setUp(self): -# webnotes.conn.begin() -# load_data() -# -# def test_bom_validation(self): -# # show throw error bacause bom no missing for sub-assembly item -# bom_fg = copy.deepcopy(base_bom_fg) -# self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg)) -# -# # main item is not a manufacturing item -# bom_fg = copy.deepcopy(base_bom_fg) -# bom_fg[0]["item"] = "Home Desktop 200" -# bom_fg.pop(4) -# self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg)) -# -# # operation no mentioed in material table not matching with operation table -# bom_fg = copy.deepcopy(base_bom_fg) -# bom_fg.pop(4) -# bom_fg[2]["operation_no"] = 2 -# self.assertRaises(webnotes.ValidationError, webnotes.insert, DocList(bom_fg)) -# -# -# def test_bom(self): -# gc_wrapper = webnotes.insert(DocList(base_bom_grandchild)) -# gc_wrapper.submit() -# -# bom_child = copy.deepcopy(base_bom_child) -# bom_child[2]["bom_no"] = gc_wrapper.doc.name -# child_wrapper = webnotes.insert(DocList(bom_child)) -# child_wrapper.submit() -# -# bom_fg = copy.deepcopy(base_bom_fg) -# bom_fg[4]["bom_no"] = child_wrapper.doc.name -# fg_wrapper = webnotes.insert(DocList(bom_fg)) -# fg_wrapper.load_from_db() -# -# self.check_bom_cost(fg_wrapper) -# -# self.check_flat_bom(fg_wrapper, child_wrapper, gc_wrapper) -# -# def check_bom_cost(self, fg_wrapper): -# expected_values = { -# "operating_cost": 15, -# "raw_material_cost": 640, -# "total_cost": 655 -# } -# -# for key in expected_values: -# self.assertEqual(flt(expected_values[key]), flt(fg_wrapper.doc.fields.get(key))) -# -# def check_flat_bom(self, fg_wrapper, child_wrapper, gc_wrapper): -# expected_flat_bom_items = { -# ("Home Desktop 300", fg_wrapper.doc.name): (2, 20), -# ("Home Desktop 100", fg_wrapper.doc.name): (1, 300), -# ("Home Desktop 300", gc_wrapper.doc.name): (30, 10) -# } -# -# self.assertEqual(len(fg_wrapper.doclist.get({"parentfield": "flat_bom_details"})), 3) -# -# for key, val in expected_flat_bom_items.items(): -# flat_bom = fg_wrapper.doclist.get({"parentfield": "flat_bom_details", -# "item_code": key[0], "parent_bom": key[1]})[0] -# self.assertEqual(val, (flat_bom.qty, flat_bom.rate)) -# -# -# def tearDown(self): -# webnotes.conn.rollback() \ No newline at end of file +] \ No newline at end of file diff --git a/manufacturing/doctype/bom_explosion_item/bom_explosion_item.txt b/manufacturing/doctype/bom_explosion_item/bom_explosion_item.txt index 07aad7dc66f..3808cdfb00f 100644 --- a/manufacturing/doctype/bom_explosion_item/bom_explosion_item.txt +++ b/manufacturing/doctype/bom_explosion_item/bom_explosion_item.txt @@ -1,8 +1,8 @@ [ { - "creation": "2013-02-22 01:27:48", + "creation": "2013-03-07 11:42:57", "docstatus": 0, - "modified": "2013-03-07 07:03:18", + "modified": "2013-06-04 13:13:28", "modified_by": "Administrator", "owner": "Administrator" }, @@ -80,25 +80,6 @@ "oldfieldtype": "Link", "options": "UOM" }, - { - "doctype": "DocField", - "fieldname": "parent_bom", - "fieldtype": "Link", - "hidden": 0, - "label": "Parent BOM", - "oldfieldname": "parent_bom", - "oldfieldtype": "Link", - "options": "BOM", - "print_width": "250px", - "width": "250px" - }, - { - "doctype": "DocField", - "fieldname": "mat_detail_no", - "fieldtype": "Data", - "hidden": 1, - "label": "Mat Detail No" - }, { "doctype": "DocField", "fieldname": "qty_consumed_per_unit", diff --git a/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py b/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py index 4c9c42da2e9..e69c48723d3 100644 --- a/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py +++ b/manufacturing/doctype/bom_replace_tool/bom_replace_tool.py @@ -29,9 +29,10 @@ class DocType: self.validate_bom() self.update_new_bom() bom_list = self.get_parent_boms() + updated_bom = [] for bom in bom_list: bom_obj = get_obj("BOM", bom, with_children=1) - bom_obj.update_cost_and_exploded_items() + updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom) webnotes.msgprint(_("BOM replaced")) diff --git a/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/manufacturing/doctype/production_planning_tool/production_planning_tool.py index d4e41aca5fb..ed7f7bfb3f8 100644 --- a/manufacturing/doctype/production_planning_tool/production_planning_tool.py +++ b/manufacturing/doctype/production_planning_tool/production_planning_tool.py @@ -241,40 +241,30 @@ class DocType: def get_raw_materials(self, bom_dict): """ Get raw materials considering sub-assembly items { - "item_code": [qty_required, description, stock_uom] + "item_code": [qty_required, description, stock_uom, min_order_qty] } """ for bom in bom_dict: if self.doc.use_multi_level_bom: # get all raw materials with sub assembly childs - fl_bom_items = sql(""" - select - item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty, - description, stock_uom, min_order_qty - from - ( - select distinct fb.name, fb.description, fb.item_code, - fb.qty_consumed_per_unit, fb.stock_uom, it.min_order_qty - from `tabBOM Explosion Item` fb,`tabItem` it - where it.name = fb.item_code - and ifnull(it.is_pro_applicable, 'No') = 'No' - and ifnull(it.is_sub_contracted_item, 'No') = 'No' - and fb.docstatus<2 and fb.parent=%s - ) a - group by item_code,stock_uom - """ , (flt(bom_dict[bom]), bom)) + fl_bom_items = sql("""select fb.item_code, + ifnull(sum(fb.qty_consumed_per_unit), 0)*%s as qty, + fb.description, fb.stock_uom, it.min_order_qty + from `tabBOM Explosion Item` fb,`tabItem` it + where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' + and ifnull(it.is_sub_contracted_item, 'No') = 'No' + and fb.docstatus<2 and fb.parent=%s + group by item_code, stock_uom""", (flt(bom_dict[bom]), bom)) else: # Get all raw materials considering SA items as raw materials, # so no childs of SA items - fl_bom_items = sql(""" - select bom_item.item_code, + fl_bom_items = sql("""select bom_item.item_code, ifnull(sum(bom_item.qty_consumed_per_unit), 0) * %s, bom_item.description, bom_item.stock_uom, item.min_order_qty from `tabBOM Item` bom_item, tabItem item where bom_item.parent = %s and bom_item.docstatus < 2 - and bom_item.item_code = item.name - group by item_code - """, (flt(bom_dict[bom]), bom)) + and bom_item.item_code = item.name + group by item_code""", (flt(bom_dict[bom]), bom)) self.make_items_dict(fl_bom_items) def make_items_dict(self, item_list): diff --git a/patches/june_2013/__init__.py b/patches/june_2013/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/patches/june_2013/p01_update_bom_exploded_items.py b/patches/june_2013/p01_update_bom_exploded_items.py new file mode 100644 index 00000000000..dff70229bb1 --- /dev/null +++ b/patches/june_2013/p01_update_bom_exploded_items.py @@ -0,0 +1,28 @@ +# ERPNext - web based ERP (http://erpnext.com) +# Copyright (C) 2012 Web Notes Technologies Pvt Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from __future__ import unicode_literals +import webnotes + +def execute(): + updated_bom = [] + for bom in webnotes.conn.sql("select name from tabBOM where docstatus < 2"): + webnotes.errprint(bom[0]) + if bom[0] not in updated_bom: + bom_obj = webnotes.get_obj("BOM", bom[0], with_children=1) + updated_bom = bom_obj.update_cost_and_exploded_items(updated_bom) + + webnotes.errprint(updated_bom) \ No newline at end of file diff --git a/patches/patch_list.py b/patches/patch_list.py index ea61a04dc35..6a396725dd0 100644 --- a/patches/patch_list.py +++ b/patches/patch_list.py @@ -252,4 +252,5 @@ patch_list = [ "patches.may_2013.p03_update_support_ticket", "patches.may_2013.p04_reorder_level", "patches.may_2013.p05_update_cancelled_gl_entries", + "patches.june_2013.p01_update_bom_exploded_items", ] \ No newline at end of file diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index bce0f620d47..522a14af6ba 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -506,17 +506,13 @@ class DocType(StockController): if self.doc.use_multi_level_bom: # get all raw materials with sub assembly childs - fl_bom_sa_child_item = sql("""select - item_code,ifnull(sum(qty_consumed_per_unit),0)*%s as qty, - description,stock_uom - from ( select distinct fb.name, fb.description, fb.item_code, - fb.qty_consumed_per_unit, fb.stock_uom - from `tabBOM Explosion Item` fb,`tabItem` it - where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' - and ifnull(it.is_sub_contracted_item, 'No') = 'No' and fb.docstatus<2 - and fb.parent=%s - ) a - group by item_code, stock_uom""" , (qty, self.doc.bom_no), as_dict=1) + fl_bom_sa_child_item = sql("""select fb.item_code, + ifnull(sum(fb.qty_consumed_per_unit),0)*%s as qty, fb.description, fb.stock_uom + from `tabBOM Explosion Item` fb,`tabItem` it + where it.name = fb.item_code and ifnull(it.is_pro_applicable, 'No') = 'No' + and ifnull(it.is_sub_contracted_item, 'No') = 'No' and fb.docstatus < 2 + and fb.parent=%s group by item_code, stock_uom""", + (qty, self.doc.bom_no), as_dict=1) if fl_bom_sa_child_item: _make_items_dict(fl_bom_sa_child_item) @@ -524,10 +520,10 @@ class DocType(StockController): # Get all raw materials considering multi level BOM, # if multi level bom consider childs of Sub-Assembly items fl_bom_sa_items = sql("""select item_code, - ifnull(sum(qty_consumed_per_unit), 0) * '%s' as qty, + ifnull(sum(qty_consumed_per_unit), 0) *%s as qty, description, stock_uom from `tabBOM Item` - where parent = '%s' and docstatus < 2 - group by item_code""" % (qty, self.doc.bom_no), as_dict=1) + where parent = %s and docstatus < 2 + group by item_code""", (qty, self.doc.bom_no), as_dict=1) if fl_bom_sa_items: _make_items_dict(fl_bom_sa_items)