bom cleanup: update cost and flat bom by traversing full tree
This commit is contained in:
parent
af63b16be8
commit
5999944a5c
@ -17,11 +17,11 @@
|
|||||||
// On REFRESH
|
// On REFRESH
|
||||||
cur_frm.cscript.refresh = function(doc,dt,dn){
|
cur_frm.cscript.refresh = function(doc,dt,dn){
|
||||||
cur_frm.toggle_enable("item", doc.__islocal);
|
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) {
|
cur_frm.cscript.item = function(doc, dt, dn) {
|
||||||
if (doc.item) {
|
if (doc.item) {
|
||||||
get_server_fields('get_item_detail',doc.item,'',doc,dt,dn,1);
|
get_server_fields('get_item_detail',doc.item,'',doc,dt,dn,1);
|
||||||
@ -36,7 +36,8 @@ cur_frm.cscript.workstation = function(doc,dt,dn) {
|
|||||||
calculate_op_cost(doc, dt, dn);
|
calculate_op_cost(doc, dt, dn);
|
||||||
calculate_total(doc);
|
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.hour_rate = function(doc, dt, dn) {
|
|||||||
|
|
||||||
cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
|
cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
|
||||||
|
|
||||||
|
|
||||||
cur_frm.cscript.item_code = function(doc, cdt, cdn) {
|
cur_frm.cscript.item_code = function(doc, cdt, cdn) {
|
||||||
get_bom_material_detail(doc, cdt, cdn);
|
get_bom_material_detail(doc, cdt, cdn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
|
cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
|
||||||
get_bom_material_detail(doc, cdt, cdn);
|
get_bom_material_detail(doc, cdt, cdn);
|
||||||
}
|
}
|
||||||
@ -89,9 +88,16 @@ cur_frm.cscript.qty = function(doc, cdt, cdn) {
|
|||||||
calculate_total(doc);
|
calculate_total(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cur_frm.cscript.rate = function(doc, cdt, cdn) {
|
||||||
cur_frm.cscript.rate = cur_frm.cscript.qty;
|
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) {
|
cur_frm.cscript.is_default = function(doc, cdt, cdn) {
|
||||||
if (doc.docstatus == 1)
|
if (doc.docstatus == 1)
|
||||||
@ -104,13 +110,11 @@ cur_frm.cscript.is_active = function(doc, dt, dn) {
|
|||||||
$c_obj(make_doclist(dt, dn), 'manage_active_bom', '', '');
|
$c_obj(make_doclist(dt, dn), 'manage_active_bom', '', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Calculate Operating Cost
|
|
||||||
var calculate_op_cost = function(doc, dt, dn) {
|
var calculate_op_cost = function(doc, dt, dn) {
|
||||||
var op = getchildren('BOM Operation', doc.name, 'bom_operations');
|
var op = getchildren('BOM Operation', doc.name, 'bom_operations');
|
||||||
total_op_cost = 0;
|
total_op_cost = 0;
|
||||||
for(var i=0;i<op.length;i++) {
|
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');
|
set_multiple('BOM Operation',op[i].name, {'operating_cost': op_cost}, 'bom_operations');
|
||||||
total_op_cost += op_cost;
|
total_op_cost += op_cost;
|
||||||
}
|
}
|
||||||
@ -118,15 +122,14 @@ var calculate_op_cost = function(doc, dt, dn) {
|
|||||||
refresh_field('operating_cost');
|
refresh_field('operating_cost');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Calculate Raw Material Cost
|
|
||||||
var calculate_rm_cost = function(doc, dt, dn) {
|
var calculate_rm_cost = function(doc, dt, dn) {
|
||||||
var rm = getchildren('BOM Item', doc.name, 'bom_materials');
|
var rm = getchildren('BOM Item', doc.name, 'bom_materials');
|
||||||
total_rm_cost = 0;
|
total_rm_cost = 0;
|
||||||
for(var i=0;i<rm.length;i++) {
|
for(var i=0;i<rm.length;i++) {
|
||||||
amt = flt(rm[i].rate) * flt(rm[i].qty);
|
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, {'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;
|
total_rm_cost += amt;
|
||||||
}
|
}
|
||||||
doc.raw_material_cost = total_rm_cost;
|
doc.raw_material_cost = total_rm_cost;
|
||||||
@ -142,7 +145,10 @@ var calculate_total = function(doc) {
|
|||||||
|
|
||||||
|
|
||||||
cur_frm.fields_dict['item'].get_query = function(doc) {
|
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) {
|
cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
|
||||||
@ -152,12 +158,18 @@ cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cur_frm.fields_dict['bom_materials'].grid.get_field('item_code').get_query = function(doc) {
|
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) {
|
cur_frm.fields_dict['bom_materials'].grid.get_field('bom_no').get_query = function(doc) {
|
||||||
var d = locals[this.doctype][this.docname];
|
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) {
|
cur_frm.cscript.validate = function(doc, dt, dn) {
|
||||||
|
@ -64,22 +64,20 @@ class DocType:
|
|||||||
def get_workstation_details(self,workstation):
|
def get_workstation_details(self,workstation):
|
||||||
""" Fetch hour rate from workstation master"""
|
""" Fetch hour rate from workstation master"""
|
||||||
|
|
||||||
ws = sql("select hour_rate from `tabWorkstation` where name = %s",workstation , as_dict = 1)
|
ws = sql("select hour_rate from `tabWorkstation` where name = %s",
|
||||||
ret = {
|
workstation , as_dict = 1)
|
||||||
'hour_rate' : ws and flt(ws[0]['hour_rate']) or '',
|
return {'hour_rate' : ws and flt(ws[0]['hour_rate']) or ''}
|
||||||
}
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def validate_rm_item(self, item):
|
def validate_rm_item(self, item):
|
||||||
""" Validate raw material items"""
|
""" Validate raw material items"""
|
||||||
|
|
||||||
if item[0]['name'] == self.doc.item:
|
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':
|
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:
|
if not item or item[0]['docstatus'] == 2:
|
||||||
msgprint("Item %s does not exist in system" % item[0]['item_code'], raise_exception = 1)
|
msgprint("Item %s does not exist in system" % item[0]['item_code'], raise_exception = 1)
|
||||||
@ -113,9 +111,7 @@ class DocType:
|
|||||||
""" Get raw material rate as per selected method, if bom exists takes bom cost """
|
""" Get raw material rate as per selected method, if bom exists takes bom cost """
|
||||||
|
|
||||||
if arg['bom_no']:
|
if arg['bom_no']:
|
||||||
bom = sql("""select name, total_cost/quantity as unit_cost from `tabBOM`
|
rate = self.get_bom_unitcost(arg['bom_no'])
|
||||||
where is_active = 'Yes' and name = %s""", arg['bom_no'], as_dict=1)
|
|
||||||
rate = bom and bom[0]['unit_cost'] or 0
|
|
||||||
elif arg and (arg['is_purchase_item'] == 'Yes' or arg['is_sub_contracted_item'] == 'Yes'):
|
elif arg and (arg['is_purchase_item'] == 'Yes' or arg['is_sub_contracted_item'] == 'Yes'):
|
||||||
if self.doc.rm_cost_as_per == 'Valuation Rate':
|
if self.doc.rm_cost_as_per == 'Valuation Rate':
|
||||||
rate = self.get_valuation_rate(arg)
|
rate = self.get_valuation_rate(arg)
|
||||||
@ -126,7 +122,10 @@ class DocType:
|
|||||||
|
|
||||||
return 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):
|
def get_valuation_rate(self, arg):
|
||||||
""" Get average valuation rate of relevant warehouses
|
""" Get average valuation rate of relevant warehouses
|
||||||
@ -139,7 +138,8 @@ class DocType:
|
|||||||
warehouse = sql("select warehouse from `tabBin` where item_code = %s", arg['item_code'])
|
warehouse = sql("select warehouse from `tabBin` where item_code = %s", arg['item_code'])
|
||||||
rate = []
|
rate = []
|
||||||
for wh in warehouse:
|
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:
|
if r:
|
||||||
rate.append(r)
|
rate.append(r)
|
||||||
|
|
||||||
@ -148,15 +148,20 @@ class DocType:
|
|||||||
|
|
||||||
|
|
||||||
def manage_default_bom(self):
|
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':
|
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
|
# 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:
|
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):
|
def manage_active_bom(self):
|
||||||
@ -181,39 +186,33 @@ class DocType:
|
|||||||
|
|
||||||
def calculate_cost(self):
|
def calculate_cost(self):
|
||||||
"""Calculate bom totals"""
|
"""Calculate bom totals"""
|
||||||
self.doc.costing_date = nowdate()
|
|
||||||
self.calculate_op_cost()
|
self.calculate_op_cost()
|
||||||
self.calculate_rm_cost()
|
self.calculate_rm_cost()
|
||||||
self.doc.total_cost = self.doc.raw_material_cost + self.doc.operating_cost
|
self.doc.total_cost = self.doc.raw_material_cost + self.doc.operating_cost
|
||||||
self.doc.modified = now()
|
self.doc.modified = now()
|
||||||
self.doc.save()
|
self.doc.save()
|
||||||
|
|
||||||
self.update_flat_bom_engine(is_submit = self.doc.docstatus)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_op_cost(self):
|
def calculate_op_cost(self):
|
||||||
"""Update workstation rate and calculates totals"""
|
"""Update workstation rate and calculates totals"""
|
||||||
total_op_cost = 0
|
total_op_cost = 0
|
||||||
for d in getlist(self.doclist, 'bom_operations'):
|
for d in getlist(self.doclist, 'bom_operations'):
|
||||||
hour_rate = sql("select hour_rate from `tabWorkstation` where name = %s", cstr(d.workstation))
|
if d.hour_rate and d.time_in_mins:
|
||||||
d.hour_rate = hour_rate and flt(hour_rate[0][0]) or d.hour_rate or 0
|
d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.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
|
|
||||||
d.save()
|
d.save()
|
||||||
total_op_cost += d.operating_cost
|
total_op_cost += flt(d.operating_cost)
|
||||||
self.doc.operating_cost = total_op_cost
|
self.doc.operating_cost = total_op_cost
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_rm_cost(self):
|
def calculate_rm_cost(self):
|
||||||
"""Fetch RM rate as per today's valuation rate and calculate totals"""
|
"""Fetch RM rate as per today's valuation rate and calculate totals"""
|
||||||
total_rm_cost = 0
|
total_rm_cost = 0
|
||||||
for d in getlist(self.doclist, 'bom_materials'):
|
for d in getlist(self.doclist, 'bom_materials'):
|
||||||
arg = {'item_code': d.item_code, 'qty': d.qty, 'bom_no': d.bom_no}
|
if d.bom_no:
|
||||||
ret = self.get_bom_material_detail(cstr(arg))
|
d.rate = self.get_bom_unitcost(d.bom_no)
|
||||||
for k in ret:
|
|
||||||
d.fields[k] = ret[k]
|
|
||||||
d.amount = flt(d.rate) * flt(d.qty)
|
d.amount = flt(d.rate) * flt(d.qty)
|
||||||
|
d.qty_consumed_per_unit = flt(d.qty) / flt(self.doc.quantity)
|
||||||
d.save()
|
d.save()
|
||||||
total_rm_cost += d.amount
|
total_rm_cost += d.amount
|
||||||
self.doc.raw_material_cost = total_rm_cost
|
self.doc.raw_material_cost = total_rm_cost
|
||||||
@ -224,20 +223,22 @@ class DocType:
|
|||||||
""" Validate main FG item"""
|
""" Validate main FG item"""
|
||||||
item = self.get_item_det(self.doc.item)
|
item = self.get_item_det(self.doc.item)
|
||||||
if not 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':
|
elif item[0]['is_manufactured_item'] != 'Yes' \
|
||||||
msgprint("""As Item: %s is not a manufactured / sub-contracted item,
|
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)
|
you can not make BOM for it""" % self.doc.item, raise_exception = 1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def validate_operations(self):
|
def validate_operations(self):
|
||||||
""" Check duplicate operation no"""
|
""" Check duplicate operation no"""
|
||||||
self.op = []
|
self.op = []
|
||||||
for d in getlist(self.doclist, 'bom_operations'):
|
for d in getlist(self.doclist, 'bom_operations'):
|
||||||
if cstr(d.operation_no) in self.op:
|
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:
|
else:
|
||||||
# add operation in op list
|
# add operation in op list
|
||||||
self.op.append(cstr(d.operation_no))
|
self.op.append(cstr(d.operation_no))
|
||||||
@ -248,40 +249,47 @@ class DocType:
|
|||||||
for m in getlist(self.doclist, 'bom_materials'):
|
for m in getlist(self.doclist, 'bom_materials'):
|
||||||
# check if operation no not in op table
|
# check if operation no not in op table
|
||||||
if cstr(m.operation_no) not in self.op:
|
if cstr(m.operation_no) not in self.op:
|
||||||
msgprint("""Operation no: %s against item: %s at row no: %s is not present
|
msgprint("""Operation no: %s against item: %s at row no: %s \
|
||||||
at Operations table"""% (m.operation_no, m.item_code, m.idx), raise_exception = 1)
|
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)
|
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:
|
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:
|
else:
|
||||||
self.validate_bom_no(m.item_code, m.bom_no, m.idx)
|
self.validate_bom_no(m.item_code, m.bom_no, m.idx)
|
||||||
|
|
||||||
elif m.bom_no:
|
elif m.bom_no:
|
||||||
msgprint("""As Item %s is not a manufactured / sub-contracted item,
|
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)
|
you can enter BOM against it (Row No: %s).""" %
|
||||||
|
(m.item_code, m.idx), raise_exception = 1)
|
||||||
|
|
||||||
if flt(m.qty) <= 0:
|
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)
|
self.check_if_item_repeated(m.item_code, m.operation_no, check_list)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def validate_bom_no(self, item, bom_no, idx):
|
def validate_bom_no(self, item, bom_no, idx):
|
||||||
"""Validate BOM No of sub-contracted items"""
|
"""Validate BOM No of sub-contracted items"""
|
||||||
bom = sql("""select name from `tabBOM` where name = %s and item = %s
|
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:
|
if not bom:
|
||||||
msgprint("""Incorrect BOM No: %s against item: %s at row no: %s.
|
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):
|
def check_if_item_repeated(self, item, op, check_list):
|
||||||
if [cstr(item), cstr(op)] in 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:
|
else:
|
||||||
check_list.append([cstr(item), cstr(op)])
|
check_list.append([cstr(item), cstr(op)])
|
||||||
|
|
||||||
@ -292,13 +300,14 @@ class DocType:
|
|||||||
self.validate_materials()
|
self.validate_materials()
|
||||||
|
|
||||||
def check_recursion(self):
|
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']]
|
check_list = [['parent', 'bom_no', 'parent'], ['bom_no', 'parent', 'child']]
|
||||||
for d in check_list:
|
for d in check_list:
|
||||||
bom_list, count = [self.doc.name], 0
|
bom_list, count = [self.doc.name], 0
|
||||||
while (len(bom_list) > count ):
|
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
|
count = count + 1
|
||||||
for b in boms:
|
for b in boms:
|
||||||
if b[0] == self.doc.name:
|
if b[0] == self.doc.name:
|
||||||
@ -308,30 +317,32 @@ class DocType:
|
|||||||
bom_list.append(b[0])
|
bom_list.append(b[0])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_update(self):
|
def on_update(self):
|
||||||
self.check_recursion()
|
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"
|
"Add items to Flat BOM table"
|
||||||
self.doclist = self.doc.clear_table(self.doclist, 'flat_bom_details', 1)
|
self.doclist = self.doc.clear_table(self.doclist, 'flat_bom_details', 1)
|
||||||
for d in self.cur_flat_bom_items:
|
for d in self.cur_flat_bom_items:
|
||||||
ch = addchild(self.doc, 'flat_bom_details', 'BOM Explosion Item', 1, self.doclist)
|
ch = addchild(self.doc, 'flat_bom_details', 'BOM Explosion Item', 1, self.doclist)
|
||||||
for i in d.keys():
|
for i in d.keys():
|
||||||
ch.fields[i] = d[i]
|
ch.fields[i] = d[i]
|
||||||
ch.docstatus = is_submit
|
ch.docstatus = self.doc.docstatus
|
||||||
ch.save(1)
|
ch.save(1)
|
||||||
self.doc.save()
|
self.doc.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_child_flat_bom_items(self, bom_no, qty):
|
def get_child_flat_bom_items(self, bom_no, qty):
|
||||||
""" Add all items from Flat BOM of child BOM"""
|
""" 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
|
child_fb_items = sql("""select item_code, description, stock_uom, qty, rate,
|
||||||
from `tabBOM Explosion Item` where parent = '%s' and docstatus = 1""" % bom_no, as_dict = 1)
|
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:
|
for d in child_fb_items:
|
||||||
self.cur_flat_bom_items.append({
|
self.cur_flat_bom_items.append({
|
||||||
'item_code' : d['item_code'],
|
'item_code' : d['item_code'],
|
||||||
@ -347,12 +358,13 @@ class DocType:
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
# Get Current Flat BOM Items
|
def get_flat_bom_items(self):
|
||||||
# -----------------------------
|
|
||||||
def get_current_flat_bom_items(self):
|
|
||||||
""" Get all raw materials including items from child bom"""
|
""" Get all raw materials including items from child bom"""
|
||||||
self.cur_flat_bom_items = []
|
self.cur_flat_bom_items = []
|
||||||
for d in getlist(self.doclist, 'bom_materials'):
|
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({
|
self.cur_flat_bom_items.append({
|
||||||
'item_code' : d.item_code,
|
'item_code' : d.item_code,
|
||||||
'description' : d.description,
|
'description' : d.description,
|
||||||
@ -360,18 +372,16 @@ class DocType:
|
|||||||
'qty' : flt(d.qty),
|
'qty' : flt(d.qty),
|
||||||
'rate' : flt(d.rate),
|
'rate' : flt(d.rate),
|
||||||
'amount' : flt(d.amount),
|
'amount' : flt(d.amount),
|
||||||
'parent_bom' : d.parent, #item and item[0][0]=='No' and d.bom_no or d.parent,
|
'parent_bom' : d.parent,
|
||||||
'mat_detail_no' : d.name,
|
'mat_detail_no' : d.name,
|
||||||
'qty_consumed_per_unit' : flt(d.qty_consumed_per_unit)
|
'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)
|
|
||||||
|
|
||||||
|
|
||||||
def update_flat_bom_engine(self, is_submit = 0):
|
def update_flat_bom(self):
|
||||||
""" Update Flat BOM, following will be correct data"""
|
""" Update Flat BOM, following will be correct data"""
|
||||||
self.get_current_flat_bom_items()
|
self.get_flat_bom_items()
|
||||||
self.add_to_flat_bom_detail(is_submit)
|
self.add_to_flat_bom_detail()
|
||||||
|
|
||||||
|
|
||||||
def get_parent_bom_list(self, bom_no):
|
def get_parent_bom_list(self, bom_no):
|
||||||
@ -381,17 +391,43 @@ class DocType:
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.manage_default_bom()
|
self.manage_default_bom()
|
||||||
self.update_flat_bom_engine(1)
|
|
||||||
|
|
||||||
|
|
||||||
def on_cancel(self):
|
def on_cancel(self):
|
||||||
# check if used in any other bom
|
# check if used in any other bom
|
||||||
par = sql("""select t1.parent from `tabBOM Item` t1, `tabBOM` t2
|
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:
|
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])
|
msgprint("""BOM can not be cancelled, as it is a child item \
|
||||||
raise Exception
|
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_active", "No")
|
||||||
webnotes.conn.set(self.doc, "is_default", 0)
|
webnotes.conn.set(self.doc, "is_default", 0)
|
||||||
self.manage_default_bom()
|
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()
|
@ -4,7 +4,7 @@
|
|||||||
"docstatus": 0,
|
"docstatus": 0,
|
||||||
"creation": "2012-07-03 13:30:03",
|
"creation": "2012-07-03 13:30:03",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"modified": "2012-12-03 13:29:26"
|
"modified": "2012-12-10 12:03:14"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"istable": 0,
|
"istable": 0,
|
||||||
@ -56,12 +56,10 @@
|
|||||||
{
|
{
|
||||||
"description": "Select the item code for which Bill of Material is being created",
|
"description": "Select the item code for which Bill of Material is being created",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Item",
|
"label": "Item",
|
||||||
"oldfieldname": "item",
|
"oldfieldname": "item",
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"trigger": "Client",
|
|
||||||
"fieldname": "item",
|
"fieldname": "item",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"search_index": 1,
|
"search_index": 1,
|
||||||
@ -72,7 +70,6 @@
|
|||||||
{
|
{
|
||||||
"description": "Total quantity of items for which raw materials required and operations done will be defined",
|
"description": "Total quantity of items for which raw materials required and operations done will be defined",
|
||||||
"oldfieldtype": "Currency",
|
"oldfieldtype": "Currency",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Quantity",
|
"label": "Quantity",
|
||||||
"oldfieldname": "quantity",
|
"oldfieldname": "quantity",
|
||||||
@ -91,7 +88,6 @@
|
|||||||
{
|
{
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Select",
|
"oldfieldtype": "Select",
|
||||||
"colour": "White:FFF",
|
|
||||||
"allow_on_submit": 1,
|
"allow_on_submit": 1,
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Is Active",
|
"label": "Is Active",
|
||||||
@ -104,10 +100,9 @@
|
|||||||
"options": "\nYes\nNo"
|
"options": "\nYes\nNo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"allow_on_submit": 1,
|
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Check",
|
"oldfieldtype": "Check",
|
||||||
"colour": "White:FFF",
|
"allow_on_submit": 1,
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Is Default",
|
"label": "Is Default",
|
||||||
"oldfieldname": "is_default",
|
"oldfieldname": "is_default",
|
||||||
@ -126,7 +121,6 @@
|
|||||||
{
|
{
|
||||||
"description": "Specify the operations, operating cost and give a unique Operation no to your operations.",
|
"description": "Specify the operations, operating cost and give a unique Operation no to your operations.",
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "BOM Operations",
|
"label": "BOM Operations",
|
||||||
"oldfieldname": "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.",
|
"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",
|
"oldfieldtype": "Table",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "BOM Item",
|
"label": "BOM Item",
|
||||||
"oldfieldname": "bom_materials",
|
"oldfieldname": "bom_materials",
|
||||||
@ -185,6 +178,12 @@
|
|||||||
"fieldtype": "Float",
|
"fieldtype": "Float",
|
||||||
"permlevel": 1
|
"permlevel": 1
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"doctype": "DocField",
|
||||||
|
"fieldname": "col_break24",
|
||||||
|
"fieldtype": "Column Break",
|
||||||
|
"permlevel": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Total Cost",
|
"label": "Total Cost",
|
||||||
@ -202,13 +201,12 @@
|
|||||||
{
|
{
|
||||||
"description": "Select name of the project if BOM need to be created against any project",
|
"description": "Select name of the project if BOM need to be created against any project",
|
||||||
"oldfieldtype": "Link",
|
"oldfieldtype": "Link",
|
||||||
|
"doctype": "DocField",
|
||||||
"label": "Project Name",
|
"label": "Project Name",
|
||||||
"oldfieldname": "project_name",
|
"oldfieldname": "project_name",
|
||||||
"trigger": "Client",
|
"options": "Project",
|
||||||
"fieldname": "project_name",
|
"fieldname": "project_name",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"doctype": "DocField",
|
|
||||||
"options": "Project",
|
|
||||||
"permlevel": 0,
|
"permlevel": 0,
|
||||||
"in_filter": 1
|
"in_filter": 1
|
||||||
},
|
},
|
||||||
@ -225,7 +223,6 @@
|
|||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Item Description",
|
"label": "Item Description",
|
||||||
"oldfieldname": "description",
|
"oldfieldname": "description",
|
||||||
"width": "300px",
|
|
||||||
"fieldname": "description",
|
"fieldname": "description",
|
||||||
"fieldtype": "Small Text",
|
"fieldtype": "Small Text",
|
||||||
"permlevel": 0
|
"permlevel": 0
|
||||||
@ -269,7 +266,6 @@
|
|||||||
{
|
{
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Text",
|
"oldfieldtype": "Text",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "Remarks",
|
"label": "Remarks",
|
||||||
"oldfieldname": "remarks",
|
"oldfieldname": "remarks",
|
||||||
@ -292,7 +288,6 @@
|
|||||||
"permlevel": 1,
|
"permlevel": 1,
|
||||||
"no_copy": 1,
|
"no_copy": 1,
|
||||||
"oldfieldtype": "Table",
|
"oldfieldtype": "Table",
|
||||||
"colour": "White:FFF",
|
|
||||||
"doctype": "DocField",
|
"doctype": "DocField",
|
||||||
"label": "BOM Explosion Item",
|
"label": "BOM Explosion Item",
|
||||||
"oldfieldname": "flat_bom_details",
|
"oldfieldname": "flat_bom_details",
|
||||||
|
147
production/doctype/bom/test_bom.py
Normal file
147
production/doctype/bom/test_bom.py
Normal 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()
|
@ -1 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
@ -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
|
|
@ -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'
|
|
||||||
}
|
|
||||||
]
|
|
@ -1,138 +1,107 @@
|
|||||||
# DocType, BOM Explosion Item
|
|
||||||
[
|
[
|
||||||
|
|
||||||
# These values are common in all dictionaries
|
|
||||||
{
|
{
|
||||||
'creation': '2012-03-27 14:36:03',
|
"owner": "jai@webnotestech.com",
|
||||||
'docstatus': 0,
|
"docstatus": 0,
|
||||||
'modified': '2012-03-27 14:36:03',
|
"creation": "2012-07-03 13:30:04",
|
||||||
'modified_by': u'Administrator',
|
"modified_by": "Administrator",
|
||||||
'owner': u'jai@webnotestech.com'
|
"modified": "2012-12-10 12:05:27"
|
||||||
},
|
},
|
||||||
|
|
||||||
# These values are common for all DocType
|
|
||||||
{
|
{
|
||||||
'autoname': u'FBD/.######',
|
"read_only": 0,
|
||||||
'colour': u'White:FFF',
|
"istable": 1,
|
||||||
'default_print_format': u'Standard',
|
"autoname": "FBD/.######",
|
||||||
'doctype': 'DocType',
|
"name": "__common__",
|
||||||
'istable': 1,
|
"default_print_format": "Standard",
|
||||||
'module': u'Production',
|
"doctype": "DocType",
|
||||||
'name': '__common__',
|
"module": "Production"
|
||||||
'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__",
|
||||||
'name': '__common__',
|
"parent": "BOM Explosion Item",
|
||||||
'parent': u'BOM Explosion Item',
|
"doctype": "DocField",
|
||||||
'parentfield': u'fields',
|
"parenttype": "DocType",
|
||||||
'parenttype': u'DocType',
|
"permlevel": 1,
|
||||||
'permlevel': 0
|
"parentfield": "fields"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocType, BOM Explosion Item
|
|
||||||
{
|
{
|
||||||
'doctype': 'DocType',
|
"name": "BOM Explosion Item",
|
||||||
'name': u'BOM Explosion Item'
|
"doctype": "DocType"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"oldfieldtype": "Link",
|
||||||
'fieldname': u'item_code',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Link',
|
"label": "Item Code",
|
||||||
'label': u'Item Code',
|
"oldfieldname": "item_code",
|
||||||
'oldfieldname': u'item_code',
|
"fieldname": "item_code",
|
||||||
'oldfieldtype': u'Link',
|
"fieldtype": "Link",
|
||||||
'options': u'Item'
|
"options": "Item"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"oldfieldtype": "Text",
|
||||||
'fieldname': u'description',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Text',
|
"label": "Description",
|
||||||
'label': u'Description',
|
"oldfieldname": "description",
|
||||||
'oldfieldname': u'description',
|
"width": "300px",
|
||||||
'oldfieldtype': u'Text',
|
"fieldname": "description",
|
||||||
'width': u'300px'
|
"fieldtype": "Text"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"oldfieldtype": "Currency",
|
||||||
'fieldname': u'qty',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Float',
|
"label": "Qty",
|
||||||
'label': u'Qty',
|
"oldfieldname": "qty",
|
||||||
'oldfieldname': u'qty',
|
"fieldname": "qty",
|
||||||
'oldfieldtype': u'Currency'
|
"fieldtype": "Float"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"oldfieldtype": "Currency",
|
||||||
'fieldname': u'rate',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Float',
|
"label": "Rate",
|
||||||
'label': u'Rate',
|
"oldfieldname": "standard_rate",
|
||||||
'oldfieldname': u'standard_rate',
|
"fieldname": "rate",
|
||||||
'oldfieldtype': u'Currency'
|
"fieldtype": "Float"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"oldfieldtype": "Currency",
|
||||||
'fieldname': u'amount',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Float',
|
"label": "Amount",
|
||||||
'label': u'Amount',
|
"oldfieldname": "amount_as_per_sr",
|
||||||
'oldfieldname': u'amount_as_per_sr',
|
"fieldname": "amount",
|
||||||
'oldfieldtype': u'Currency'
|
"fieldtype": "Float"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"oldfieldtype": "Link",
|
||||||
'fieldname': u'stock_uom',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Link',
|
"label": "Stock UOM",
|
||||||
'label': u'Stock UOM',
|
"oldfieldname": "stock_uom",
|
||||||
'oldfieldname': u'stock_uom',
|
"fieldname": "stock_uom",
|
||||||
'oldfieldtype': u'Link',
|
"fieldtype": "Link",
|
||||||
'options': u'UOM'
|
"options": "UOM"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"oldfieldtype": "Link",
|
||||||
'fieldname': u'parent_bom',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Link',
|
"label": "Parent BOM",
|
||||||
'hidden': 0,
|
"oldfieldname": "parent_bom",
|
||||||
'label': u'Parent BOM',
|
"width": "250px",
|
||||||
'oldfieldname': u'parent_bom',
|
"fieldname": "parent_bom",
|
||||||
'oldfieldtype': u'Link',
|
"fieldtype": "Link",
|
||||||
'width': u'250px'
|
"hidden": 0,
|
||||||
|
"options": "BOM"
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"doctype": "DocField",
|
||||||
'fieldname': u'mat_detail_no',
|
"label": "Mat Detail No",
|
||||||
'fieldtype': u'Data',
|
"fieldname": "mat_detail_no",
|
||||||
'hidden': 1,
|
"fieldtype": "Data",
|
||||||
'label': u'Mat Detail No'
|
"hidden": 1
|
||||||
},
|
},
|
||||||
|
|
||||||
# DocField
|
|
||||||
{
|
{
|
||||||
'doctype': u'DocField',
|
"no_copy": 0,
|
||||||
'fieldname': u'qty_consumed_per_unit',
|
"doctype": "DocField",
|
||||||
'fieldtype': u'Float',
|
"label": "Qty Consumed Per Unit",
|
||||||
'hidden': 0,
|
"fieldname": "qty_consumed_per_unit",
|
||||||
'label': u'Qty Consumed Per Unit',
|
"fieldtype": "Float",
|
||||||
'no_copy': 0
|
"hidden": 0
|
||||||
}
|
}
|
||||||
]
|
]
|
@ -1 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
@ -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)
|
|
@ -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'
|
|
||||||
}
|
|
||||||
]
|
|
@ -92,12 +92,9 @@ cur_frm.fields_dict['project_name'].get_query = function(doc, dt, dn) {
|
|||||||
AND `tabProject`.name LIKE "%s" ORDER BY `tabProject`.name ASC LIMIT 50';
|
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");
|
||||||
|
});
|
@ -72,8 +72,33 @@ class DocType:
|
|||||||
where name=%s and docstatus = 1""", self.doc.sales_order):
|
where name=%s and docstatus = 1""", self.doc.sales_order):
|
||||||
msgprint("Sales Order: %s is not valid" % self.doc.sales_order, raise_exception=1)
|
msgprint("Sales Order: %s is not valid" % self.doc.sales_order, raise_exception=1)
|
||||||
|
|
||||||
get_obj("Production Control").validate_production_order_against_so(
|
self.validate_production_order_against_so()
|
||||||
self.doc.production_item, self.doc.sales_order, self.doc.qty, self.doc.name)
|
|
||||||
|
|
||||||
|
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):
|
def stop_unstop(self, status):
|
||||||
|
@ -51,10 +51,9 @@ cur_frm.fields_dict['pp_details'].grid.get_field('item_code').get_query = functi
|
|||||||
|
|
||||||
cur_frm.fields_dict['pp_details'].grid.get_field('bom_no').get_query = function(doc) {
|
cur_frm.fields_dict['pp_details'].grid.get_field('bom_no').get_query = function(doc) {
|
||||||
var d = locals[this.doctype][this.docname];
|
var d = locals[this.doctype][this.docname];
|
||||||
return 'SELECT DISTINCT `tabBOM`.`name` \
|
if (d.item_code) {
|
||||||
FROM `tabBOM` WHERE `tabBOM`.`item` = "' + d.item_code +
|
return erpnext.queries.bom({item: cstr(d.item_code)});
|
||||||
'" AND `tabBOM`.`is_active` = "Yes" AND `tabBOM`.docstatus = 1 \
|
} else msgprint(" Please enter Item first");
|
||||||
AND `tabBOM`.`name` like "%s" ORDER BY `tabBOM`.`name` LIMIT 50';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query;
|
cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query;
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import webnotes
|
import webnotes
|
||||||
from webnotes.utils import cstr, flt
|
from webnotes.utils import cstr, flt, nowdate, get_defaults
|
||||||
from webnotes.model.doc import addchild
|
from webnotes.model.doc import addchild, Document
|
||||||
from webnotes.model.wrapper import getlist
|
from webnotes.model.wrapper import getlist
|
||||||
from webnotes.model.code import get_obj
|
from webnotes.model.code import get_obj
|
||||||
from webnotes import msgprint
|
from webnotes import msgprint
|
||||||
@ -186,7 +186,7 @@ class DocType:
|
|||||||
self.validate_data()
|
self.validate_data()
|
||||||
|
|
||||||
items = self.get_distinct_items_and_boms()[1]
|
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:
|
if pro:
|
||||||
msgprint("Following Production Order has been generated:\n" + '\n'.join(pro))
|
msgprint("Following Production Order has been generated:\n" + '\n'.join(pro))
|
||||||
else :
|
else :
|
||||||
@ -199,16 +199,40 @@ class DocType:
|
|||||||
for d in self.doclist.get({"parentfield": "pp_details"}):
|
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)
|
bom_dict[d.bom_no] = bom_dict.get(d.bom_no, 0) + flt(d.planned_qty)
|
||||||
item_dict[(d.item_code, d.sales_order)] = {
|
item_dict[(d.item_code, d.sales_order)] = {
|
||||||
"qty" : flt(item_dict.get((d.item_code, d.sales_order), {}).get("qty")) + \
|
"qty" : flt(item_dict.get((d.item_code, d.sales_order), \
|
||||||
flt(d.planned_qty),
|
{}).get("qty")) + flt(d.planned_qty),
|
||||||
"bom_no" : d.bom_no,
|
"bom_no" : d.bom_no,
|
||||||
"description" : d.description,
|
"description" : d.description,
|
||||||
"stock_uom" : d.stock_uom,
|
"stock_uom" : d.stock_uom,
|
||||||
"use_multi_level_bom": self.doc.use_multi_level_bom,
|
"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
|
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):
|
def download_raw_materials(self):
|
||||||
""" Create csv data for required raw material to produce finished goods"""
|
""" Create csv data for required raw material to produce finished goods"""
|
||||||
bom_dict = self.get_distinct_items_and_boms()[0]
|
bom_dict = self.get_distinct_items_and_boms()[0]
|
||||||
|
@ -22,6 +22,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="layout-side-section">
|
<div class="layout-side-section">
|
||||||
<div class="psidebar">
|
<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">
|
||||||
<div class="section-head">Setup</div>
|
<div class="section-head">Setup</div>
|
||||||
<div class="section-body">
|
<div class="section-body">
|
||||||
|
@ -104,3 +104,23 @@ erpnext.queries.account = function(opts) {
|
|||||||
? (" AND " + conditions.join(" AND "))
|
? (" 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 "))
|
||||||
|
: "")
|
||||||
|
}
|
@ -390,6 +390,7 @@ class DocType(TransactionBase):
|
|||||||
if d.s_warehouse == d.t_warehouse:
|
if d.s_warehouse == d.t_warehouse:
|
||||||
msgprint("Source and Target Warehouse Cannot be Same.")
|
msgprint("Source and Target Warehouse Cannot be Same.")
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
if self.doc.purpose == 'Material Issue':
|
if self.doc.purpose == 'Material Issue':
|
||||||
if not cstr(d.s_warehouse):
|
if not cstr(d.s_warehouse):
|
||||||
msgprint("Source Warehouse is Mandatory for Purpose => 'Material Issue'")
|
msgprint("Source Warehouse is Mandatory for Purpose => 'Material Issue'")
|
||||||
@ -415,6 +416,7 @@ class DocType(TransactionBase):
|
|||||||
if not cstr(d.s_warehouse):
|
if not cstr(d.s_warehouse):
|
||||||
msgprint("Please Enter Source Warehouse at Row No %s." % (cstr(d.idx)))
|
msgprint("Please Enter Source Warehouse at Row No %s." % (cstr(d.idx)))
|
||||||
raise Exception
|
raise Exception
|
||||||
|
|
||||||
if self.doc.process == 'Backflush':
|
if self.doc.process == 'Backflush':
|
||||||
if flt(d.fg_item):
|
if flt(d.fg_item):
|
||||||
if cstr(pro_obj.doc.production_item) != cstr(d.item_code):
|
if cstr(pro_obj.doc.production_item) != cstr(d.item_code):
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
'has_serial_no': u'No',
|
'has_serial_no': u'No',
|
||||||
'inspection_required': u'No',
|
'inspection_required': u'No',
|
||||||
'is_purchase_item': u'Yes',
|
'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_sales_item': u'Yes',
|
||||||
'is_service_item': u'No',
|
'is_service_item': u'No',
|
||||||
'is_stock_item': u'Yes',
|
'is_stock_item': u'Yes',
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
'has_serial_no': u'No',
|
'has_serial_no': u'No',
|
||||||
'inspection_required': u'No',
|
'inspection_required': u'No',
|
||||||
'is_purchase_item': u'Yes',
|
'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_sales_item': u'Yes',
|
||||||
'is_service_item': u'No',
|
'is_service_item': u'No',
|
||||||
'is_stock_item': u'Yes',
|
'is_stock_item': u'Yes',
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
'has_serial_no': u'No',
|
'has_serial_no': u'No',
|
||||||
'inspection_required': u'No',
|
'inspection_required': u'No',
|
||||||
'is_purchase_item': u'Yes',
|
'is_purchase_item': u'Yes',
|
||||||
|
'is_manufactured_item': u'No',
|
||||||
'is_sales_item': u'Yes',
|
'is_sales_item': u'Yes',
|
||||||
'is_service_item': u'No',
|
'is_service_item': u'No',
|
||||||
'is_stock_item': u'Yes',
|
'is_stock_item': u'Yes',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user