merge, fixed production cleanup patch

This commit is contained in:
Nabin Hait
2012-12-10 19:28:38 +05:30
162 changed files with 75 additions and 4216 deletions

View File

@@ -0,0 +1 @@
from __future__ import unicode_literals

View File

@@ -0,0 +1,179 @@
// 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/>.
// 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");
}
}
cur_frm.cscript.item = function(doc, dt, dn) {
if (doc.item) {
get_server_fields('get_item_detail',doc.item,'',doc,dt,dn,1);
}
}
cur_frm.cscript.workstation = function(doc,dt,dn) {
var d = locals[dt][dn];
if (d.workstation) {
var callback = function(r, rt) {
calculate_op_cost(doc, dt, dn);
calculate_total(doc);
}
get_server_fields('get_workstation_details', d.workstation,
'bom_operations', doc, dt, dn, 1, callback);
}
}
cur_frm.cscript.hour_rate = function(doc, dt, dn) {
calculate_op_cost(doc, dt, dn);
calculate_total(doc);
}
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);
}
var get_bom_material_detail= function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.item_code) {
wn.call({
doc: cur_frm.doc,
method: "get_bom_material_detail",
args: {
'item_code': d.item_code,
'bom_no': d.bom_no != null ? d.bom_no: '',
'qty': d.qty
},
callback: function(r) {
d = locals[cdt][cdn];
$.extend(d, r.message);
refresh_field("bom_materials");
doc = locals[doc.doctype][doc.name];
calculate_rm_cost(doc, cdt, cdn);
calculate_total(doc);
},
freeze: true
});
}
}
cur_frm.cscript.qty = function(doc, cdt, cdn) {
calculate_rm_cost(doc, cdt, cdn);
calculate_total(doc);
}
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)
$c_obj(make_doclist(cdt, cdn), 'manage_default_bom', '', '');
}
cur_frm.cscript.is_active = function(doc, dt, dn) {
if (!doc.__islocal)
$c_obj(make_doclist(dt, dn), 'manage_active_bom', '', '');
}
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(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;
}
doc.operating_cost = total_op_cost;
refresh_field('operating_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');
total_rm_cost += amt;
}
doc.raw_material_cost = total_rm_cost;
refresh_field('raw_material_cost');
}
// Calculate Total Cost
var calculate_total = function(doc) {
doc.total_cost = flt(doc.raw_material_cost) + flt(doc.operating_cost);
refresh_field('total_cost');
}
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';
}
cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
return 'SELECT `tabProject`.name FROM `tabProject` \
WHERE `tabProject`.status not in ("Completed", "Cancelled") \
AND `tabProject`.name LIKE "%s" ORDER BY `tabProject`.name ASC LIMIT 50';
}
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';
}
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';
}
cur_frm.cscript.validate = function(doc, dt, dn) {
calculate_op_cost(doc, dt, dn);
calculate_rm_cost(doc, dt, dn);
calculate_total(doc);
}

View File

