From 56bc5c0ea68195109e4576a96a31255de11c5c03 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 11 Oct 2013 14:34:46 +0530 Subject: [PATCH] [material request] Added feature to pull items from BOM --- manufacturing/doctype/bom/bom.py | 51 ++++++++++++++- manufacturing/doctype/bom/test_bom.py | 56 +++++++++++++++- stock/doctype/item/test_item.py | 27 +++++++- .../material_request/material_request.js | 53 ++++++++++++++- .../material_request_item.txt | 4 +- stock/doctype/stock_entry/stock_entry.py | 64 ++----------------- stock/doctype/stock_entry/test_stock_entry.py | 3 - 7 files changed, 188 insertions(+), 70 deletions(-) diff --git a/manufacturing/doctype/bom/bom.py b/manufacturing/doctype/bom/bom.py index 20c11410930..5954475376e 100644 --- a/manufacturing/doctype/bom/bom.py +++ b/manufacturing/doctype/bom/bom.py @@ -402,4 +402,53 @@ class DocType: if act_pbom and act_pbom[0][0]: action = self.doc.docstatus < 2 and _("deactivate") or _("cancel") msgprint(_("Cannot ") + action + _(": It is linked to other active BOM(s)"), - raise_exception=1) \ No newline at end of file + raise_exception=1) + +def get_bom_items_as_dict(bom, qty=1, fetch_exploded=1): + item_dict = {} + + query = """select + bom_item.item_code, + ifnull(sum(bom_item.qty_consumed_per_unit),0) * %(qty)s as qty, + item.description, + item.stock_uom, + item.default_warehouse + from + `tab%(table)s` bom_item, `tabItem` item + where + bom_item.docstatus < 2 + and bom_item.parent = "%(bom)s" + and item.name = bom_item.item_code + %(conditions)s + group by item_code, stock_uom""" + + if fetch_exploded: + items = webnotes.conn.sql(query % { + "qty": qty, + "table": "BOM Explosion Item", + "bom": bom, + "conditions": """and ifnull(item.is_pro_applicable, 'No') = 'No' + and ifnull(item.is_sub_contracted_item, 'No') = 'No' """ + }, as_dict=True) + else: + items = webnotes.conn.sql(query % { + "qty": qty, + "table": "BOM Item", + "bom": bom, + "conditions": "" + }, as_dict=True) + + # make unique + for item in items: + if item_dict.has_key(item.item_code): + item_dict[item.item_code]["qty"] += flt(item.qty) + else: + item_dict[item.item_code] = item + + return item_dict + +@webnotes.whitelist() +def get_bom_items(bom, qty=1, fetch_exploded=1): + items = get_bom_items_as_dict(bom, qty, fetch_exploded).values() + items.sort(lambda a, b: a.item_code > b.item_code and 1 or -1) + return items \ No newline at end of file diff --git a/manufacturing/doctype/bom/test_bom.py b/manufacturing/doctype/bom/test_bom.py index d0b394a1f89..da98faf8f40 100644 --- a/manufacturing/doctype/bom/test_bom.py +++ b/manufacturing/doctype/bom/test_bom.py @@ -7,6 +7,35 @@ import unittest import webnotes test_records = [ + [ + { + "doctype": "BOM", + "item": "_Test Item Home Desktop 100", + "quantity": 1.0, + "is_active": 1, + "is_default": 1, + "docstatus": 1 + }, + { + "doctype": "BOM Item", + "item_code": "_Test Serialized Item With Series", + "parentfield": "bom_materials", + "qty": 1.0, + "rate": 5000.0, + "amount": 5000.0, + "stock_uom": "_Test UOM" + }, + { + "doctype": "BOM Item", + "item_code": "_Test Item 2", + "parentfield": "bom_materials", + "qty": 2.0, + "rate": 1000.0, + "amount": 2000.0, + "stock_uom": "_Test UOM" + } + ], + [ { "doctype": "BOM", @@ -29,10 +58,33 @@ test_records = [ "doctype": "BOM Item", "item_code": "_Test Item Home Desktop 100", "parentfield": "bom_materials", + "bom_no": "BOM/_Test Item Home Desktop 100/001", "qty": 2.0, "rate": 1000.0, "amount": 2000.0, "stock_uom": "_Test UOM" } - ] -] \ No newline at end of file + ], +] + +class TestBOM(unittest.TestCase): + def test_get_items(self): + from manufacturing.doctype.bom.bom import get_bom_items_as_dict + items_dict = get_bom_items_as_dict(bom="BOM/_Test FG Item/001", qty=1, fetch_exploded=0) + self.assertTrue(test_records[1][1]["item_code"] in items_dict) + self.assertTrue(test_records[1][2]["item_code"] in items_dict) + self.assertEquals(len(items_dict.values()), 2) + + def test_get_items_exploded(self): + from manufacturing.doctype.bom.bom import get_bom_items_as_dict + items_dict = get_bom_items_as_dict(bom="BOM/_Test FG Item/001", qty=1, fetch_exploded=1) + self.assertTrue(test_records[1][1]["item_code"] in items_dict) + self.assertFalse(test_records[1][2]["item_code"] in items_dict) + self.assertTrue(test_records[0][1]["item_code"] in items_dict) + self.assertTrue(test_records[0][2]["item_code"] in items_dict) + self.assertEquals(len(items_dict.values()), 3) + + def test_get_items_list(self): + from manufacturing.doctype.bom.bom import get_bom_items + self.assertEquals(len(get_bom_items(bom="BOM/_Test FG Item/001", qty=1, fetch_exploded=1)), 3) + diff --git a/stock/doctype/item/test_item.py b/stock/doctype/item/test_item.py index 12bb4e05783..ad88c8cc8f7 100644 --- a/stock/doctype/item/test_item.py +++ b/stock/doctype/item/test_item.py @@ -31,7 +31,7 @@ test_records = [ "is_sales_item": "Yes", "is_service_item": "No", "inspection_required": "No", - "is_pro_applicable": "No", + "is_pro_applicable": "Yes", "is_sub_contracted_item": "No", "stock_uom": "_Test UOM", "default_income_account": "Sales - _TC", @@ -45,6 +45,26 @@ test_records = [ "material_request_type": "Purchase" }, ], + [{ + "doctype": "Item", + "item_code": "_Test Item 2", + "item_name": "_Test Item 2", + "description": "_Test Item 2", + "item_group": "_Test Item Group", + "is_stock_item": "Yes", + "is_asset_item": "No", + "has_batch_no": "No", + "has_serial_no": "No", + "is_purchase_item": "Yes", + "is_sales_item": "Yes", + "is_service_item": "No", + "inspection_required": "No", + "is_pro_applicable": "Yes", + "is_sub_contracted_item": "No", + "stock_uom": "_Test UOM", + "default_income_account": "Sales - _TC", + "default_warehouse": "_Test Warehouse - _TC", + }], [{ "doctype": "Item", "item_code": "_Test Item Home Desktop 100", @@ -61,8 +81,9 @@ test_records = [ "is_sales_item": "Yes", "is_service_item": "No", "inspection_required": "No", - "is_pro_applicable": "No", + "is_pro_applicable": "Yes", "is_sub_contracted_item": "No", + "is_manufactured_item": "Yes", "stock_uom": "_Test UOM" }, { @@ -182,7 +203,7 @@ test_records = [ "is_sales_item": "Yes", "is_service_item": "No", "inspection_required": "No", - "is_pro_applicable": "No", + "is_pro_applicable": "Yes", "is_sub_contracted_item": "No", "stock_uom": "_Test UOM" }], diff --git a/stock/doctype/material_request/material_request.js b/stock/doctype/material_request/material_request.js index 6931181dbac..99e8afb0a1a 100644 --- a/stock/doctype/material_request/material_request.js +++ b/stock/doctype/material_request/material_request.js @@ -21,7 +21,11 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten + wn._("Fulfilled"), cint(doc.per_ordered)); } - if(doc.docstatus == 1 && doc.status != 'Stopped'){ + if(doc.docstatus==0) { + cur_frm.add_custom_button(wn._("Get Items from BOM"), cur_frm.cscript.get_items_from_bom, "icon-sitemap"); + } + + if(doc.docstatus == 1 && doc.status != 'Stopped') { if(doc.material_request_type === "Purchase") cur_frm.add_custom_button("Make Supplier Quotation", this.make_supplier_quotation); @@ -63,6 +67,53 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten }, + schedule_date: function(doc, cdt, cdn) { + var val = locals[cdt][cdn].schedule_date; + if(val) { + $.each(wn.model.get("Material Request Item", { parent: cur_frm.doc.name }), function(i, d) { + if(!d.schedule_date) { + d.schedule_date = val; + } + }); + refresh_field("indent_details"); + } + }, + + get_items_from_bom: function() { + var d = new wn.ui.Dialog({ + title: wn._("Get Items from BOM"), + fields: [ + {"fieldname":"bom", "fieldtype":"Link", "label":wn._("BOM"), + options:"BOM"}, + {"fieldname":"fetch_exploded", "fieldtype":"Check", + "label":wn._("Fetch exploded BOM (including sub-assemblies)"), "default":1}, + {fieldname:"fetch", "label":wn._("Get Items from BOM"), "fieldtype":"Button"} + ] + }); + d.get_input("fetch").on("click", function() { + var values = d.get_values(); + if(!values) return; + + wn.call({ + method:"manufacturing.doctype.bom.bom.get_bom_items", + args: values, + callback: function(r) { + $.each(r.message, function(i, item) { + var d = wn.model.add_child(cur_frm.doc, "Material Request Item", "indent_details"); + d.item_code = item.item_code; + d.description = item.description; + d.warehouse = item.default_warehouse; + d.uom = item.stock_uom; + d.qty = item.qty; + }); + d.hide(); + refresh_field("indent_details"); + } + }); + }); + d.show(); + }, + tc_name: function() { this.get_terms(); }, diff --git a/stock/doctype/material_request_item/material_request_item.txt b/stock/doctype/material_request_item/material_request_item.txt index dae97e09295..2ef4acd531e 100644 --- a/stock/doctype/material_request_item/material_request_item.txt +++ b/stock/doctype/material_request_item/material_request_item.txt @@ -2,7 +2,7 @@ { "creation": "2013-02-22 01:28:02", "docstatus": 0, - "modified": "2013-08-07 14:45:11", + "modified": "2013-10-11 14:21:32", "modified_by": "Administrator", "owner": "Administrator" }, @@ -138,7 +138,7 @@ "oldfieldname": "item_name", "oldfieldtype": "Data", "print_width": "100px", - "reqd": 1, + "reqd": 0, "search_index": 1, "width": "100px" }, diff --git a/stock/doctype/stock_entry/stock_entry.py b/stock/doctype/stock_entry/stock_entry.py index 63cfd2be90f..78a0bd28126 100644 --- a/stock/doctype/stock_entry/stock_entry.py +++ b/stock/doctype/stock_entry/stock_entry.py @@ -472,68 +472,16 @@ class DocType(StockController): self.get_stock_and_rate() def get_bom_raw_materials(self, qty): - """ - get all items from flat bom except - child items of sub-contracted and sub assembly items - and sub assembly items itself. - """ + from manufacturing.doctype.bom.bom import get_bom_items_as_dict + # item dict = { item_code: {qty, description, stock_uom} } - item_dict = {} + item_dict = get_bom_items_as_dict(self.doc.bom_no, qty=qty, fetch_exploded = self.doc.use_multi_level_bom) - def _make_items_dict(items_list): - """makes dict of unique items with it's qty""" - for item in items_list: - if item_dict.has_key(item.item_code): - item_dict[item.item_code]["qty"] += flt(item.qty) - else: - item_dict[item.item_code] = { - "qty": flt(item.qty), - "description": item.description, - "stock_uom": item.stock_uom, - "from_warehouse": item.default_warehouse - } - - if self.doc.use_multi_level_bom: - # get all raw materials with sub assembly childs - fl_bom_sa_child_item = webnotes.conn.sql("""select - fb.item_code, - ifnull(sum(fb.qty_consumed_per_unit),0)*%s as qty, - fb.description, - fb.stock_uom, - it.default_warehouse - 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) - else: - # get only BOM items - fl_bom_sa_items = webnotes.conn.sql("""select - `tabItem`.item_code, - ifnull(sum(`tabBOM Item`.qty_consumed_per_unit), 0) *%s as qty, - `tabItem`.description, - `tabItem`.stock_uom, - `tabItem`.default_warehouse - from - `tabBOM Item`, `tabItem` - where - `tabBOM Item`.parent = %s and - `tabBOM Item`.item_code = tabItem.name and - `tabBOM Item`.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) + for item in item_dict.values(): + item.from_warehouse = item.default_warehouse return item_dict - + def get_pending_raw_materials(self, pro_obj): """ issue (item quantity) that is pending to issue or desire to transfer, diff --git a/stock/doctype/stock_entry/test_stock_entry.py b/stock/doctype/stock_entry/test_stock_entry.py index f01a83b2f72..4b36e5828c2 100644 --- a/stock/doctype/stock_entry/test_stock_entry.py +++ b/stock/doctype/stock_entry/test_stock_entry.py @@ -1,9 +1,6 @@ # Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. # License: GNU General Public License v3. See license.txt -# ERPNext - web based ERP (http://erpnext.com) -# For license information, please see license.txt - from __future__ import unicode_literals import webnotes, unittest from webnotes.utils import flt