bom cleanup: update cost and flat bom by traversing full tree
diff --git a/production/doctype/bom/bom.js b/production/doctype/bom/bom.js
index 9dffd6c..9d0175f 100644
--- a/production/doctype/bom/bom.js
+++ b/production/doctype/bom/bom.js
@@ -17,11 +17,11 @@
// On REFRESH
cur_frm.cscript.refresh = function(doc,dt,dn){
cur_frm.toggle_enable("item", doc.__islocal);
+ if (!doc.__islocal && doc.docstatus==0) {
+ cur_frm.set_intro("Submit the BOM to use it in production");
+ }
}
-
-// Triggers
-//--------------------------------------------------------------------------------------------------
cur_frm.cscript.item = function(doc, dt, dn) {
if (doc.item) {
get_server_fields('get_item_detail',doc.item,'',doc,dt,dn,1);
@@ -36,7 +36,8 @@
calculate_op_cost(doc, dt, dn);
calculate_total(doc);
}
- get_server_fields('get_workstation_details',d.workstation,'bom_operations',doc,dt,dn,1, callback);
+ get_server_fields('get_workstation_details', d.workstation,
+ 'bom_operations', doc, dt, dn, 1, callback);
}
}
@@ -49,12 +50,10 @@
cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
-
cur_frm.cscript.item_code = function(doc, cdt, cdn) {
get_bom_material_detail(doc, cdt, cdn);
}
-
cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
get_bom_material_detail(doc, cdt, cdn);
}
@@ -89,9 +88,16 @@
calculate_total(doc);
}
-
-cur_frm.cscript.rate = cur_frm.cscript.qty;
-
+cur_frm.cscript.rate = function(doc, cdt, cdn) {
+ var d = locals[cdt][cdn];
+ if (d.bom_no) {
+ msgprint("You can not change rate if BOM mentioned agianst any item");
+ get_bom_material_detail(doc, cdt, cdn);
+ } else {
+ calculate_rm_cost(doc, cdt, cdn);
+ calculate_total(doc);
+ }
+}
cur_frm.cscript.is_default = function(doc, cdt, cdn) {
if (doc.docstatus == 1)
@@ -104,13 +110,11 @@
$c_obj(make_doclist(dt, dn), 'manage_active_bom', '', '');
}
-
-// Calculate Operating Cost
var calculate_op_cost = function(doc, dt, dn) {
var op = getchildren('BOM Operation', doc.name, 'bom_operations');
total_op_cost = 0;
for(var i=0;i<op.length;i++) {
- op_cost = flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60;
+ op_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
set_multiple('BOM Operation',op[i].name, {'operating_cost': op_cost}, 'bom_operations');
total_op_cost += op_cost;
}
@@ -118,15 +122,14 @@
refresh_field('operating_cost');
}
-
-// Calculate Raw Material Cost
var calculate_rm_cost = function(doc, dt, dn) {
var rm = getchildren('BOM Item', doc.name, 'bom_materials');
total_rm_cost = 0;
for(var i=0;i<rm.length;i++) {
amt = flt(rm[i].rate) * flt(rm[i].qty);
set_multiple('BOM Item',rm[i].name, {'amount': amt}, 'bom_materials');
- set_multiple('BOM Item',rm[i].name, {'qty_consumed_per_unit': flt(rm[i].qty)/flt(doc.quantity)}, 'bom_materials');
+ set_multiple('BOM Item',rm[i].name,
+ {'qty_consumed_per_unit': flt(rm[i].qty)/flt(doc.quantity)}, 'bom_materials');
total_rm_cost += amt;
}
doc.raw_material_cost = total_rm_cost;
@@ -142,7 +145,10 @@
cur_frm.fields_dict['item'].get_query = function(doc) {
- return 'SELECT DISTINCT `tabItem`.`name`, `tabItem`.description FROM `tabItem` WHERE is_manufactured_item = "Yes" and (IFNULL(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` = "0000-00-00" OR `tabItem`.`end_of_life` > NOW()) AND `tabItem`.`%(key)s` like "%s" ORDER BY `tabItem`.`name` LIMIT 50';
+ return 'SELECT DISTINCT `tabItem`.`name`, `tabItem`.description FROM `tabItem` \
+ WHERE is_manufactured_item = "Yes" and (IFNULL(`tabItem`.`end_of_life`,"") = "" OR \
+ `tabItem`.`end_of_life` = "0000-00-00" OR `tabItem`.`end_of_life` > NOW()) AND \
+ `tabItem`.`%(key)s` like "%s" ORDER BY `tabItem`.`name` LIMIT 50';
}
cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
@@ -152,12 +158,18 @@
}
cur_frm.fields_dict['bom_materials'].grid.get_field('item_code').get_query = function(doc) {
- return 'SELECT DISTINCT `tabItem`.`name`, `tabItem`.description FROM `tabItem` WHERE (IFNULL(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` = "0000-00-00" OR `tabItem`.`end_of_life` > NOW()) AND `tabItem`.`%(key)s` like "%s" ORDER BY `tabItem`.`name` LIMIT 50';
+ return 'SELECT DISTINCT `tabItem`.`name`, `tabItem`.description FROM `tabItem` \
+ WHERE (IFNULL(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` = "0000-00-00" \
+ OR `tabItem`.`end_of_life` > NOW()) AND `tabItem`.`%(key)s` like "%s" \
+ ORDER BY `tabItem`.`name` LIMIT 50';
}
cur_frm.fields_dict['bom_materials'].grid.get_field('bom_no').get_query = function(doc) {
var d = locals[this.doctype][this.docname];
- return 'SELECT DISTINCT `tabBOM`.`name`, `tabBOM`.`remarks` FROM `tabBOM` WHERE `tabBOM`.`item` = "' + d.item_code + '" AND `tabBOM`.`is_active` = "Yes" AND `tabBOM`.docstatus = 1 AND `tabBOM`.`name` like "%s" ORDER BY `tabBOM`.`name` LIMIT 50';
+ return 'SELECT DISTINCT `tabBOM`.`name`, `tabBOM`.`remarks` FROM `tabBOM` \
+ WHERE `tabBOM`.`item` = "' + d.item_code + '" AND `tabBOM`.`is_active` = "Yes" AND \
+ `tabBOM`.docstatus = 1 AND `tabBOM`.`name` like "%s" \
+ ORDER BY `tabBOM`.`name` LIMIT 50';
}
cur_frm.cscript.validate = function(doc, dt, dn) {
diff --git a/production/doctype/bom/bom.py b/production/doctype/bom/bom.py
index ac21d21..857458e 100644
--- a/production/doctype/bom/bom.py
+++ b/production/doctype/bom/bom.py
@@ -64,22 +64,20 @@
def get_workstation_details(self,workstation):
""" Fetch hour rate from workstation master"""
- ws = sql("select hour_rate from `tabWorkstation` where name = %s",workstation , as_dict = 1)
- ret = {
- 'hour_rate' : ws and flt(ws[0]['hour_rate']) or '',
- }
- return ret
-
+ ws = sql("select hour_rate from `tabWorkstation` where name = %s",
+ workstation , as_dict = 1)
+ return {'hour_rate' : ws and flt(ws[0]['hour_rate']) or ''}
def validate_rm_item(self, item):
""" Validate raw material items"""
if item[0]['name'] == self.doc.item:
- msgprint(" Item_code: "+item[0]['name']+" in materials tab cannot be same as FG Item in BOM := " +cstr(self.doc.name), raise_exception=1)
+ msgprint("Item_code: %s in materials tab cannot be same as FG Item",
+ item[0]['name'], raise_exception=1)
if item and item[0]['is_asset_item'] == 'Yes':
- msgprint("Sorry!!! Item " + item[0]['name'] + " is an Asset of the company. Entered in BOM => " + cstr(self.doc.name), raise_exception = 1)
+ msgprint("Item: %s is an asset item, please check", item[0]['name'], raise_exception=1)
if not item or item[0]['docstatus'] == 2:
msgprint("Item %s does not exist in system" % item[0]['item_code'], raise_exception = 1)
@@ -113,9 +111,7 @@
""" Get raw material rate as per selected method, if bom exists takes bom cost """
if arg['bom_no']:
- bom = sql("""select name, total_cost/quantity as unit_cost from `tabBOM`
- where is_active = 'Yes' and name = %s""", arg['bom_no'], as_dict=1)
- rate = bom and bom[0]['unit_cost'] or 0
+ rate = self.get_bom_unitcost(arg['bom_no'])
elif arg and (arg['is_purchase_item'] == 'Yes' or arg['is_sub_contracted_item'] == 'Yes'):
if self.doc.rm_cost_as_per == 'Valuation Rate':
rate = self.get_valuation_rate(arg)
@@ -126,7 +122,10 @@
return rate
-
+ def get_bom_unitcost(self, bom_no):
+ bom = sql("""select name, total_cost/quantity as unit_cost from `tabBOM`
+ where is_active = 'Yes' and name = %s""", bom_no, as_dict=1)
+ return bom and bom[0]['unit_cost'] or 0
def get_valuation_rate(self, arg):
""" Get average valuation rate of relevant warehouses
@@ -139,7 +138,8 @@
warehouse = sql("select warehouse from `tabBin` where item_code = %s", arg['item_code'])
rate = []
for wh in warehouse:
- r = get_obj('Valuation Control').get_incoming_rate(dt, time, arg['item_code'], wh[0], qty = arg.get('qty', 0))
+ r = get_obj('Valuation Control').get_incoming_rate(dt, time,
+ arg['item_code'], wh[0], qty=arg.get('qty', 0))
if r:
rate.append(r)
@@ -148,15 +148,20 @@
def manage_default_bom(self):
- """ Uncheck others if current one is selected as default, update default bom in item master"""
+ """ Uncheck others if current one is selected as default,
+ update default bom in item master
+ """
if self.doc.is_default and self.doc.is_active == 'Yes':
- sql("update `tabBOM` set is_default = 0 where name != %s and item=%s", (self.doc.name, self.doc.item))
+ sql("update `tabBOM` set is_default = 0 where name != %s and item=%s",
+ (self.doc.name, self.doc.item))
# update default bom in Item Master
- sql("update `tabItem` set default_bom = %s where name = %s", (self.doc.name, self.doc.item))
+ sql("update `tabItem` set default_bom = %s where name = %s",
+ (self.doc.name, self.doc.item))
else:
- sql("update `tabItem` set default_bom = '' where name = %s and default_bom = %s", (self.doc.item, self.doc.name))
+ sql("update `tabItem` set default_bom = '' where name = %s and default_bom = %s",
+ (self.doc.item, self.doc.name))
def manage_active_bom(self):
@@ -181,39 +186,33 @@
def calculate_cost(self):
"""Calculate bom totals"""
- self.doc.costing_date = nowdate()
self.calculate_op_cost()
self.calculate_rm_cost()
self.doc.total_cost = self.doc.raw_material_cost + self.doc.operating_cost
self.doc.modified = now()
self.doc.save()
-
- self.update_flat_bom_engine(is_submit = self.doc.docstatus)
-
def calculate_op_cost(self):
"""Update workstation rate and calculates totals"""
total_op_cost = 0
for d in getlist(self.doclist, 'bom_operations'):
- hour_rate = sql("select hour_rate from `tabWorkstation` where name = %s", cstr(d.workstation))
- d.hour_rate = hour_rate and flt(hour_rate[0][0]) or d.hour_rate or 0
- d.operating_cost = d.hour_rate and d.time_in_mins and \
- flt(d.hour_rate) * flt(d.time_in_mins) / 60 or d.operating_cost
+ if d.hour_rate and d.time_in_mins:
+ d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
d.save()
- total_op_cost += d.operating_cost
+ total_op_cost += flt(d.operating_cost)
self.doc.operating_cost = total_op_cost
+
def calculate_rm_cost(self):
"""Fetch RM rate as per today's valuation rate and calculate totals"""
total_rm_cost = 0
for d in getlist(self.doclist, 'bom_materials'):
- arg = {'item_code': d.item_code, 'qty': d.qty, 'bom_no': d.bom_no}
- ret = self.get_bom_material_detail(cstr(arg))
- for k in ret:
- d.fields[k] = ret[k]
+ if d.bom_no:
+ d.rate = self.get_bom_unitcost(d.bom_no)
d.amount = flt(d.rate) * flt(d.qty)
+ d.qty_consumed_per_unit = flt(d.qty) / flt(self.doc.quantity)
d.save()
total_rm_cost += d.amount
self.doc.raw_material_cost = total_rm_cost
@@ -224,20 +223,22 @@
""" Validate main FG item"""
item = self.get_item_det(self.doc.item)
if not item:
- msgprint("Item %s does not exists in the system or expired." % self.doc.item, raise_exception = 1)
+ msgprint("Item %s does not exists in the system or expired." %
+ self.doc.item, raise_exception = 1)
- elif item[0]['is_manufactured_item'] != 'Yes' and item[0]['is_sub_contracted_item'] != 'Yes':
- msgprint("""As Item: %s is not a manufactured / sub-contracted item,
+ elif item[0]['is_manufactured_item'] != 'Yes' \
+ and item[0]['is_sub_contracted_item'] != 'Yes':
+ msgprint("""As Item: %s is not a manufactured / sub-contracted item, \
you can not make BOM for it""" % self.doc.item, raise_exception = 1)
-
def validate_operations(self):
""" Check duplicate operation no"""
self.op = []
for d in getlist(self.doclist, 'bom_operations'):
if cstr(d.operation_no) in self.op:
- msgprint("Operation no: %s is repeated in Operations Table"% d.operation_no, raise_exception=1)
+ msgprint("Operation no: %s is repeated in Operations Table" %
+ d.operation_no, raise_exception=1)
else:
# add operation in op list
self.op.append(cstr(d.operation_no))
@@ -248,40 +249,47 @@
for m in getlist(self.doclist, 'bom_materials'):
# check if operation no not in op table
if cstr(m.operation_no) not in self.op:
- msgprint("""Operation no: %s against item: %s at row no: %s is not present
- at Operations table"""% (m.operation_no, m.item_code, m.idx), raise_exception = 1)
+ msgprint("""Operation no: %s against item: %s at row no: %s \
+ is not present at Operations table""" %
+ (m.operation_no, m.item_code, m.idx), raise_exception = 1)
item = self.get_item_det(m.item_code)
- if item[0]['is_manufactured_item'] == 'Yes' or item[0]['is_sub_contracted_item'] == 'Yes':
+ if item[0]['is_manufactured_item'] == 'Yes' or \
+ item[0]['is_sub_contracted_item'] == 'Yes':
if not m.bom_no:
- msgprint("Please enter BOM No aginst item: %s at row no: %s"% (m.item_code, m.idx), raise_exception=1)
+ msgprint("Please enter BOM No aginst item: %s at row no: %s" %
+ (m.item_code, m.idx), raise_exception=1)
else:
self.validate_bom_no(m.item_code, m.bom_no, m.idx)
elif m.bom_no:
- msgprint("""As Item %s is not a manufactured / sub-contracted item,
- you can enter BOM against it (Row No: %s)."""% (m.item_code, m.idx), raise_excepiton = 1)
+ msgprint("""As Item %s is not a manufactured / sub-contracted item, \
+ you can enter BOM against it (Row No: %s).""" %
+ (m.item_code, m.idx), raise_exception = 1)
if flt(m.qty) <= 0:
- msgprint("Please enter qty against raw material: %s at row no: %s"% (m.item_code, m.idx), raise_exception = 1)
+ msgprint("Please enter qty against raw material: %s at row no: %s" %
+ (m.item_code, m.idx), raise_exception = 1)
self.check_if_item_repeated(m.item_code, m.operation_no, check_list)
-
def validate_bom_no(self, item, bom_no, idx):
"""Validate BOM No of sub-contracted items"""
bom = sql("""select name from `tabBOM` where name = %s and item = %s
- and ifnull(is_active, 'No') = 'Yes' and docstatus < 2 """, (bom_no, item), as_dict =1)
+ and ifnull(is_active, 'No') = 'Yes' and docstatus < 2 """,
+ (bom_no, item), as_dict =1)
if not bom:
msgprint("""Incorrect BOM No: %s against item: %s at row no: %s.
- It may be inactive or cancelled or for some other item."""% (bom_no, item, idx), raise_exception = 1)
+ It may be inactive or cancelled or for some other item.""" %
+ (bom_no, item, idx), raise_exception = 1)
def check_if_item_repeated(self, item, op, check_list):
if [cstr(item), cstr(op)] in check_list:
- msgprint("Item %s has been entered twice against same operation" % item, raise_exception = 1)
+ msgprint("Item %s has been entered twice against same operation" %
+ item, raise_exception = 1)
else:
check_list.append([cstr(item), cstr(op)])
@@ -292,13 +300,14 @@
self.validate_materials()
def check_recursion(self):
- """ Check whether reqursion occurs in any bom"""
+ """ Check whether recursion occurs in any bom"""
check_list = [['parent', 'bom_no', 'parent'], ['bom_no', 'parent', 'child']]
for d in check_list:
bom_list, count = [self.doc.name], 0
while (len(bom_list) > count ):
- boms = sql(" select %s from `tabBOM Item` where %s = '%s' " % (d[0], d[1], cstr(bom_list[count])))
+ boms = sql(" select %s from `tabBOM Item` where %s = '%s' " %
+ (d[0], d[1], cstr(bom_list[count])))
count = count + 1
for b in boms:
if b[0] == self.doc.name:
@@ -308,30 +317,32 @@
bom_list.append(b[0])
-
def on_update(self):
self.check_recursion()
+ self.update_cost_by_traversing()
+ self.update_flat_bom_by_traversing()
+
-
- def add_to_flat_bom_detail(self, is_submit = 0):
+ def add_to_flat_bom_detail(self):
"Add items to Flat BOM table"
self.doclist = self.doc.clear_table(self.doclist, 'flat_bom_details', 1)
for d in self.cur_flat_bom_items:
ch = addchild(self.doc, 'flat_bom_details', 'BOM Explosion Item', 1, self.doclist)
for i in d.keys():
ch.fields[i] = d[i]
- ch.docstatus = is_submit
+ ch.docstatus = self.doc.docstatus
ch.save(1)
self.doc.save()
-
def get_child_flat_bom_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)
+
+ 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)
for d in child_fb_items:
self.cur_flat_bom_items.append({
'item_code' : d['item_code'],
@@ -347,31 +358,30 @@
})
- # Get Current Flat BOM Items
- # -----------------------------
- def get_current_flat_bom_items(self):
+ def get_flat_bom_items(self):
""" Get all raw materials including items from child bom"""
self.cur_flat_bom_items = []
for d in getlist(self.doclist, 'bom_materials'):
- self.cur_flat_bom_items.append({
- '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, #item and item[0][0]=='No' and d.bom_no or d.parent,
- 'mat_detail_no' : d.name,
- 'qty_consumed_per_unit' : flt(d.qty_consumed_per_unit)
- })
if d.bom_no:
self.get_child_flat_bom_items(d.bom_no, d.qty)
+ else:
+ self.cur_flat_bom_items.append({
+ '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)
+ })
- def update_flat_bom_engine(self, is_submit = 0):
+ def update_flat_bom(self):
""" Update Flat BOM, following will be correct data"""
- self.get_current_flat_bom_items()
- self.add_to_flat_bom_detail(is_submit)
+ self.get_flat_bom_items()
+ self.add_to_flat_bom_detail()
def get_parent_bom_list(self, bom_no):
@@ -381,17 +391,43 @@
def on_submit(self):
self.manage_default_bom()
- self.update_flat_bom_engine(1)
-
def on_cancel(self):
# check if used in any other bom
par = sql("""select t1.parent from `tabBOM Item` t1, `tabBOM` t2
- where t1.parent = t2.name and t1.bom_no = %s and t1.docstatus = 1 and t2.is_active = 'Yes'""", self.doc.name)
+ where t1.parent = t2.name and t1.bom_no = %s and t1.docstatus = 1
+ and t2.is_active = 'Yes'""", self.doc.name)
if par:
- msgprint("BOM can not be cancelled, as it is a child item in following active BOM %s"% [d[0] for d in par])
- raise Exception
+ msgprint("""BOM can not be cancelled, as it is a child item \
+ in following active BOM %s""" % [d[0] for d in par], raise_exception=1)
webnotes.conn.set(self.doc, "is_active", "No")
webnotes.conn.set(self.doc, "is_default", 0)
self.manage_default_bom()
+ self.update_flat_bom_by_traversing()
+
+ def traverse_tree(self):
+ def _get_childs(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
+ while(count < len(bom_list)):
+ for child_bom in _get_childs(bom_list[count]):
+ if child_bom not in bom_list:
+ bom_list.append(child_bom)
+ count += 1
+
+ return bom_list
+
+ def update_cost_by_traversing(self):
+ bom_list = self.traverse_tree()
+ bom_list.reverse()
+ for bom in bom_list:
+ get_obj("BOM", bom, with_children=1).calculate_cost()
+
+ def update_flat_bom_by_traversing(self):
+ bom_list = self.traverse_tree()
+ bom_list.reverse()
+ for bom in bom_list:
+ get_obj("BOM", bom, with_children=1).update_flat_bom()
\ No newline at end of file
diff --git a/production/doctype/bom/bom.txt b/production/doctype/bom/bom.txt
index 58cb5cb..5c029cd 100644
--- a/production/doctype/bom/bom.txt
+++ b/production/doctype/bom/bom.txt
@@ -4,7 +4,7 @@
"docstatus": 0,
"creation": "2012-07-03 13:30:03",
"modified_by": "Administrator",
- "modified": "2012-12-03 13:29:26"
+ "modified": "2012-12-10 12:03:14"
},
{
"istable": 0,
@@ -56,12 +56,10 @@
{
"description": "Select the item code for which Bill of Material is being created",
"oldfieldtype": "Link",
- "colour": "White:FFF",
"doctype": "DocField",
"label": "Item",
"oldfieldname": "item",
"permlevel": 0,
- "trigger": "Client",
"fieldname": "item",
"fieldtype": "Link",
"search_index": 1,
@@ -72,7 +70,6 @@
{
"description": "Total quantity of items for which raw materials required and operations done will be defined",
"oldfieldtype": "Currency",
- "colour": "White:FFF",
"doctype": "DocField",
"label": "Quantity",
"oldfieldname": "quantity",
@@ -91,7 +88,6 @@
{
"no_copy": 1,
"oldfieldtype": "Select",
- "colour": "White:FFF",
"allow_on_submit": 1,
"doctype": "DocField",
"label": "Is Active",
@@ -104,10 +100,9 @@
"options": "\nYes\nNo"
},
{
- "allow_on_submit": 1,
"no_copy": 1,
"oldfieldtype": "Check",
- "colour": "White:FFF",
+ "allow_on_submit": 1,
"doctype": "DocField",
"label": "Is Default",
"oldfieldname": "is_default",
@@ -126,7 +121,6 @@
{
"description": "Specify the operations, operating cost and give a unique Operation no to your operations.",
"oldfieldtype": "Table",
- "colour": "White:FFF",
"doctype": "DocField",
"label": "BOM Operations",
"oldfieldname": "bom_operations",
@@ -154,7 +148,6 @@
{
"description": "Enter the raw materials required to manufacture the BOM item. Specify the operation no as entered in the previous tab which will be performed on the raw materials entered.",
"oldfieldtype": "Table",
- "colour": "White:FFF",
"doctype": "DocField",
"label": "BOM Item",
"oldfieldname": "bom_materials",
@@ -187,6 +180,12 @@
},
{
"doctype": "DocField",
+ "fieldname": "col_break24",
+ "fieldtype": "Column Break",
+ "permlevel": 0
+ },
+ {
+ "doctype": "DocField",
"label": "Total Cost",
"fieldname": "total_cost",
"fieldtype": "Float",
@@ -202,13 +201,12 @@
{
"description": "Select name of the project if BOM need to be created against any project",
"oldfieldtype": "Link",
+ "doctype": "DocField",
"label": "Project Name",
"oldfieldname": "project_name",
- "trigger": "Client",
+ "options": "Project",
"fieldname": "project_name",
"fieldtype": "Link",
- "doctype": "DocField",
- "options": "Project",
"permlevel": 0,
"in_filter": 1
},
@@ -225,7 +223,6 @@
"doctype": "DocField",
"label": "Item Description",
"oldfieldname": "description",
- "width": "300px",
"fieldname": "description",
"fieldtype": "Small Text",
"permlevel": 0
@@ -269,7 +266,6 @@
{
"no_copy": 1,
"oldfieldtype": "Text",
- "colour": "White:FFF",
"doctype": "DocField",
"label": "Remarks",
"oldfieldname": "remarks",
@@ -292,7 +288,6 @@
"permlevel": 1,
"no_copy": 1,
"oldfieldtype": "Table",
- "colour": "White:FFF",
"doctype": "DocField",
"label": "BOM Explosion Item",
"oldfieldname": "flat_bom_details",
diff --git a/production/doctype/bom/test_bom.py b/production/doctype/bom/test_bom.py
new file mode 100644
index 0000000..68d9ce8
--- /dev/null
+++ b/production/doctype/bom/test_bom.py
@@ -0,0 +1,147 @@
+# 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 <http://www.gnu.org/licenses/>.
+
+
+from __future__ import unicode_literals
+import unittest
+import webnotes
+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
diff --git a/production/doctype/bom_control/__init__.py b/production/doctype/bom_control/__init__.py
deleted file mode 100644
index baffc48..0000000
--- a/production/doctype/bom_control/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from __future__ import unicode_literals
diff --git a/production/doctype/bom_control/bom_control.py b/production/doctype/bom_control/bom_control.py
deleted file mode 100644
index 27812a8..0000000
--- a/production/doctype/bom_control/bom_control.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# 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 <http://www.gnu.org/licenses/>.
-
-from __future__ import unicode_literals
-import webnotes
-
-from webnotes.utils import cint, flt
-from webnotes.model import db_exists
-from webnotes.model.wrapper import copy_doclist
-from webnotes.model.code import get_obj
-
-sql = webnotes.conn.sql
-
-
-
-class DocType:
- def __init__(self, doc, doclist):
- self.doc = doc
- self.doclist = doclist
-
-
-
- def get_item_group(self):
- ret = sql("select name from `tabItem Group` ")
- item_group = []
- for r in ret:
- item =sql("select t1.name from `tabItem` t1, `tabBOM` t2 where t2.item = t1.name and t1.item_group = '%s' " % (r[0]))
- if item and item[0][0]:
- item_group.append(r[0])
- return '~~~'.join([r for r in item_group])
-
-
-
- def get_item_code(self,item_group):
- """ here BOM docstatus = 1 and is_active ='yes' condition is not given because some bom
- is under construction that is it is still in saved mode and they want see till where they have reach.
- """
- ret = sql("select distinct t1.name from `tabItem` t1, `tabBOM` t2 where t2.item = t1.name and t1.item_group = '%s' " % (item_group))
- return '~~~'.join([r[0] for r in ret])
-
-
-
- def get_bom_no(self,item_code):
- ret = sql("select name from `tabBOM` where item = '%s' " % (item_code))
- return '~~~'.join([r[0] for r in ret])
-
-
-
- def get_operations(self,bom_no):
- ret = sql("select operation_no,opn_description,workstation,hour_rate,time_in_mins from `tabBOM Operation` where parent = %s", bom_no, as_dict = 1)
- cost = sql("select dir_mat_as_per_mar , operating_cost , cost_as_per_mar from `tabBOM` where name = %s", bom_no, as_dict = 1)
-
- # Validate the BOM ENTRIES
- reply = []
-
- if ret:
- for r in ret:
- reply.append(['operation',cint(r['operation_no']), r['opn_description'] or '','%s'% bom_no,r['workstation'],flt(r['hour_rate']),flt(r['time_in_mins']),0,0,0])
-
- reply[0][7]= flt(cost[0]['dir_mat_as_per_mar'])
- reply[0][8]=flt(cost[0]['operating_cost'])
- reply[0][9]=flt(cost[0]['cost_as_per_mar'])
- return reply
-
-
-
- def get_item_bom(self,data):
- data = eval(data)
- reply = []
- ret = sql("select item_code,description,bom_no,qty,scrap,stock_uom,value_as_per_mar,moving_avg_rate from `tabBOM Item` where parent = '%s' and operation_no = '%s'" % (data['bom_no'],data['op_no']), as_dict =1 )
-
- for r in ret:
- item = sql("select is_manufactured_item, is_sub_contracted_item from `tabItem` where name = '%s'" % r['item_code'], as_dict=1)
- if not item[0]['is_manufactured_item'] == 'Yes' and not item[0]['is_sub_contracted_item'] =='Yes':
- #if item is not manufactured or it is not sub-contracted
- reply.append([ 'item_bom', r['item_code'] or '', r['description'] or '', r['bom_no'] or '', flt(r['qty']) or 0, r['stock_uom'] or '', flt(r['scrap']) or 0, flt(r['moving_avg_rate']) or 0, 1])
- else:
- # if it is manufactured or sub_contracted this will be considered(here item can be purchase item)
- reply.append([ 'item_bom', r['item_code'] or '', r['description'] or '', r['bom_no'] or '', flt(r['qty']) or 0, r['stock_uom'] or '', flt(r['scrap']) or 0, flt(r['value_as_per_mar']) or 0, 0])
- return reply
-
-
-
- #------------- Wrapper Code --------------
- def calculate_cost(self, bom_no):
- main_bom_list = get_obj('Production Control').traverse_bom_tree( bom_no = bom_no, qty = 1, calculate_cost = 1)
- main_bom_list.reverse()
- for bom in main_bom_list:
- bom_obj = get_obj('BOM', bom, with_children = 1)
- bom_obj.calculate_cost()
- return 'calculated'
-
-
-
- def get_bom_tree_list(self,args):
- arg = eval(args)
- i =[]
- for a in sql("select t1.name from `tabBOM` t1, `tabItem` t2 where t2.item_group like '%s' and t1.item like '%s'"%(arg['item_group'] +'%',arg['item_code'] + '%')):
- if a[0] not in i:
- i.append(a[0])
- return i
diff --git a/production/doctype/bom_control/bom_control.txt b/production/doctype/bom_control/bom_control.txt
deleted file mode 100644
index 3c322e1..0000000
--- a/production/doctype/bom_control/bom_control.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# DocType, BOM Control
-[
-
- # These values are common in all dictionaries
- {
- 'creation': '2012-03-27 14:36:02',
- 'docstatus': 0,
- 'modified': '2012-03-27 14:36:02',
- 'modified_by': u'Administrator',
- 'owner': u'Administrator'
- },
-
- # These values are common for all DocType
- {
- 'colour': u'White:FFF',
- 'doctype': 'DocType',
- 'issingle': 1,
- 'module': u'Production',
- 'name': '__common__',
- 'section_style': u'Simple',
- 'server_code_error': u' ',
- 'show_in_menu': 0,
- 'version': 108
- },
-
- # DocType, BOM Control
- {
- 'doctype': 'DocType',
- 'name': u'BOM Control'
- }
-]
\ No newline at end of file
diff --git a/production/doctype/bom_explosion_item/bom_explosion_item.txt b/production/doctype/bom_explosion_item/bom_explosion_item.txt
index 1ba688f..d1cac5a 100644
--- a/production/doctype/bom_explosion_item/bom_explosion_item.txt
+++ b/production/doctype/bom_explosion_item/bom_explosion_item.txt
@@ -1,138 +1,107 @@
-# DocType, BOM Explosion Item
[
-
- # These values are common in all dictionaries
- {
- 'creation': '2012-03-27 14:36:03',
- 'docstatus': 0,
- 'modified': '2012-03-27 14:36:03',
- 'modified_by': u'Administrator',
- 'owner': u'jai@webnotestech.com'
- },
-
- # These values are common for all DocType
- {
- 'autoname': u'FBD/.######',
- 'colour': u'White:FFF',
- 'default_print_format': u'Standard',
- 'doctype': 'DocType',
- 'istable': 1,
- 'module': u'Production',
- 'name': '__common__',
- 'read_only': 0,
- 'section_style': u'Simple',
- 'server_code_error': u' ',
- 'show_in_menu': 0,
- 'version': 24
- },
-
- # These values are common for all DocField
- {
- 'doctype': u'DocField',
- 'name': '__common__',
- 'parent': u'BOM Explosion Item',
- 'parentfield': u'fields',
- 'parenttype': u'DocType',
- 'permlevel': 0
- },
-
- # DocType, BOM Explosion Item
- {
- 'doctype': 'DocType',
- 'name': u'BOM Explosion Item'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'item_code',
- 'fieldtype': u'Link',
- 'label': u'Item Code',
- 'oldfieldname': u'item_code',
- 'oldfieldtype': u'Link',
- 'options': u'Item'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'description',
- 'fieldtype': u'Text',
- 'label': u'Description',
- 'oldfieldname': u'description',
- 'oldfieldtype': u'Text',
- 'width': u'300px'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'qty',
- 'fieldtype': u'Float',
- 'label': u'Qty',
- 'oldfieldname': u'qty',
- 'oldfieldtype': u'Currency'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'rate',
- 'fieldtype': u'Float',
- 'label': u'Rate',
- 'oldfieldname': u'standard_rate',
- 'oldfieldtype': u'Currency'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'amount',
- 'fieldtype': u'Float',
- 'label': u'Amount',
- 'oldfieldname': u'amount_as_per_sr',
- 'oldfieldtype': u'Currency'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'stock_uom',
- 'fieldtype': u'Link',
- 'label': u'Stock UOM',
- 'oldfieldname': u'stock_uom',
- 'oldfieldtype': u'Link',
- 'options': u'UOM'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'parent_bom',
- 'fieldtype': u'Link',
- 'hidden': 0,
- 'label': u'Parent BOM',
- 'oldfieldname': u'parent_bom',
- 'oldfieldtype': u'Link',
- 'width': u'250px'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'mat_detail_no',
- 'fieldtype': u'Data',
- 'hidden': 1,
- 'label': u'Mat Detail No'
- },
-
- # DocField
- {
- 'doctype': u'DocField',
- 'fieldname': u'qty_consumed_per_unit',
- 'fieldtype': u'Float',
- 'hidden': 0,
- 'label': u'Qty Consumed Per Unit',
- 'no_copy': 0
- }
+ {
+ "owner": "jai@webnotestech.com",
+ "docstatus": 0,
+ "creation": "2012-07-03 13:30:04",
+ "modified_by": "Administrator",
+ "modified": "2012-12-10 12:05:27"
+ },
+ {
+ "read_only": 0,
+ "istable": 1,
+ "autoname": "FBD/.######",
+ "name": "__common__",
+ "default_print_format": "Standard",
+ "doctype": "DocType",
+ "module": "Production"
+ },
+ {
+ "name": "__common__",
+ "parent": "BOM Explosion Item",
+ "doctype": "DocField",
+ "parenttype": "DocType",
+ "permlevel": 1,
+ "parentfield": "fields"
+ },
+ {
+ "name": "BOM Explosion Item",
+ "doctype": "DocType"
+ },
+ {
+ "oldfieldtype": "Link",
+ "doctype": "DocField",
+ "label": "Item Code",
+ "oldfieldname": "item_code",
+ "fieldname": "item_code",
+ "fieldtype": "Link",
+ "options": "Item"
+ },
+ {
+ "oldfieldtype": "Text",
+ "doctype": "DocField",
+ "label": "Description",
+ "oldfieldname": "description",
+ "width": "300px",
+ "fieldname": "description",
+ "fieldtype": "Text"
+ },
+ {
+ "oldfieldtype": "Currency",
+ "doctype": "DocField",
+ "label": "Qty",
+ "oldfieldname": "qty",
+ "fieldname": "qty",
+ "fieldtype": "Float"
+ },
+ {
+ "oldfieldtype": "Currency",
+ "doctype": "DocField",
+ "label": "Rate",
+ "oldfieldname": "standard_rate",
+ "fieldname": "rate",
+ "fieldtype": "Float"
+ },
+ {
+ "oldfieldtype": "Currency",
+ "doctype": "DocField",
+ "label": "Amount",
+ "oldfieldname": "amount_as_per_sr",
+ "fieldname": "amount",
+ "fieldtype": "Float"
+ },
+ {
+ "oldfieldtype": "Link",
+ "doctype": "DocField",
+ "label": "Stock UOM",
+ "oldfieldname": "stock_uom",
+ "fieldname": "stock_uom",
+ "fieldtype": "Link",
+ "options": "UOM"
+ },
+ {
+ "oldfieldtype": "Link",
+ "doctype": "DocField",
+ "label": "Parent BOM",
+ "oldfieldname": "parent_bom",
+ "width": "250px",
+ "fieldname": "parent_bom",
+ "fieldtype": "Link",
+ "hidden": 0,
+ "options": "BOM"
+ },
+ {
+ "doctype": "DocField",
+ "label": "Mat Detail No",
+ "fieldname": "mat_detail_no",
+ "fieldtype": "Data",
+ "hidden": 1
+ },
+ {
+ "no_copy": 0,
+ "doctype": "DocField",
+ "label": "Qty Consumed Per Unit",
+ "fieldname": "qty_consumed_per_unit",
+ "fieldtype": "Float",
+ "hidden": 0
+ }
]
\ No newline at end of file
diff --git a/production/doctype/production_control/__init__.py b/production/doctype/production_control/__init__.py
deleted file mode 100644
index baffc48..0000000
--- a/production/doctype/production_control/__init__.py
+++ /dev/null
@@ -1 +0,0 @@
-from __future__ import unicode_literals
diff --git a/production/doctype/production_control/production_control.py b/production/doctype/production_control/production_control.py
deleted file mode 100644
index b389e69..0000000
--- a/production/doctype/production_control/production_control.py
+++ /dev/null
@@ -1,138 +0,0 @@
-# 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 <http://www.gnu.org/licenses/>.
-
-from __future__ import unicode_literals
-import webnotes
-
-from webnotes.utils import cstr, flt, get_defaults, now, nowdate
-from webnotes.model import db_exists
-from webnotes.model.doc import Document
-from webnotes.model.wrapper import copy_doclist
-from webnotes.model.code import get_obj
-from webnotes import msgprint
-
-sql = webnotes.conn.sql
-
-
-
-class DocType:
- def __init__( self, doc, doclist=[]):
- self.doc = doc
- self.doclist = doclist
- self.pur_items = {}
- self.bom_list = []
- self.sub_assembly_items = []
- self.item_master = {}
-
- def traverse_bom_tree( self, bom_no, qty, ext_pur_items = 0, ext_sub_assembly_items = 0, calculate_cost = 0, maintain_item_master = 0 ):
- count, bom_list, qty_list = 0, [bom_no], [qty]
- while (count < len(bom_list)):
- # get child items from BOM MAterial Table.
- child_items = sql("select item_code, bom_no, qty, qty_consumed_per_unit from `tabBOM Item` where parent = %s", bom_list[count], as_dict = 1)
- child_items = child_items and child_items or []
- for item in child_items:
- # Calculate qty required for FG's qty.
- item['reqd_qty'] = flt(qty) * ((count == 0) and 1 or flt(qty_list[count]) )* flt(item['qty_consumed_per_unit'])
-
- # extracting Purchase Items
- if ext_pur_items and not item['bom_no']:
- self.pur_items[item['item_code']] = flt(self.pur_items.get(item['item_code'], 0)) + flt(item['reqd_qty'])
-
- # For calculate cost extracting BOM Items check for duplicate boms, this optmizes the time complexity for while loop.
- if calculate_cost and item['bom_no'] and (item['bom_no'] not in bom_list):
- bom_list.append(item['bom_no'])
- qty_list.append(item['reqd_qty'])
-
- # Here repeated bom are considered to calculate total qty of raw material required
- if not calculate_cost and item['bom_no']:
- bom_list.append(item['bom_no'])
- qty_list.append(item['reqd_qty'])
-
- count += 1
- return bom_list
-
-
-
- # Raise Production Order
- def create_production_order(self, items):
- """Create production order. Called from Production Planning Tool"""
-
- default_values = {
- 'posting_date' : nowdate(),
- 'origin' : 'MRP',
- 'wip_warehouse' : '',
- 'fg_warehouse' : '',
- 'status' : 'Draft',
- 'fiscal_year' : get_defaults()['fiscal_year']
- }
- pro_list = []
-
- for item_so in items:
- if item_so[1]:
- self.validate_production_order_against_so(
- item_so[0], item_so[1], items[item_so].get("qty"))
-
- pro_doc = Document('Production Order')
- pro_doc.production_item = item_so[0]
- pro_doc.sales_order = item_so[1]
- for key in items[item_so]:
- pro_doc.fields[key] = items[item_so][key]
-
- for key in default_values:
- pro_doc.fields[key] = default_values[key]
-
- pro_doc.save(new = 1)
- pro_list.append(pro_doc.name)
-
- return pro_list
-
- def validate_production_order_against_so(self, item, sales_order, qty, pro_order=None):
- # already ordered qty
- ordered_qty_against_so = webnotes.conn.sql("""select sum(qty) from `tabProduction Order`
- where production_item = %s and sales_order = %s and name != %s""",
- (item, sales_order, cstr(pro_order)))[0][0]
- # qty including current
- total_ordered_qty_against_so = flt(ordered_qty_against_so) + flt(qty)
-
- # get qty from Sales Order Item table
- so_item_qty = webnotes.conn.sql("""select sum(qty) from `tabSales Order Item`
- where parent = %s and item_code = %s""", (sales_order, item))[0][0]
- # get qty from Packing Item table
- dnpi_qty = webnotes.conn.sql("""select sum(qty) from `tabDelivery Note Packing Item`
- where parent = %s and parenttype = 'Sales Order' and item_code = %s""",
- (sales_order, item))[0][0]
- # total qty in SO
- so_qty = flt(so_item_qty) + flt(dnpi_qty)
-
- if total_ordered_qty_against_so > so_qty:
- msgprint("""Total production order qty for item: %s against sales order: %s \
- will be %s, which is greater than sales order qty (%s).
- Please reduce qty or remove the item.""" %
- (item, sales_order, total_ordered_qty_against_so, so_qty), raise_exception=1)
-
- def update_bom(self, bom_no):
- main_bom_list = self.traverse_bom_tree(bom_no, 1)
- main_bom_list.reverse()
- # run calculate cost and get
- for bom in main_bom_list:
- if bom and bom not in self.check_bom_list:
- bom_obj = get_obj('BOM', bom, with_children = 1)
- bom_obj.doc.save()
- bom_obj.check_recursion()
- bom_obj.update_flat_bom_engine()
- bom_obj.doc.docstatus = 1
- bom_obj.doc.save()
- self.check_bom_list.append(bom)
\ No newline at end of file
diff --git a/production/doctype/production_control/production_control.txt b/production/doctype/production_control/production_control.txt
deleted file mode 100644
index 4f8564c..0000000
--- a/production/doctype/production_control/production_control.txt
+++ /dev/null
@@ -1,31 +0,0 @@
-# DocType, Production Control
-[
-
- # These values are common in all dictionaries
- {
- 'creation': '2012-03-27 14:36:05',
- 'docstatus': 0,
- 'modified': '2012-03-27 14:36:05',
- 'modified_by': u'Administrator',
- 'owner': u'Administrator'
- },
-
- # These values are common for all DocType
- {
- 'colour': u'White:FFF',
- 'doctype': 'DocType',
- 'issingle': 1,
- 'module': u'Production',
- 'name': '__common__',
- 'section_style': u'Simple',
- 'server_code_error': u' ',
- 'show_in_menu': 0,
- 'version': 19
- },
-
- # DocType, Production Control
- {
- 'doctype': 'DocType',
- 'name': u'Production Control'
- }
-]
\ No newline at end of file
diff --git a/production/doctype/production_order/production_order.js b/production/doctype/production_order/production_order.js
index 7949757..85d9633 100644
--- a/production/doctype/production_order/production_order.js
+++ b/production/doctype/production_order/production_order.js
@@ -92,12 +92,9 @@
AND `tabProject`.name LIKE "%s" ORDER BY `tabProject`.name ASC LIMIT 50';
}
-cur_frm.fields_dict['bom_no'].get_query = function(doc) {
- if (doc.production_item){
- return 'SELECT DISTINCT `tabBOM`.`name` FROM `tabBOM` WHERE `tabBOM`.`is_active` = "Yes" AND `tabBOM`.docstatus = 1 AND `tabBOM`.`item` = "' + cstr(doc.production_item) + '" AND`tabBOM`.%(key)s LIKE "%s" ORDER BY `tabBOM`.`name` LIMIT 50';
- }
- else {
- alert(" Please Enter Production Item First.")
- }
-}
+cur_frm.set_query("bom_no", function(doc) {
+ if (doc.production_item) {
+ return erpnext.queries.bom({item: cstr(doc.production_item)});
+ } else msgprint(" Please enter Production Item first");
+});
\ No newline at end of file
diff --git a/production/doctype/production_order/production_order.py b/production/doctype/production_order/production_order.py
index 5314971..07073da 100644
--- a/production/doctype/production_order/production_order.py
+++ b/production/doctype/production_order/production_order.py
@@ -72,8 +72,33 @@
where name=%s and docstatus = 1""", self.doc.sales_order):
msgprint("Sales Order: %s is not valid" % self.doc.sales_order, raise_exception=1)
- get_obj("Production Control").validate_production_order_against_so(
- self.doc.production_item, self.doc.sales_order, self.doc.qty, self.doc.name)
+ self.validate_production_order_against_so()
+
+
+ def validate_production_order_against_so(self):
+ # already ordered qty
+ ordered_qty_against_so = webnotes.conn.sql("""select sum(qty) from `tabProduction Order`
+ where production_item = %s and sales_order = %s and docstatus < 2""",
+ (self.doc.production_item, self.doc.sales_order))[0][0]
+
+
+ # get qty from Sales Order Item table
+ so_item_qty = webnotes.conn.sql("""select sum(qty) from `tabSales Order Item`
+ where parent = %s and item_code = %s""",
+ (self.doc.sales_order, self.doc.production_item))[0][0]
+ # get qty from Packing Item table
+ dnpi_qty = webnotes.conn.sql("""select sum(qty) from `tabDelivery Note Packing Item`
+ where parent = %s and parenttype = 'Sales Order' and item_code = %s""",
+ (self.doc.sales_order, self.doc.production_item))[0][0]
+ # total qty in SO
+ so_qty = flt(so_item_qty) + flt(dnpi_qty)
+
+ if ordered_qty_against_so > so_qty:
+ msgprint("""Total production order qty for item: %s against sales order: %s \
+ will be %s, which is greater than sales order qty (%s).
+ Please reduce qty or remove the item.""" %
+ (self.doc.production_item, self.doc.sales_order,
+ ordered_qty_against_so, so_qty), raise_exception=1)
def stop_unstop(self, status):
diff --git a/production/doctype/production_planning_tool/production_planning_tool.js b/production/doctype/production_planning_tool/production_planning_tool.js
index ac4d76d..970da65 100644
--- a/production/doctype/production_planning_tool/production_planning_tool.js
+++ b/production/doctype/production_planning_tool/production_planning_tool.js
@@ -51,10 +51,9 @@
cur_frm.fields_dict['pp_details'].grid.get_field('bom_no').get_query = function(doc) {
var d = locals[this.doctype][this.docname];
- return 'SELECT DISTINCT `tabBOM`.`name` \
- FROM `tabBOM` WHERE `tabBOM`.`item` = "' + d.item_code +
- '" AND `tabBOM`.`is_active` = "Yes" AND `tabBOM`.docstatus = 1 \
- AND `tabBOM`.`name` like "%s" ORDER BY `tabBOM`.`name` LIMIT 50';
+ if (d.item_code) {
+ return erpnext.queries.bom({item: cstr(d.item_code)});
+ } else msgprint(" Please enter Item first");
}
cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query;
diff --git a/production/doctype/production_planning_tool/production_planning_tool.py b/production/doctype/production_planning_tool/production_planning_tool.py
index 64ac73d..fb87fb2 100644
--- a/production/doctype/production_planning_tool/production_planning_tool.py
+++ b/production/doctype/production_planning_tool/production_planning_tool.py
@@ -16,8 +16,8 @@
from __future__ import unicode_literals
import webnotes
-from webnotes.utils import cstr, flt
-from webnotes.model.doc import addchild
+from webnotes.utils import cstr, flt, nowdate, get_defaults
+from webnotes.model.doc import addchild, Document
from webnotes.model.wrapper import getlist
from webnotes.model.code import get_obj
from webnotes import msgprint
@@ -186,7 +186,7 @@
self.validate_data()
items = self.get_distinct_items_and_boms()[1]
- pro = get_obj('Production Control').create_production_order(items)
+ pro = self.create_production_order(items)
if pro:
msgprint("Following Production Order has been generated:\n" + '\n'.join(pro))
else :
@@ -199,15 +199,39 @@
for d in self.doclist.get({"parentfield": "pp_details"}):
bom_dict[d.bom_no] = bom_dict.get(d.bom_no, 0) + flt(d.planned_qty)
item_dict[(d.item_code, d.sales_order)] = {
- "qty" : flt(item_dict.get((d.item_code, d.sales_order), {}).get("qty")) + \
- flt(d.planned_qty),
- "bom_no": d.bom_no,
- "description": d.description,
- "stock_uom": d.stock_uom,
+ "qty" : flt(item_dict.get((d.item_code, d.sales_order), \
+ {}).get("qty")) + flt(d.planned_qty),
+ "bom_no" : d.bom_no,
+ "description" : d.description,
+ "stock_uom" : d.stock_uom,
"use_multi_level_bom": self.doc.use_multi_level_bom,
- "company": self.doc.company,
+ "company" : self.doc.company,
+ "posting_date" : nowdate(),
+ "origin" : "MRP",
+ "wip_warehouse" : "",
+ "fg_warehouse" : "",
+ "status" : "Draft",
+ "fiscal_year" : get_defaults()["fiscal_year"]
}
return bom_dict, item_dict
+
+ def create_production_order(self, items):
+ """Create production order. Called from Production Planning Tool"""
+
+ pro_list = []
+ for item_so in items:
+ pro_doc = Document('Production Order')
+ pro_doc.production_item = item_so[0]
+ pro_doc.sales_order = item_so[1]
+ for key in items[item_so]:
+ pro_doc.fields[key] = items[item_so][key]
+
+ pro_doc.save(new = 1)
+
+ get_obj("Production Order", pro_doc.name).validate_production_order_against_so()
+ pro_list.append(pro_doc.name)
+
+ return pro_list
def download_raw_materials(self):
""" Create csv data for required raw material to produce finished goods"""
diff --git a/production/page/production_home/production_home.html b/production/page/production_home/production_home.html
index 50f99b3..4ac410c 100644
--- a/production/page/production_home/production_home.html
+++ b/production/page/production_home/production_home.html
@@ -23,6 +23,16 @@
<div class="layout-side-section">
<div class="psidebar">
<div class="section">
+ <div class="section-head">Tools</div>
+ <div class="section-body">
+ <div class="section-item">
+ <a class="section-link"
+ title = "BOM Replace Tool"
+ href="#!Form/BOM Replace Tool">BOM Replace Tool</a>
+ </div>
+ </div>
+ </div>
+ <div class="section">
<div class="section-head">Setup</div>
<div class="section-body">
<div class="section-item">
diff --git a/public/js/utils.js b/public/js/utils.js
index d02fdb3..599232d 100644
--- a/public/js/utils.js
+++ b/public/js/utils.js
@@ -103,4 +103,24 @@
AND tabAccount.%(key)s LIKE "%s" ' + (conditions
? (" AND " + conditions.join(" AND "))
: "")
+}
+
+erpnext.queries.bom = function(opts) {
+ conditions = [];
+ if (opts) {
+ $.each(opts, function(key, val) {
+ if (esc_quotes(val).charAt(0) != "!")
+ conditions.push("tabBOM.`" + key + "`='"+esc_quotes(val)+"'");
+ else
+ conditions.push("tabBOM.`" + key + "`!='"+esc_quotes(val).substr(1)+"'");
+ });
+ }
+
+ return 'SELECT tabBOM.name, tabBOM.item \
+ FROM tabBOM \
+ WHERE tabBOM.docstatus=1 \
+ AND tabBOM.is_active="Yes" \
+ AND tabBOM.%(key)s LIKE "%s" ' + (conditions.length
+ ? (" AND " + conditions.join(" AND "))
+ : "")
}
\ 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 67f1643..04d898a 100644
--- a/stock/doctype/stock_entry/stock_entry.py
+++ b/stock/doctype/stock_entry/stock_entry.py
@@ -379,7 +379,7 @@
d.t_warehouse = self.doc.to_warehouse
if not (d.s_warehouse or d.t_warehouse):
- msgprint("Atleast one warehouse is mandatory for Stock Entry ")
+ msgprint("Atleast one warehouse is mandatory for Stock Entry")
raise Exception
if d.s_warehouse and not sql("select name from tabWarehouse where name = '%s'" % d.s_warehouse):
msgprint("Invalid Warehouse: %s" % self.doc.s_warehouse)
@@ -390,6 +390,7 @@
if d.s_warehouse == d.t_warehouse:
msgprint("Source and Target Warehouse Cannot be Same.")
raise Exception
+
if self.doc.purpose == 'Material Issue':
if not cstr(d.s_warehouse):
msgprint("Source Warehouse is Mandatory for Purpose => 'Material Issue'")
@@ -415,6 +416,7 @@
if not cstr(d.s_warehouse):
msgprint("Please Enter Source Warehouse at Row No %s." % (cstr(d.idx)))
raise Exception
+
if self.doc.process == 'Backflush':
if flt(d.fg_item):
if cstr(pro_obj.doc.production_item) != cstr(d.item_code):
diff --git a/tests/data/item/android_jack_d.txt b/tests/data/item/android_jack_d.txt
index 24944e3..c7edeee 100644
--- a/tests/data/item/android_jack_d.txt
+++ b/tests/data/item/android_jack_d.txt
@@ -18,6 +18,9 @@
'has_serial_no': u'No',
'inspection_required': u'No',
'is_purchase_item': u'Yes',
+ 'is_manufactured_item': u'Yes',
+ 'is_sub_contracted_item': 'Yes',
+ 'is_pro_applicable': 'Yes',
'is_sales_item': u'Yes',
'is_service_item': u'No',
'is_stock_item': u'Yes',
diff --git a/tests/data/item/android_jack_s.txt b/tests/data/item/android_jack_s.txt
index feaceef..d32d793 100644
--- a/tests/data/item/android_jack_s.txt
+++ b/tests/data/item/android_jack_s.txt
@@ -18,6 +18,9 @@
'has_serial_no': u'No',
'inspection_required': u'No',
'is_purchase_item': u'Yes',
+ 'is_manufactured_item': u'Yes',
+ 'is_sub_contracted_item': 'Yes',
+ 'is_pro_applicable': 'Yes',
'is_sales_item': u'Yes',
'is_service_item': u'No',
'is_stock_item': u'Yes',
diff --git a/tests/data/item/home_desktop_100.txt b/tests/data/item/home_desktop_100.txt
index 19ef01d..6745c0d 100644
--- a/tests/data/item/home_desktop_100.txt
+++ b/tests/data/item/home_desktop_100.txt
@@ -18,6 +18,7 @@
'has_serial_no': u'No',
'inspection_required': u'No',
'is_purchase_item': u'Yes',
+ 'is_manufactured_item': u'No',
'is_sales_item': u'Yes',
'is_service_item': u'No',
'is_stock_item': u'Yes',