@@ -0,0 +1,433 @@
# 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, cstr, flt, now, nowdate
from webnotes.model.doc import Document, addchild
from webnotes.model.wrapper import getlist
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
def autoname(self):
last_name = sql("""select max(name) from `tabBOM`
where name like 'BOM/%s/%%'""" % self.doc.item)
if last_name:
idx = cint(cstr(last_name[0][0]).split('/')[-1]) + 1
else:
idx = 1
self.doc.name = 'BOM/' + self.doc.item + ('/%.3i' % idx)
def get_item_det(self, item_code):
item = sql("""select name, is_asset_item, is_purchase_item, docstatus,
is_sub_contracted_item, description, stock_uom, default_bom,
last_purchase_rate, standard_rate, is_manufactured_item
from `tabItem` where item_code = %s""", item_code, as_dict = 1)
return item
def get_item_detail(self, item_code):
""" Get stock uom and description for finished good item"""
item = self.get_item_det(item_code)
ret={
'description' : item and item[0]['description'] or '',
'uom' : item and item[0]['stock_uom'] or ''
}
return ret
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)
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: %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("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)
def get_bom_material_detail(self):
""" Get raw material details like uom, desc and rate"""
arg = webnotes.form_dict.get('args')
import json
arg = json.loads(arg)
item = self.get_item_det(arg['item_code'])
self.validate_rm_item(item)
arg['bom_no'] = arg['bom_no'] or item and cstr(item[0]['default_bom']) or ''
arg.update(item[0])
rate = self.get_rm_rate(arg)
ret_item = {
'description' : item and arg['description'] or '',
'stock_uom' : item and arg['stock_uom'] or '',
'bom_no' : arg['bom_no'],
'rate' : rate
}
return ret_item
def get_rm_rate(self, arg):
""" Get raw material rate as per selected method, if bom exists takes bom cost """
if arg['bom_no']:
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)
elif self.doc.rm_cost_as_per == 'Last Purchase Rate':
rate = arg['last_purchase_rate']
elif self.doc.rm_cost_as_per == 'Standard Rate':
rate = arg['standard_rate']
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
as per valuation method (MAR/FIFO)
as on costing date
"""
dt = self.doc.costing_date or nowdate()
time = self.doc.costing_date == nowdate() and now().split()[1] or '23:59'
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))
if r:
rate.append(r)
return rate and flt(sum(rate))/len(rate) or 0
def manage_default_bom(self):
""" 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))
# update default bom in Item Master
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))
def manage_active_bom(self):
""" Manage active/inactive """
if self.doc.is_active == 'Yes':
self.validate()
else:
self.check_active_parent_boms()
def check_active_parent_boms(self):
""" Check parent BOM before making it inactive """
act_pbom = sql("""select distinct t1.parent from `tabBOM Item` t1, `tabBOM` t2
where t1.bom_no =%s and t2.name = t1.parent and t2.is_active = 'Yes'
and t2.docstatus = 1 and t1.docstatus =1 """, self.doc.name)
if act_pbom and act_pbom[0][0]:
msgprint("""Sorry cannot inactivate as BOM: %s is child
of one or many other active parent BOMs""" % self.doc.name, raise_exception=1)
def calculate_cost(self):
"""Calculate bom totals"""
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()
def calculate_op_cost(self):
"""Update workstation rate and calculates totals"""
total_op_cost = 0
for d in getlist(self.doclist, 'bom_operations'):
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 += 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'):
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
def validate_main_item(self):
""" 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)
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)
else:
# add operation in op list
self.op.append(cstr(d.operation_no))
def validate_materials(self):
""" Validate raw material entries """
check_list = []
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)
item = self.get_item_det(m.item_code)
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)
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_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)
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)
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)
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)
else:
check_list.append([cstr(item), cstr(op)])
def validate(self):
self.validate_main_item()
self.validate_operations()
self.validate_materials()
def check_recursion(self):
""" 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])))
count = count + 1
for b in boms:
if b[0] == self.doc.name:
msgprint("""Recursion Occured => '%s' cannot be '%s' of '%s'.
""" % (cstr(b), cstr(d[2]), self.doc.name), raise_exception = 1)
if b[0]:
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):
"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 = 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)
for d in child_fb_items:
self.cur_flat_bom_items.append({
'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)
})
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'):
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(self):
""" Update Flat BOM, following will be correct data"""
self.get_flat_bom_items()
self.add_to_flat_bom_detail()
def get_parent_bom_list(self, bom_no):
p_bom = sql("select parent from `tabBOM Item` where bom_no = '%s'" % bom_no)
return p_bom and [i[0] for i in p_bom] or []
def on_submit(self):
self.manage_default_bom()
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)
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=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()

View File

