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