mirror of
https://github.com/frappe/erpnext.git
synced 2026-06-04 20:59:11 +00:00
[material request] Added feature to pull items from BOM
This commit is contained in:
@@ -402,4 +402,53 @@ class DocType:
|
|||||||
if act_pbom and act_pbom[0][0]:
|
if act_pbom and act_pbom[0][0]:
|
||||||
action = self.doc.docstatus < 2 and _("deactivate") or _("cancel")
|
action = self.doc.docstatus < 2 and _("deactivate") or _("cancel")
|
||||||
msgprint(_("Cannot ") + action + _(": It is linked to other active BOM(s)"),
|
msgprint(_("Cannot ") + action + _(": It is linked to other active BOM(s)"),
|
||||||
raise_exception=1)
|
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
|
||||||
@@ -7,6 +7,35 @@ import unittest
|
|||||||
import webnotes
|
import webnotes
|
||||||
|
|
||||||
test_records = [
|
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",
|
"doctype": "BOM",
|
||||||
@@ -29,10 +58,33 @@ test_records = [
|
|||||||
"doctype": "BOM Item",
|
"doctype": "BOM Item",
|
||||||
"item_code": "_Test Item Home Desktop 100",
|
"item_code": "_Test Item Home Desktop 100",
|
||||||
"parentfield": "bom_materials",
|
"parentfield": "bom_materials",
|
||||||
|
"bom_no": "BOM/_Test Item Home Desktop 100/001",
|
||||||
"qty": 2.0,
|
"qty": 2.0,
|
||||||
"rate": 1000.0,
|
"rate": 1000.0,
|
||||||
"amount": 2000.0,
|
"amount": 2000.0,
|
||||||
"stock_uom": "_Test UOM"
|
"stock_uom": "_Test UOM"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ test_records = [
|
|||||||
"is_sales_item": "Yes",
|
"is_sales_item": "Yes",
|
||||||
"is_service_item": "No",
|
"is_service_item": "No",
|
||||||
"inspection_required": "No",
|
"inspection_required": "No",
|
||||||
"is_pro_applicable": "No",
|
"is_pro_applicable": "Yes",
|
||||||
"is_sub_contracted_item": "No",
|
"is_sub_contracted_item": "No",
|
||||||
"stock_uom": "_Test UOM",
|
"stock_uom": "_Test UOM",
|
||||||
"default_income_account": "Sales - _TC",
|
"default_income_account": "Sales - _TC",
|
||||||
@@ -45,6 +45,26 @@ test_records = [
|
|||||||
"material_request_type": "Purchase"
|
"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",
|
"doctype": "Item",
|
||||||
"item_code": "_Test Item Home Desktop 100",
|
"item_code": "_Test Item Home Desktop 100",
|
||||||
@@ -61,8 +81,9 @@ test_records = [
|
|||||||
"is_sales_item": "Yes",
|
"is_sales_item": "Yes",
|
||||||
"is_service_item": "No",
|
"is_service_item": "No",
|
||||||
"inspection_required": "No",
|
"inspection_required": "No",
|
||||||
"is_pro_applicable": "No",
|
"is_pro_applicable": "Yes",
|
||||||
"is_sub_contracted_item": "No",
|
"is_sub_contracted_item": "No",
|
||||||
|
"is_manufactured_item": "Yes",
|
||||||
"stock_uom": "_Test UOM"
|
"stock_uom": "_Test UOM"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -182,7 +203,7 @@ test_records = [
|
|||||||
"is_sales_item": "Yes",
|
"is_sales_item": "Yes",
|
||||||
"is_service_item": "No",
|
"is_service_item": "No",
|
||||||
"inspection_required": "No",
|
"inspection_required": "No",
|
||||||
"is_pro_applicable": "No",
|
"is_pro_applicable": "Yes",
|
||||||
"is_sub_contracted_item": "No",
|
"is_sub_contracted_item": "No",
|
||||||
"stock_uom": "_Test UOM"
|
"stock_uom": "_Test UOM"
|
||||||
}],
|
}],
|
||||||
|
|||||||
@@ -21,7 +21,11 @@ erpnext.buying.MaterialRequestController = erpnext.buying.BuyingController.exten
|
|||||||
+ wn._("Fulfilled"), cint(doc.per_ordered));
|
+ 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")
|
if(doc.material_request_type === "Purchase")
|
||||||
cur_frm.add_custom_button("Make Supplier Quotation",
|
cur_frm.add_custom_button("Make Supplier Quotation",
|
||||||
this.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() {
|
tc_name: function() {
|
||||||
this.get_terms();
|
this.get_terms();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
{
|
{
|
||||||
"creation": "2013-02-22 01:28:02",
|
"creation": "2013-02-22 01:28:02",
|
||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"modified": "2013-08-07 14:45:11",
|
"modified": "2013-10-11 14:21:32",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"owner": "Administrator"
|
"owner": "Administrator"
|
||||||
},
|
},
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
"oldfieldname": "item_name",
|
"oldfieldname": "item_name",
|
||||||
"oldfieldtype": "Data",
|
"oldfieldtype": "Data",
|
||||||
"print_width": "100px",
|
"print_width": "100px",
|
||||||
"reqd": 1,
|
"reqd": 0,
|
||||||
"search_index": 1,
|
"search_index": 1,
|
||||||
"width": "100px"
|
"width": "100px"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -472,68 +472,16 @@ class DocType(StockController):
|
|||||||
self.get_stock_and_rate()
|
self.get_stock_and_rate()
|
||||||
|
|
||||||
def get_bom_raw_materials(self, qty):
|
def get_bom_raw_materials(self, qty):
|
||||||
"""
|
from manufacturing.doctype.bom.bom import get_bom_items_as_dict
|
||||||
get all items from flat bom except
|
|
||||||
child items of sub-contracted and sub assembly items
|
|
||||||
and sub assembly items itself.
|
|
||||||
"""
|
|
||||||
# item dict = { item_code: {qty, description, stock_uom} }
|
# 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):
|
for item in item_dict.values():
|
||||||
"""makes dict of unique items with it's qty"""
|
item.from_warehouse = item.default_warehouse
|
||||||
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)
|
|
||||||
|
|
||||||
return item_dict
|
return item_dict
|
||||||
|
|
||||||
def get_pending_raw_materials(self, pro_obj):
|
def get_pending_raw_materials(self, pro_obj):
|
||||||
"""
|
"""
|
||||||
issue (item quantity) that is pending to issue or desire to transfer,
|
issue (item quantity) that is pending to issue or desire to transfer,
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
|
||||||
# License: GNU General Public License v3. See license.txt
|
# 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
|
from __future__ import unicode_literals
|
||||||
import webnotes, unittest
|
import webnotes, unittest
|
||||||
from webnotes.utils import flt
|
from webnotes.utils import flt
|
||||||
|
|||||||
Reference in New Issue
Block a user