bom cleanup: update cost and flat bom by traversing full tree

This commit is contained in:
Nabin Hait 2012-12-10 18:11:42 +05:30
parent af63b16be8
commit 5999944a5c
21 changed files with 514 additions and 587 deletions

View File

@ -17,11 +17,11 @@
// On REFRESH
cur_frm.cscript.refresh = function(doc,dt,dn){
cur_frm.toggle_enable("item", doc.__islocal);
if (!doc.__islocal && doc.docstatus==0) {
cur_frm.set_intro("Submit the BOM to use it in production");
}
}
// Triggers
//--------------------------------------------------------------------------------------------------
cur_frm.cscript.item = function(doc, dt, dn) {
if (doc.item) {
get_server_fields('get_item_detail',doc.item,'',doc,dt,dn,1);
@ -36,7 +36,8 @@ cur_frm.cscript.workstation = function(doc,dt,dn) {
calculate_op_cost(doc, dt, dn);
calculate_total(doc);
}
get_server_fields('get_workstation_details',d.workstation,'bom_operations',doc,dt,dn,1, callback);
get_server_fields('get_workstation_details', d.workstation,
'bom_operations', doc, dt, dn, 1, callback);
}
}
@ -49,12 +50,10 @@ cur_frm.cscript.hour_rate = function(doc, dt, dn) {
cur_frm.cscript.time_in_mins = cur_frm.cscript.hour_rate;
cur_frm.cscript.item_code = function(doc, cdt, cdn) {
get_bom_material_detail(doc, cdt, cdn);
}
cur_frm.cscript.bom_no = function(doc, cdt, cdn) {
get_bom_material_detail(doc, cdt, cdn);
}
@ -89,9 +88,16 @@ cur_frm.cscript.qty = function(doc, cdt, cdn) {
calculate_total(doc);
}
cur_frm.cscript.rate = cur_frm.cscript.qty;
cur_frm.cscript.rate = function(doc, cdt, cdn) {
var d = locals[cdt][cdn];
if (d.bom_no) {
msgprint("You can not change rate if BOM mentioned agianst any item");
get_bom_material_detail(doc, cdt, cdn);
} else {
calculate_rm_cost(doc, cdt, cdn);
calculate_total(doc);
}
}
cur_frm.cscript.is_default = function(doc, cdt, cdn) {
if (doc.docstatus == 1)
@ -104,13 +110,11 @@ cur_frm.cscript.is_active = function(doc, dt, dn) {
$c_obj(make_doclist(dt, dn), 'manage_active_bom', '', '');
}
// Calculate Operating Cost
var calculate_op_cost = function(doc, dt, dn) {
var op = getchildren('BOM Operation', doc.name, 'bom_operations');
total_op_cost = 0;
for(var i=0;i<op.length;i++) {
op_cost = flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60;
op_cost = flt(flt(op[i].hour_rate) * flt(op[i].time_in_mins) / 60, 2);
set_multiple('BOM Operation',op[i].name, {'operating_cost': op_cost}, 'bom_operations');
total_op_cost += op_cost;
}
@ -118,15 +122,14 @@ var calculate_op_cost = function(doc, dt, dn) {
refresh_field('operating_cost');
}
// Calculate Raw Material Cost
var calculate_rm_cost = function(doc, dt, dn) {
var rm = getchildren('BOM Item', doc.name, 'bom_materials');
total_rm_cost = 0;
for(var i=0;i<rm.length;i++) {
amt = flt(rm[i].rate) * flt(rm[i].qty);
set_multiple('BOM Item',rm[i].name, {'amount': amt}, 'bom_materials');
set_multiple('BOM Item',rm[i].name, {'qty_consumed_per_unit': flt(rm[i].qty)/flt(doc.quantity)}, 'bom_materials');
set_multiple('BOM Item',rm[i].name,
{'qty_consumed_per_unit': flt(rm[i].qty)/flt(doc.quantity)}, 'bom_materials');
total_rm_cost += amt;
}
doc.raw_material_cost = total_rm_cost;
@ -142,7 +145,10 @@ var calculate_total = 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) {
@ -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) {
return 'SELECT DISTINCT `tabItem`.`name`, `tabItem`.description FROM `tabItem` WHERE (IFNULL(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` = "0000-00-00" OR `tabItem`.`end_of_life` > NOW()) AND `tabItem`.`%(key)s` like "%s" ORDER BY `tabItem`.`name` LIMIT 50';
return 'SELECT DISTINCT `tabItem`.`name`, `tabItem`.description FROM `tabItem` \
WHERE (IFNULL(`tabItem`.`end_of_life`,"") = "" OR `tabItem`.`end_of_life` = "0000-00-00" \
OR `tabItem`.`end_of_life` > NOW()) AND `tabItem`.`%(key)s` like "%s" \
ORDER BY `tabItem`.`name` LIMIT 50';
}
cur_frm.fields_dict['bom_materials'].grid.get_field('bom_no').get_query = function(doc) {
var d = locals[this.doctype][this.docname];
return 'SELECT DISTINCT `tabBOM`.`name`, `tabBOM`.`remarks` FROM `tabBOM` WHERE `tabBOM`.`item` = "' + d.item_code + '" AND `tabBOM`.`is_active` = "Yes" AND `tabBOM`.docstatus = 1 AND `tabBOM`.`name` like "%s" ORDER BY `tabBOM`.`name` LIMIT 50';
return 'SELECT DISTINCT `tabBOM`.`name`, `tabBOM`.`remarks` FROM `tabBOM` \
WHERE `tabBOM`.`item` = "' + d.item_code + '" AND `tabBOM`.`is_active` = "Yes" AND \
`tabBOM`.docstatus = 1 AND `tabBOM`.`name` like "%s" \
ORDER BY `tabBOM`.`name` LIMIT 50';
}
cur_frm.cscript.validate = function(doc, dt, dn) {

View File

@ -64,22 +64,20 @@ class DocType:
def get_workstation_details(self,workstation):
""" Fetch hour rate from workstation master"""
ws = sql("select hour_rate from `tabWorkstation` where name = %s",workstation , as_dict = 1)
ret = {
'hour_rate' : ws and flt(ws[0]['hour_rate']) or '',
}
return ret
ws = sql("select hour_rate from `tabWorkstation` where name = %s",
workstation , as_dict = 1)
return {'hour_rate' : ws and flt(ws[0]['hour_rate']) or ''}
def validate_rm_item(self, item):
""" Validate raw material items"""
if item[0]['name'] == self.doc.item:
msgprint(" Item_code: "+item[0]['name']+" in materials tab cannot be same as FG Item in BOM := " +cstr(self.doc.name), raise_exception=1)
msgprint("Item_code: %s in materials tab cannot be same as FG Item",
item[0]['name'], raise_exception=1)
if item and item[0]['is_asset_item'] == 'Yes':
msgprint("Sorry!!! Item " + item[0]['name'] + " is an Asset of the company. Entered in BOM => " + cstr(self.doc.name), raise_exception = 1)
msgprint("Item: %s is an asset item, please check", item[0]['name'], raise_exception=1)
if not item or item[0]['docstatus'] == 2:
msgprint("Item %s does not exist in system" % item[0]['item_code'], raise_exception = 1)
@ -113,9 +111,7 @@ class DocType:
""" Get raw material rate as per selected method, if bom exists takes bom cost """
if arg['bom_no']:
bom = sql("""select name, total_cost/quantity as unit_cost from `tabBOM`
where is_active = 'Yes' and name = %s""", arg['bom_no'], as_dict=1)
rate = bom and bom[0]['unit_cost'] or 0
rate = self.get_bom_unitcost(arg['bom_no'])
elif arg and (arg['is_purchase_item'] == 'Yes' or arg['is_sub_contracted_item'] == 'Yes'):
if self.doc.rm_cost_as_per == 'Valuation Rate':
rate = self.get_valuation_rate(arg)
@ -126,7 +122,10 @@ class DocType:
return rate
def get_bom_unitcost(self, bom_no):
bom = sql("""select name, total_cost/quantity as unit_cost from `tabBOM`
where is_active = 'Yes' and name = %s""", bom_no, as_dict=1)
return bom and bom[0]['unit_cost'] or 0
def get_valuation_rate(self, arg):
""" Get average valuation rate of relevant warehouses
@ -139,7 +138,8 @@ class DocType:
warehouse = sql("select warehouse from `tabBin` where item_code = %s", arg['item_code'])
rate = []
for wh in warehouse:
r = get_obj('Valuation Control').get_incoming_rate(dt, time, arg['item_code'], wh[0], qty = arg.get('qty', 0))
r = get_obj('Valuation Control').get_incoming_rate(dt, time,
arg['item_code'], wh[0], qty=arg.get('qty', 0))
if r:
rate.append(r)
@ -148,15 +148,20 @@ class DocType:
def manage_default_bom(self):
""" Uncheck others if current one is selected as default, update default bom in item master"""
""" Uncheck others if current one is selected as default,
update default bom in item master
"""
if self.doc.is_default and self.doc.is_active == 'Yes':
sql("update `tabBOM` set is_default = 0 where name != %s and item=%s", (self.doc.name, self.doc.item))
sql("update `tabBOM` set is_default = 0 where name != %s and item=%s",
(self.doc.name, self.doc.item))
# update default bom in Item Master
sql("update `tabItem` set default_bom = %s where name = %s", (self.doc.name, self.doc.item))
sql("update `tabItem` set default_bom = %s where name = %s",
(self.doc.name, self.doc.item))
else:
sql("update `tabItem` set default_bom = '' where name = %s and default_bom = %s", (self.doc.item, self.doc.name))
sql("update `tabItem` set default_bom = '' where name = %s and default_bom = %s",
(self.doc.item, self.doc.name))
def manage_active_bom(self):
@ -181,39 +186,33 @@ class DocType:
def calculate_cost(self):
"""Calculate bom totals"""
self.doc.costing_date = nowdate()
self.calculate_op_cost()
self.calculate_rm_cost()
self.doc.total_cost = self.doc.raw_material_cost + self.doc.operating_cost
self.doc.modified = now()
self.doc.save()
self.update_flat_bom_engine(is_submit = self.doc.docstatus)
def calculate_op_cost(self):
"""Update workstation rate and calculates totals"""
total_op_cost = 0
for d in getlist(self.doclist, 'bom_operations'):
hour_rate = sql("select hour_rate from `tabWorkstation` where name = %s", cstr(d.workstation))
d.hour_rate = hour_rate and flt(hour_rate[0][0]) or d.hour_rate or 0
d.operating_cost = d.hour_rate and d.time_in_mins and \
flt(d.hour_rate) * flt(d.time_in_mins) / 60 or d.operating_cost
if d.hour_rate and d.time_in_mins:
d.operating_cost = flt(d.hour_rate) * flt(d.time_in_mins) / 60.0
d.save()
total_op_cost += d.operating_cost
total_op_cost += flt(d.operating_cost)
self.doc.operating_cost = total_op_cost
def calculate_rm_cost(self):
"""Fetch RM rate as per today's valuation rate and calculate totals"""
total_rm_cost = 0
for d in getlist(self.doclist, 'bom_materials'):
arg = {'item_code': d.item_code, 'qty': d.qty, 'bom_no': d.bom_no}
ret = self.get_bom_material_detail(cstr(arg))
for k in ret:
d.fields[k] = ret[k]
if d.bom_no:
d.rate = self.get_bom_unitcost(d.bom_no)
d.amount = flt(d.rate) * flt(d.qty)
d.qty_consumed_per_unit = flt(d.qty) / flt(self.doc.quantity)
d.save()
total_rm_cost += d.amount
self.doc.raw_material_cost = total_rm_cost
@ -224,20 +223,22 @@ class DocType:
""" Validate main FG item"""
item = self.get_item_det(self.doc.item)
if not item:
msgprint("Item %s does not exists in the system or expired." % self.doc.item, raise_exception = 1)
msgprint("Item %s does not exists in the system or expired." %
self.doc.item, raise_exception = 1)
elif item[0]['is_manufactured_item'] != 'Yes' and item[0]['is_sub_contracted_item'] != 'Yes':
msgprint("""As Item: %s is not a manufactured / sub-contracted item,
elif item[0]['is_manufactured_item'] != 'Yes' \
and item[0]['is_sub_contracted_item'] != 'Yes':
msgprint("""As Item: %s is not a manufactured / sub-contracted item, \
you can not make BOM for it""" % self.doc.item, raise_exception = 1)
def validate_operations(self):
""" Check duplicate operation no"""
self.op = []
for d in getlist(self.doclist, 'bom_operations'):
if cstr(d.operation_no) in self.op:
msgprint("Operation no: %s is repeated in Operations Table"% d.operation_no, raise_exception=1)
msgprint("Operation no: %s is repeated in Operations Table" %
d.operation_no, raise_exception=1)
else:
# add operation in op list
self.op.append(cstr(d.operation_no))
@ -248,40 +249,47 @@ class DocType:
for m in getlist(self.doclist, 'bom_materials'):
# check if operation no not in op table
if cstr(m.operation_no) not in self.op:
msgprint("""Operation no: %s against item: %s at row no: %s is not present
at Operations table"""% (m.operation_no, m.item_code, m.idx), raise_exception = 1)
msgprint("""Operation no: %s against item: %s at row no: %s \
is not present at Operations table""" %
(m.operation_no, m.item_code, m.idx), raise_exception = 1)
item = self.get_item_det(m.item_code)
if item[0]['is_manufactured_item'] == 'Yes' or item[0]['is_sub_contracted_item'] == 'Yes':
if item[0]['is_manufactured_item'] == 'Yes' or \
item[0]['is_sub_contracted_item'] == 'Yes':
if not m.bom_no:
msgprint("Please enter BOM No aginst item: %s at row no: %s"% (m.item_code, m.idx), raise_exception=1)
msgprint("Please enter BOM No aginst item: %s at row no: %s" %
(m.item_code, m.idx), raise_exception=1)
else:
self.validate_bom_no(m.item_code, m.bom_no, m.idx)
elif m.bom_no:
msgprint("""As Item %s is not a manufactured / sub-contracted item,
you can enter BOM against it (Row No: %s)."""% (m.item_code, m.idx), raise_excepiton = 1)
msgprint("""As Item %s is not a manufactured / sub-contracted item, \
you can enter BOM against it (Row No: %s).""" %
(m.item_code, m.idx), raise_exception = 1)
if flt(m.qty) <= 0:
msgprint("Please enter qty against raw material: %s at row no: %s"% (m.item_code, m.idx), raise_exception = 1)
msgprint("Please enter qty against raw material: %s at row no: %s" %
(m.item_code, m.idx), raise_exception = 1)
self.check_if_item_repeated(m.item_code, m.operation_no, check_list)
def validate_bom_no(self, item, bom_no, idx):
"""Validate BOM No of sub-contracted items"""
bom = sql("""select name from `tabBOM` where name = %s and item = %s
and ifnull(is_active, 'No') = 'Yes' and docstatus < 2 """, (bom_no, item), as_dict =1)
and ifnull(is_active, 'No') = 'Yes' and docstatus < 2 """,
(bom_no, item), as_dict =1)
if not bom:
msgprint("""Incorrect BOM No: %s against item: %s at row no: %s.
It may be inactive or cancelled or for some other item."""% (bom_no, item, idx), raise_exception = 1)
It may be inactive or cancelled or for some other item.""" %
(bom_no, item, idx), raise_exception = 1)
def check_if_item_repeated(self, item, op, check_list):
if [cstr(item), cstr(op)] in check_list:
msgprint("Item %s has been entered twice against same operation" % item, raise_exception = 1)
msgprint("Item %s has been entered twice against same operation" %
item, raise_exception = 1)
else:
check_list.append([cstr(item), cstr(op)])
@ -292,13 +300,14 @@ class DocType:
self.validate_materials()
def check_recursion(self):
""" Check whether reqursion occurs in any bom"""
""" Check whether recursion occurs in any bom"""
check_list = [['parent', 'bom_no', 'parent'], ['bom_no', 'parent', 'child']]
for d in check_list:
bom_list, count = [self.doc.name], 0
while (len(bom_list) > count ):
boms = sql(" select %s from `tabBOM Item` where %s = '%s' " % (d[0], d[1], cstr(bom_list[count])))
boms = sql(" select %s from `tabBOM Item` where %s = '%s' " %
(d[0], d[1], cstr(bom_list[count])))
count = count + 1
for b in boms:
if b[0] == self.doc.name:
@ -308,30 +317,32 @@ class DocType:
bom_list.append(b[0])
def on_update(self):
self.check_recursion()
self.update_cost_by_traversing()
self.update_flat_bom_by_traversing()
def add_to_flat_bom_detail(self, is_submit = 0):
def add_to_flat_bom_detail(self):
"Add items to Flat BOM table"
self.doclist = self.doc.clear_table(self.doclist, 'flat_bom_details', 1)
for d in self.cur_flat_bom_items:
ch = addchild(self.doc, 'flat_bom_details', 'BOM Explosion Item', 1, self.doclist)
for i in d.keys():
ch.fields[i] = d[i]
ch.docstatus = is_submit
ch.docstatus = self.doc.docstatus
ch.save(1)
self.doc.save()
def get_child_flat_bom_items(self, bom_no, qty):
""" Add all items from Flat BOM of child BOM"""
child_fb_items = sql("""select item_code, description, stock_uom, qty, rate, amount, parent_bom, mat_detail_no, qty_consumed_per_unit
from `tabBOM Explosion Item` where parent = '%s' and docstatus = 1""" % bom_no, as_dict = 1)
child_fb_items = sql("""select item_code, description, stock_uom, qty, rate,
amount, parent_bom, mat_detail_no, qty_consumed_per_unit
from `tabBOM Explosion Item` where parent = '%s' and docstatus = 1""" %
bom_no, as_dict = 1)
for d in child_fb_items:
self.cur_flat_bom_items.append({
'item_code' : d['item_code'],
@ -347,31 +358,30 @@ class DocType:
})
# Get Current Flat BOM Items
# -----------------------------
def get_current_flat_bom_items(self):
def get_flat_bom_items(self):
""" Get all raw materials including items from child bom"""
self.cur_flat_bom_items = []
for d in getlist(self.doclist, 'bom_materials'):
self.cur_flat_bom_items.append({
'item_code' : d.item_code,
'description' : d.description,
'stock_uom' : d.stock_uom,
'qty' : flt(d.qty),
'rate' : flt(d.rate),
'amount' : flt(d.amount),
'parent_bom' : d.parent, #item and item[0][0]=='No' and d.bom_no or d.parent,
'mat_detail_no' : d.name,
'qty_consumed_per_unit' : flt(d.qty_consumed_per_unit)
})
if d.bom_no:
self.get_child_flat_bom_items(d.bom_no, d.qty)
else:
self.cur_flat_bom_items.append({
'item_code' : d.item_code,
'description' : d.description,
'stock_uom' : d.stock_uom,
'qty' : flt(d.qty),
'rate' : flt(d.rate),
'amount' : flt(d.amount),
'parent_bom' : d.parent,
'mat_detail_no' : d.name,
'qty_consumed_per_unit' : flt(d.qty_consumed_per_unit)
})
def update_flat_bom_engine(self, is_submit = 0):
def update_flat_bom(self):
""" Update Flat BOM, following will be correct data"""
self.get_current_flat_bom_items()
self.add_to_flat_bom_detail(is_submit)
self.get_flat_bom_items()
self.add_to_flat_bom_detail()
def get_parent_bom_list(self, bom_no):
@ -381,17 +391,43 @@ class DocType:
def on_submit(self):
self.manage_default_bom()
self.update_flat_bom_engine(1)
def on_cancel(self):
# check if used in any other bom
par = sql("""select t1.parent from `tabBOM Item` t1, `tabBOM` t2
where t1.parent = t2.name and t1.bom_no = %s and t1.docstatus = 1 and t2.is_active = 'Yes'""", self.doc.name)
where t1.parent = t2.name and t1.bom_no = %s and t1.docstatus = 1
and t2.is_active = 'Yes'""", self.doc.name)
if par:
msgprint("BOM can not be cancelled, as it is a child item in following active BOM %s"% [d[0] for d in par])
raise Exception
msgprint("""BOM can not be cancelled, as it is a child item \
in following active BOM %s""" % [d[0] for d in par], raise_exception=1)
webnotes.conn.set(self.doc, "is_active", "No")
webnotes.conn.set(self.doc, "is_default", 0)
self.manage_default_bom()
self.update_flat_bom_by_traversing()
def traverse_tree(self):
def _get_childs(bom_no):
return [cstr(d[0]) for d in webnotes.conn.sql("""select bom_no from `tabBOM Item`
where parent = %s and ifnull(bom_no, '') != ''""", bom_no)]
bom_list, count = [self.doc.name], 0
while(count < len(bom_list)):
for child_bom in _get_childs(bom_list[count]):
if child_bom not in bom_list:
bom_list.append(child_bom)
count += 1
return bom_list
def update_cost_by_traversing(self):
bom_list = self.traverse_tree()
bom_list.reverse()
for bom in bom_list:
get_obj("BOM", bom, with_children=1).calculate_cost()
def update_flat_bom_by_traversing(self):
bom_list = self.traverse_tree()
bom_list.reverse()
for bom in bom_list:
get_obj("BOM", bom, with_children=1).update_flat_bom()

View File

@ -4,7 +4,7 @@
"docstatus": 0,
"creation": "2012-07-03 13:30:03",
"modified_by": "Administrator",
"modified": "2012-12-03 13:29:26"
"modified": "2012-12-10 12:03:14"
},
{
"istable": 0,
@ -56,12 +56,10 @@
{
"description": "Select the item code for which Bill of Material is being created",
"oldfieldtype": "Link",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Item",
"oldfieldname": "item",
"permlevel": 0,
"trigger": "Client",
"fieldname": "item",
"fieldtype": "Link",
"search_index": 1,
@ -72,7 +70,6 @@
{
"description": "Total quantity of items for which raw materials required and operations done will be defined",
"oldfieldtype": "Currency",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Quantity",
"oldfieldname": "quantity",
@ -91,7 +88,6 @@
{
"no_copy": 1,
"oldfieldtype": "Select",
"colour": "White:FFF",
"allow_on_submit": 1,
"doctype": "DocField",
"label": "Is Active",
@ -104,10 +100,9 @@
"options": "\nYes\nNo"
},
{
"allow_on_submit": 1,
"no_copy": 1,
"oldfieldtype": "Check",
"colour": "White:FFF",
"allow_on_submit": 1,
"doctype": "DocField",
"label": "Is Default",
"oldfieldname": "is_default",
@ -126,7 +121,6 @@
{
"description": "Specify the operations, operating cost and give a unique Operation no to your operations.",
"oldfieldtype": "Table",
"colour": "White:FFF",
"doctype": "DocField",
"label": "BOM Operations",
"oldfieldname": "bom_operations",
@ -154,7 +148,6 @@
{
"description": "Enter the raw materials required to manufacture the BOM item. Specify the operation no as entered in the previous tab which will be performed on the raw materials entered.",
"oldfieldtype": "Table",
"colour": "White:FFF",
"doctype": "DocField",
"label": "BOM Item",
"oldfieldname": "bom_materials",
@ -185,6 +178,12 @@
"fieldtype": "Float",
"permlevel": 1
},
{
"doctype": "DocField",
"fieldname": "col_break24",
"fieldtype": "Column Break",
"permlevel": 0
},
{
"doctype": "DocField",
"label": "Total Cost",
@ -202,13 +201,12 @@
{
"description": "Select name of the project if BOM need to be created against any project",
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Project Name",
"oldfieldname": "project_name",
"trigger": "Client",
"options": "Project",
"fieldname": "project_name",
"fieldtype": "Link",
"doctype": "DocField",
"options": "Project",
"permlevel": 0,
"in_filter": 1
},
@ -225,7 +223,6 @@
"doctype": "DocField",
"label": "Item Description",
"oldfieldname": "description",
"width": "300px",
"fieldname": "description",
"fieldtype": "Small Text",
"permlevel": 0
@ -269,7 +266,6 @@
{
"no_copy": 1,
"oldfieldtype": "Text",
"colour": "White:FFF",
"doctype": "DocField",
"label": "Remarks",
"oldfieldname": "remarks",
@ -292,7 +288,6 @@
"permlevel": 1,
"no_copy": 1,
"oldfieldtype": "Table",
"colour": "White:FFF",
"doctype": "DocField",
"label": "BOM Explosion Item",
"oldfieldname": "flat_bom_details",

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

View File

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

View File

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

View File

@ -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'
}
]

View File

@ -1,138 +1,107 @@
# DocType, BOM Explosion Item
[
# These values are common in all dictionaries
{
'creation': '2012-03-27 14:36:03',
'docstatus': 0,
'modified': '2012-03-27 14:36:03',
'modified_by': u'Administrator',
'owner': u'jai@webnotestech.com'
},
# These values are common for all DocType
{
'autoname': u'FBD/.######',
'colour': u'White:FFF',
'default_print_format': u'Standard',
'doctype': 'DocType',
'istable': 1,
'module': u'Production',
'name': '__common__',
'read_only': 0,
'section_style': u'Simple',
'server_code_error': u' ',
'show_in_menu': 0,
'version': 24
},
# These values are common for all DocField
{
'doctype': u'DocField',
'name': '__common__',
'parent': u'BOM Explosion Item',
'parentfield': u'fields',
'parenttype': u'DocType',
'permlevel': 0
},
# DocType, BOM Explosion Item
{
'doctype': 'DocType',
'name': u'BOM Explosion Item'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'item_code',
'fieldtype': u'Link',
'label': u'Item Code',
'oldfieldname': u'item_code',
'oldfieldtype': u'Link',
'options': u'Item'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'description',
'fieldtype': u'Text',
'label': u'Description',
'oldfieldname': u'description',
'oldfieldtype': u'Text',
'width': u'300px'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'qty',
'fieldtype': u'Float',
'label': u'Qty',
'oldfieldname': u'qty',
'oldfieldtype': u'Currency'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'rate',
'fieldtype': u'Float',
'label': u'Rate',
'oldfieldname': u'standard_rate',
'oldfieldtype': u'Currency'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'amount',
'fieldtype': u'Float',
'label': u'Amount',
'oldfieldname': u'amount_as_per_sr',
'oldfieldtype': u'Currency'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'stock_uom',
'fieldtype': u'Link',
'label': u'Stock UOM',
'oldfieldname': u'stock_uom',
'oldfieldtype': u'Link',
'options': u'UOM'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'parent_bom',
'fieldtype': u'Link',
'hidden': 0,
'label': u'Parent BOM',
'oldfieldname': u'parent_bom',
'oldfieldtype': u'Link',
'width': u'250px'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'mat_detail_no',
'fieldtype': u'Data',
'hidden': 1,
'label': u'Mat Detail No'
},
# DocField
{
'doctype': u'DocField',
'fieldname': u'qty_consumed_per_unit',
'fieldtype': u'Float',
'hidden': 0,
'label': u'Qty Consumed Per Unit',
'no_copy': 0
}
{
"owner": "jai@webnotestech.com",
"docstatus": 0,
"creation": "2012-07-03 13:30:04",
"modified_by": "Administrator",
"modified": "2012-12-10 12:05:27"
},
{
"read_only": 0,
"istable": 1,
"autoname": "FBD/.######",
"name": "__common__",
"default_print_format": "Standard",
"doctype": "DocType",
"module": "Production"
},
{
"name": "__common__",
"parent": "BOM Explosion Item",
"doctype": "DocField",
"parenttype": "DocType",
"permlevel": 1,
"parentfield": "fields"
},
{
"name": "BOM Explosion Item",
"doctype": "DocType"
},
{
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Item Code",
"oldfieldname": "item_code",
"fieldname": "item_code",
"fieldtype": "Link",
"options": "Item"
},
{
"oldfieldtype": "Text",
"doctype": "DocField",
"label": "Description",
"oldfieldname": "description",
"width": "300px",
"fieldname": "description",
"fieldtype": "Text"
},
{
"oldfieldtype": "Currency",
"doctype": "DocField",
"label": "Qty",
"oldfieldname": "qty",
"fieldname": "qty",
"fieldtype": "Float"
},
{
"oldfieldtype": "Currency",
"doctype": "DocField",
"label": "Rate",
"oldfieldname": "standard_rate",
"fieldname": "rate",
"fieldtype": "Float"
},
{
"oldfieldtype": "Currency",
"doctype": "DocField",
"label": "Amount",
"oldfieldname": "amount_as_per_sr",
"fieldname": "amount",
"fieldtype": "Float"
},
{
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Stock UOM",
"oldfieldname": "stock_uom",
"fieldname": "stock_uom",
"fieldtype": "Link",
"options": "UOM"
},
{
"oldfieldtype": "Link",
"doctype": "DocField",
"label": "Parent BOM",
"oldfieldname": "parent_bom",
"width": "250px",
"fieldname": "parent_bom",
"fieldtype": "Link",
"hidden": 0,
"options": "BOM"
},
{
"doctype": "DocField",
"label": "Mat Detail No",
"fieldname": "mat_detail_no",
"fieldtype": "Data",
"hidden": 1
},
{
"no_copy": 0,
"doctype": "DocField",
"label": "Qty Consumed Per Unit",
"fieldname": "qty_consumed_per_unit",
"fieldtype": "Float",
"hidden": 0
}
]

View File

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

View File

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

View File

@ -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'
}
]

View File

@ -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';
}
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");
});

View File

@ -72,8 +72,33 @@ class DocType:
where name=%s and docstatus = 1""", self.doc.sales_order):
msgprint("Sales Order: %s is not valid" % self.doc.sales_order, raise_exception=1)
get_obj("Production Control").validate_production_order_against_so(
self.doc.production_item, self.doc.sales_order, self.doc.qty, self.doc.name)
self.validate_production_order_against_so()
def validate_production_order_against_so(self):
# already ordered qty
ordered_qty_against_so = webnotes.conn.sql("""select sum(qty) from `tabProduction Order`
where production_item = %s and sales_order = %s and docstatus < 2""",
(self.doc.production_item, self.doc.sales_order))[0][0]
# get qty from Sales Order Item table
so_item_qty = webnotes.conn.sql("""select sum(qty) from `tabSales Order Item`
where parent = %s and item_code = %s""",
(self.doc.sales_order, self.doc.production_item))[0][0]
# get qty from Packing Item table
dnpi_qty = webnotes.conn.sql("""select sum(qty) from `tabDelivery Note Packing Item`
where parent = %s and parenttype = 'Sales Order' and item_code = %s""",
(self.doc.sales_order, self.doc.production_item))[0][0]
# total qty in SO
so_qty = flt(so_item_qty) + flt(dnpi_qty)
if ordered_qty_against_so > so_qty:
msgprint("""Total production order qty for item: %s against sales order: %s \
will be %s, which is greater than sales order qty (%s).
Please reduce qty or remove the item.""" %
(self.doc.production_item, self.doc.sales_order,
ordered_qty_against_so, so_qty), raise_exception=1)
def stop_unstop(self, status):

View File

@ -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) {
var d = locals[this.doctype][this.docname];
return 'SELECT DISTINCT `tabBOM`.`name` \
FROM `tabBOM` WHERE `tabBOM`.`item` = "' + d.item_code +
'" AND `tabBOM`.`is_active` = "Yes" AND `tabBOM`.docstatus = 1 \
AND `tabBOM`.`name` like "%s" ORDER BY `tabBOM`.`name` LIMIT 50';
if (d.item_code) {
return erpnext.queries.bom({item: cstr(d.item_code)});
} else msgprint(" Please enter Item first");
}
cur_frm.fields_dict.customer.get_query = erpnext.utils.customer_query;

View File

@ -16,8 +16,8 @@
from __future__ import unicode_literals
import webnotes
from webnotes.utils import cstr, flt
from webnotes.model.doc import addchild
from webnotes.utils import cstr, flt, nowdate, get_defaults
from webnotes.model.doc import addchild, Document
from webnotes.model.wrapper import getlist
from webnotes.model.code import get_obj
from webnotes import msgprint
@ -186,7 +186,7 @@ class DocType:
self.validate_data()
items = self.get_distinct_items_and_boms()[1]
pro = get_obj('Production Control').create_production_order(items)
pro = self.create_production_order(items)
if pro:
msgprint("Following Production Order has been generated:\n" + '\n'.join(pro))
else :
@ -199,15 +199,39 @@ class DocType:
for d in self.doclist.get({"parentfield": "pp_details"}):
bom_dict[d.bom_no] = bom_dict.get(d.bom_no, 0) + flt(d.planned_qty)
item_dict[(d.item_code, d.sales_order)] = {
"qty" : flt(item_dict.get((d.item_code, d.sales_order), {}).get("qty")) + \
flt(d.planned_qty),
"bom_no": d.bom_no,
"description": d.description,
"stock_uom": d.stock_uom,
"qty" : flt(item_dict.get((d.item_code, d.sales_order), \
{}).get("qty")) + flt(d.planned_qty),
"bom_no" : d.bom_no,
"description" : d.description,
"stock_uom" : d.stock_uom,
"use_multi_level_bom": self.doc.use_multi_level_bom,
"company": self.doc.company,
"company" : self.doc.company,
"posting_date" : nowdate(),
"origin" : "MRP",
"wip_warehouse" : "",
"fg_warehouse" : "",
"status" : "Draft",
"fiscal_year" : get_defaults()["fiscal_year"]
}
return bom_dict, item_dict
def create_production_order(self, items):
"""Create production order. Called from Production Planning Tool"""
pro_list = []
for item_so in items:
pro_doc = Document('Production Order')
pro_doc.production_item = item_so[0]
pro_doc.sales_order = item_so[1]
for key in items[item_so]:
pro_doc.fields[key] = items[item_so][key]
pro_doc.save(new = 1)
get_obj("Production Order", pro_doc.name).validate_production_order_against_so()
pro_list.append(pro_doc.name)
return pro_list
def download_raw_materials(self):
""" Create csv data for required raw material to produce finished goods"""

View File

@ -22,6 +22,16 @@
</div>
<div class="layout-side-section">
<div class="psidebar">
<div class="section">
<div class="section-head">Tools</div>
<div class="section-body">
<div class="section-item">
<a class="section-link"
title = "BOM Replace Tool"
href="#!Form/BOM Replace Tool">BOM Replace Tool</a>
</div>
</div>
</div>
<div class="section">
<div class="section-head">Setup</div>
<div class="section-body">

View File

@ -103,4 +103,24 @@ erpnext.queries.account = function(opts) {
AND tabAccount.%(key)s LIKE "%s" ' + (conditions
? (" AND " + conditions.join(" AND "))
: "")
}
erpnext.queries.bom = function(opts) {
conditions = [];
if (opts) {
$.each(opts, function(key, val) {
if (esc_quotes(val).charAt(0) != "!")
conditions.push("tabBOM.`" + key + "`='"+esc_quotes(val)+"'");
else
conditions.push("tabBOM.`" + key + "`!='"+esc_quotes(val).substr(1)+"'");
});
}
return 'SELECT tabBOM.name, tabBOM.item \
FROM tabBOM \
WHERE tabBOM.docstatus=1 \
AND tabBOM.is_active="Yes" \
AND tabBOM.%(key)s LIKE "%s" ' + (conditions.length
? (" AND " + conditions.join(" AND "))
: "")
}

View File

@ -379,7 +379,7 @@ class DocType(TransactionBase):
d.t_warehouse = self.doc.to_warehouse
if not (d.s_warehouse or d.t_warehouse):
msgprint("Atleast one warehouse is mandatory for Stock Entry ")
msgprint("Atleast one warehouse is mandatory for Stock Entry")
raise Exception
if d.s_warehouse and not sql("select name from tabWarehouse where name = '%s'" % d.s_warehouse):
msgprint("Invalid Warehouse: %s" % self.doc.s_warehouse)
@ -390,6 +390,7 @@ class DocType(TransactionBase):
if d.s_warehouse == d.t_warehouse:
msgprint("Source and Target Warehouse Cannot be Same.")
raise Exception
if self.doc.purpose == 'Material Issue':
if not cstr(d.s_warehouse):
msgprint("Source Warehouse is Mandatory for Purpose => 'Material Issue'")
@ -415,6 +416,7 @@ class DocType(TransactionBase):
if not cstr(d.s_warehouse):
msgprint("Please Enter Source Warehouse at Row No %s." % (cstr(d.idx)))
raise Exception
if self.doc.process == 'Backflush':
if flt(d.fg_item):
if cstr(pro_obj.doc.production_item) != cstr(d.item_code):

View File

@ -18,6 +18,9 @@
'has_serial_no': u'No',
'inspection_required': u'No',
'is_purchase_item': u'Yes',
'is_manufactured_item': u'Yes',
'is_sub_contracted_item': 'Yes',
'is_pro_applicable': 'Yes',
'is_sales_item': u'Yes',
'is_service_item': u'No',
'is_stock_item': u'Yes',

View File

@ -18,6 +18,9 @@
'has_serial_no': u'No',
'inspection_required': u'No',
'is_purchase_item': u'Yes',
'is_manufactured_item': u'Yes',
'is_sub_contracted_item': 'Yes',
'is_pro_applicable': 'Yes',
'is_sales_item': u'Yes',
'is_service_item': u'No',
'is_stock_item': u'Yes',

View File

@ -18,6 +18,7 @@
'has_serial_no': u'No',
'inspection_required': u'No',
'is_purchase_item': u'Yes',
'is_manufactured_item': u'No',
'is_sales_item': u'Yes',
'is_service_item': u'No',
'is_stock_item': u'Yes',