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
|
||||
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) {
|
||||
|
@ -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()
|
@ -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",
|
||||
|
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',
|
||||
'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
|
||||
}
|
||||
]
|
@ -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';
|
||||
}
|
||||
|
||||
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):
|
||||
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):
|
||||
|
@ -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;
|
||||
|
@ -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"""
|
||||
|
@ -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">
|
||||
|
@ -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 "))
|
||||
: "")
|
||||
}
|
@ -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):
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user