@@ -0,0 +1,342 @@
[
{
"owner": "Administrator",
"docstatus": 0,
"creation": "2012-07-03 13:30:03",
"modified_by": "Administrator",
"modified": "2012-12-10 18:30:00"
},
{
"istable": 0,
"in_create": 0,
"allow_print": 0,
"search_fields": "item",
"module": "Manufacturing",
"document_type": "Master",
"allow_attach": 0,
"read_only": 0,
"allow_email": 0,
"hide_heading": 0,
"issingle": 0,
"name": "__common__",
"default_print_format": "Standard",
"allow_rename": 0,
"doctype": "DocType",
"is_submittable": 1,
"hide_toolbar": 0,
"allow_copy": 0
},
{
"name": "__common__",
"parent": "BOM",
"doctype": "DocField",
"parenttype": "DocType",
"parentfield": "fields"
},
{
"name": "__common__",
"parent": "BOM",
"read": 1,
"doctype": "DocPerm",
"parenttype": "DocType",
"parentfield": "permissions"
},
{
"name": "BOM",
"doctype": "DocType"
},
{
"oldfieldtype": "Column Break",
"doctype": "DocField",
"width": "50%",
"fieldname": "column_break0",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"description": "Select the item code for which Bill of Material is being created",
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Item",
"oldfieldname": "item",
"permlevel": 0,
"fieldname": "item",
"fieldtype": "Link",
"search_index": 1,
"reqd": 1,
"in_filter": 1,
"options": "Item"
},
{
"description": "Total quantity of items for which raw materials required and operations done will be defined",
"oldfieldtype": "Currency",
"doctype": "DocField",
"label": "Quantity",
"oldfieldname": "quantity",
"fieldname": "quantity",
"fieldtype": "Currency",
"reqd": 1,
"permlevel": 0
},
{
"doctype": "DocField",
"width": "50%",
"fieldname": "column_break1",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"no_copy": 1,
"oldfieldtype": "Select",
"allow_on_submit": 1,
"doctype": "DocField",
"label": "Is Active",
"oldfieldname": "is_active",
"permlevel": 0,
"fieldname": "is_active",
"fieldtype": "Select",
"reqd": 1,
"hidden": 0,
"options": "\nYes\nNo"
},
{
"no_copy": 1,
"oldfieldtype": "Check",
"allow_on_submit": 1,
"doctype": "DocField",
"label": "Is Default",
"oldfieldname": "is_default",
"fieldname": "is_default",
"fieldtype": "Check",
"permlevel": 0
},
{
"oldfieldtype": "Section Break",
"doctype": "DocField",
"label": "Operations",
"fieldname": "operations",
"fieldtype": "Section Break",
"permlevel": 0
},
{
"description": "Specify the operations, operating cost and give a unique Operation no to your operations.",
"oldfieldtype": "Table",
"doctype": "DocField",
"label": "BOM Operations",
"oldfieldname": "bom_operations",
"options": "BOM Operation",
"fieldname": "bom_operations",
"fieldtype": "Table",
"permlevel": 0
},
{
"oldfieldtype": "Section Break",
"doctype": "DocField",
"label": "Materials",
"fieldname": "materials",
"fieldtype": "Section Break",
"permlevel": 0
},
{
"doctype": "DocField",
"label": "Consider Raw Material Cost As Per",
"options": "Valuation Rate\nLast Purchase Rate\nStandard Rate",
"fieldname": "rm_cost_as_per",
"fieldtype": "Select",
"permlevel": 0
},
{
"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",
"doctype": "DocField",
"label": "BOM Item",
"oldfieldname": "bom_materials",
"options": "BOM Item",
"fieldname": "bom_materials",
"fieldtype": "Table",
"permlevel": 0
},
{
"oldfieldtype": "Section Break",
"doctype": "DocField",
"label": "Costing",
"fieldname": "costing",
"fieldtype": "Section Break",
"permlevel": 0
},
{
"doctype": "DocField",
"label": "Raw Material Cost",
"fieldname": "raw_material_cost",
"fieldtype": "Float",
"permlevel": 1
},
{
"doctype": "DocField",
"label": "Operating Cost",
"fieldname": "operating_cost",
"fieldtype": "Float",
"permlevel": 1
},
{
"doctype": "DocField",
"fieldname": "col_break24",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"doctype": "DocField",
"label": "Total Cost",
"fieldname": "total_cost",
"fieldtype": "Float",
"permlevel": 1
},
{
"doctype": "DocField",
"label": "More Info",
"fieldname": "more_info_section",
"fieldtype": "Section Break",
"permlevel": 0
},
{
"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",
"options": "Project",
"fieldname": "project_name",
"fieldtype": "Link",
"permlevel": 0,
"in_filter": 1
},
{
"doctype": "DocField",
"label": "Item UOM",
"options": "link:UOM",
"fieldname": "uom",
"fieldtype": "Select",
"permlevel": 1
},
{
"oldfieldtype": "Text",
"doctype": "DocField",
"label": "Item Description",
"oldfieldname": "description",
"fieldname": "description",
"fieldtype": "Small Text",
"permlevel": 0
},
{
"doctype": "DocField",
"fieldname": "col_break23",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"oldfieldtype": "Data",
"doctype": "DocField",
"label": "Maintained By",
"oldfieldname": "maintained_by",
"fieldname": "maintained_by",
"fieldtype": "Data",
"permlevel": 0
},
{
"print_hide": 1,
"no_copy": 1,
"doctype": "DocField",
"label": "Amended From",
"options": "Sales Invoice",
"fieldname": "amended_from",
"fieldtype": "Link",
"permlevel": 1
},
{
"print_hide": 1,
"description": "The date at which current entry is corrected in the system.",
"no_copy": 1,
"depends_on": "eval:doc.amended_from",
"doctype": "DocField",
"label": "Amendment Date",
"fieldname": "amendment_date",
"fieldtype": "Date",
"permlevel": 0
},
{
"no_copy": 1,
"oldfieldtype": "Text",
"doctype": "DocField",
"label": "Remarks",
"oldfieldname": "remarks",
"fieldname": "remarks",
"fieldtype": "Small Text",
"permlevel": 0
},
{
"print_hide": 0,
"doctype": "DocField",
"label": "BOM Explosion Items",
"options": "Simple",
"fieldname": "section_break0",
"fieldtype": "Section Break",
"hidden": 0,
"permlevel": 0
},
{
"print_hide": 1,
"permlevel": 1,
"no_copy": 1,
"oldfieldtype": "Table",
"doctype": "DocField",
"label": "BOM Explosion Item",
"oldfieldname": "flat_bom_details",
"default": "No Toolbar",
"fieldname": "flat_bom_details",
"fieldtype": "Table",
"hidden": 0,
"options": "BOM Explosion Item"
},
{
"create": 1,
"doctype": "DocPerm",
"submit": 1,
"write": 1,
"role": "System Manager",
"cancel": 1,
"permlevel": 0
},
{
"doctype": "DocPerm",
"role": "System Manager",
"permlevel": 1
},
{
"create": 1,
"doctype": "DocPerm",
"submit": 1,
"write": 1,
"role": "Manufacturing Manager",
"cancel": 1,
"permlevel": 0
},
{
"doctype": "DocPerm",
"role": "Manufacturing Manager",
"permlevel": 1
},
{
"create": 1,
"doctype": "DocPerm",
"submit": 1,
"write": 1,
"role": "Manufacturing User",
"cancel": 1,
"permlevel": 0
},
{
"doctype": "DocPerm",
"role": "Manufacturing User",
"permlevel": 1
}
]

View File

@@ -0,0 +1,48 @@
// render
wn.doclistviews['BOM'] = wn.views.ListView.extend({
init: function(d) {
this._super(d);
this.fields = this.fields.concat([
'`tabBOM`.item',
'`tabBOM`.uom',
'IFNULL(`tabBOM`.quantity, 0) as quantity',
'`tabBOM`.is_active',
'`tabBOM`.costing_date',
'`tabBOM`.total_cost',
'`tabBOM`.description',
]);
this.stats = this.stats.concat(['company']);
},
prepare_data: function(data) {
this._super(data);
data.costing_date = wn.datetime.str_to_user(data.costing_date);
data.description = (data.is_active === 'Yes' ? '' : '[Inactive] ') + data.description;
},
columns: [
{width: '3%', content: 'check'},
{width: '3%', content: 'docstatus'},
{width: '15%', content: 'name'},
{width: '15%', content: 'item'},
{width: '23%', content: 'description+tags'},
{
width: '12%',
content: function(parent, data) {
$(parent).html(data.quantity + ' ' + data.uom)
},
css: {'text-align':'right'},
},
{
width: '20%',
content: function(parent, data) {
$(parent).html(sys_defaults.currency + " "
+ fmt_money(data.total_cost));
},
css: {'text-align': 'right'},
},
{width: '12%', content:'costing_date', css: {
'text-align': 'right', 'color':'#777'
}},
]
});

View File

@@ -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()