Merge pull request #1609 from nabinhait/v4-hotfix

V4 hotfix
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 69f5c95..8d6ba46 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -45,7 +45,7 @@
 
 		self.validate_with_previous_doc()
 		self.validate_for_subcontracting()
-		self.update_raw_materials_supplied("po_raw_material_details")
+		self.create_raw_materials_supplied("po_raw_material_details")
 
 	def validate_with_previous_doc(self):
 		super(PurchaseOrder, self).validate_with_previous_doc(self.tname, {
diff --git a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
index 25ff1da..5a9f4b6 100644
--- a/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
+++ b/erpnext/buying/doctype/purchase_receipt_item_supplied/purchase_receipt_item_supplied.json
@@ -1,5 +1,5 @@
 {
- "creation": "2013-02-22 01:27:42.000000", 
+ "creation": "2013-02-22 01:27:42", 
  "docstatus": 0, 
  "doctype": "DocType", 
  "fields": [
@@ -36,6 +36,21 @@
    "width": "300px"
   }, 
   {
+   "fieldname": "batch_no", 
+   "fieldtype": "Link", 
+   "label": "Batch No", 
+   "no_copy": 1, 
+   "options": "Batch", 
+   "permlevel": 0
+  }, 
+  {
+   "fieldname": "serial_no", 
+   "fieldtype": "Text", 
+   "label": "Serial No", 
+   "no_copy": 1, 
+   "permlevel": 0
+  }, 
+  {
    "fieldname": "col_break1", 
    "fieldtype": "Column Break", 
    "permlevel": 0
@@ -57,6 +72,7 @@
    "oldfieldname": "consumed_qty", 
    "oldfieldtype": "Currency", 
    "permlevel": 0, 
+   "read_only": 1, 
    "reqd": 1
   }, 
   {
@@ -137,9 +153,12 @@
  "hide_toolbar": 0, 
  "idx": 1, 
  "istable": 1, 
- "modified": "2014-02-13 11:29:35.000000", 
+ "modified": "2014-05-08 18:37:42.966473", 
  "modified_by": "Administrator", 
  "module": "Buying", 
  "name": "Purchase Receipt Item Supplied", 
- "owner": "wasim@webnotestech.com"
+ "owner": "wasim@webnotestech.com", 
+ "permissions": [], 
+ "sort_field": "modified", 
+ "sort_order": "DESC"
 }
\ No newline at end of file
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 0722798..d2fc2bf 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -200,48 +200,82 @@
 			and not self.supplier_warehouse:
 				frappe.throw(_("Supplier Warehouse mandatory for sub-contracted Purchase Receipt"))
 
-	def update_raw_materials_supplied(self, raw_material_table):
-		self.set(raw_material_table, [])
+	def create_raw_materials_supplied(self, raw_material_table):
 		if self.is_subcontracted=="Yes":
+			parent_items = []
+			rm_supplied_idx = 0
 			for item in self.get(self.fname):
 				if self.doctype == "Purchase Receipt":
 					item.rm_supp_cost = 0.0
 				if item.item_code in self.sub_contracted_items:
-					self.add_bom_items(item, raw_material_table)
+					self.update_raw_materials_supplied(item, raw_material_table, rm_supplied_idx)
+
+					if [item.item_code, item.name] not in parent_items:
+						parent_items.append([item.item_code, item.name])
+
+			self.cleanup_raw_materials_supplied(parent_items, raw_material_table)
 
 		elif self.doctype == "Purchase Receipt":
 			for item in self.get(self.fname):
 				item.rm_supp_cost = 0.0
 
-	def add_bom_items(self, d, raw_material_table):
-		bom_items = self.get_items_from_default_bom(d.item_code)
+	def update_raw_materials_supplied(self, item, raw_material_table, rm_supplied_idx):
+		bom_items = self.get_items_from_default_bom(item.item_code)
 		raw_materials_cost = 0
-		for item in bom_items:
-			required_qty = flt(item.qty_consumed_per_unit) * flt(d.qty) * flt(d.conversion_factor)
-			rm_doclist = {
-				"doctype": self.doctype + " Item Supplied",
-				"reference_name": d.name,
-				"bom_detail_no": item.name,
-				"main_item_code": d.item_code,
-				"rm_item_code": item.item_code,
-				"stock_uom": item.stock_uom,
-				"required_qty": required_qty,
-				"conversion_factor": d.conversion_factor,
-				"rate": item.rate,
-				"amount": required_qty * flt(item.rate)
-			}
-			if self.doctype == "Purchase Receipt":
-				rm_doclist.update({
-					"consumed_qty": required_qty,
-					"description": item.description,
-				})
 
-			self.append(raw_material_table, rm_doclist)
+		for bom_item in bom_items:
+			# check if exists
+			exists = 0
+			for d in self.get(raw_material_table):
+				if d.main_item_code == item.item_code and d.rm_item_code == bom_item.item_code \
+					and d.reference_name == item.name:
+						rm, exists = d, 1
+						break
+
+			if not exists:
+				rm = self.append(raw_material_table, {})
+
+			required_qty = flt(bom_item.qty_consumed_per_unit) * flt(item.qty) * flt(item.conversion_factor)
+			rm.reference_name = item.name
+			rm.bom_detail_no = bom_item.name
+			rm.main_item_code = item.item_code
+			rm.rm_item_code = bom_item.item_code
+			rm.stock_uom = bom_item.stock_uom
+			rm.required_qty = required_qty
+
+			rm.conversion_factor = item.conversion_factor
+			rm.rate = bom_item.rate
+			rm.amount = required_qty * flt(bom_item.rate)
+			rm.idx = rm_supplied_idx
+
+			if self.doc.doctype == "Purchase Receipt":
+				rm.consumed_qty = required_qty
+				rm.description = bom_item.description
+				if item.batch_no and not rm.batch_no:
+					rm.batch_no = item.batch_no
+
+			rm_supplied_idx += 1
 
 			raw_materials_cost += required_qty * flt(item.rate)
 
 		if self.doctype == "Purchase Receipt":
-			d.rm_supp_cost = raw_materials_cost
+			item.rm_supp_cost = raw_materials_cost
+
+	def cleanup_raw_materials_supplied(self, parent_items, raw_material_table):
+		"""Remove all those child items which are no longer present in main item table"""
+		delete_list = []
+		for d in self.get(raw_material_table):
+			if [d.main_item_code, d.reference_name] not in parent_items:
+				# mark for deletion from doclist
+				delete_list.append([d.main_item_code, d.reference_name])
+
+		# delete from doclist
+		if delete_list:
+			rm_supplied_details = self.get(raw_material_table)
+			self.set(raw_material_table, [])
+			for d in rm_supplied_details:
+				if d not in delete_list:
+					self.append(raw_material_table, d)
 
 	def get_items_from_default_bom(self, item_code):
 		bom_items = frappe.db.sql("""select t2.item_code, t2.qty_consumed_per_unit,
diff --git a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
index 1e75319..a59e0e9 100644
--- a/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
+++ b/erpnext/manufacturing/doctype/production_planning_tool/production_planning_tool.py
@@ -106,14 +106,21 @@
 			msgprint(_("Please enter sales order in the above table"))
 			return []
 
+		item_condition = ""
+		if self.fg_item:
+			item_condition = ' and so_item.item_code = "' + self.fg_item + '"'
+
 		items = frappe.db.sql("""select distinct parent, item_code, warehouse,
 			(qty - ifnull(delivered_qty, 0)) as pending_qty
 			from `tabSales Order Item` so_item
 			where parent in (%s) and docstatus = 1 and ifnull(qty, 0) > ifnull(delivered_qty, 0)
 			and exists (select * from `tabItem` item where item.name=so_item.item_code
 				and (ifnull(item.is_pro_applicable, 'No') = 'Yes'
-					or ifnull(item.is_sub_contracted_item, 'No') = 'Yes'))""" % \
-			(", ".join(["%s"] * len(so_list))), tuple(so_list), as_dict=1)
+					or ifnull(item.is_sub_contracted_item, 'No') = 'Yes')) %s""" % \
+			(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
+
+		if self.fg_item:
+			item_condition = ' and pi.item_code = "' + self.fg_item + '"'
 
 		packed_items = frappe.db.sql("""select distinct pi.parent, pi.item_code, pi.warehouse as reserved_warhouse,
 			(((so_item.qty - ifnull(so_item.delivered_qty, 0)) * pi.qty) / so_item.qty)
@@ -124,8 +131,8 @@
 			and so_item.parent in (%s) and ifnull(so_item.qty, 0) > ifnull(so_item.delivered_qty, 0)
 			and exists (select * from `tabItem` item where item.name=pi.item_code
 				and (ifnull(item.is_pro_applicable, 'No') = 'Yes'
-					or ifnull(item.is_sub_contracted_item, 'No') = 'Yes'))""" % \
-			(", ".join(["%s"] * len(so_list))), tuple(so_list), as_dict=1)
+					or ifnull(item.is_sub_contracted_item, 'No') = 'Yes')) %s""" % \
+			(", ".join(["%s"] * len(so_list)), item_condition), tuple(so_list), as_dict=1)
 
 		return items + packed_items
 
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 7f7dd56..90161f5 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -60,7 +60,7 @@
 
 		# sub-contracting
 		self.validate_for_subcontracting()
-		self.update_raw_materials_supplied("pr_raw_material_details")
+		self.create_raw_materials_supplied("pr_raw_material_details")
 
 		self.update_valuation_rate("purchase_receipt_details")
 
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 870fcd0..4bc34d6 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -106,6 +106,11 @@
 			if item.has_serial_no == "Yes":
 				raise frappe.ValidationError, _("Serialized Item {0} cannot be updated using Stock Reconciliation").format(item_code)
 
+			# item managed batch-wise not allowed
+			if item.has_batch_no == "Yes":
+				frappe.throw(_("Item: {0} managed batch-wise, can not be reconciled using \
+					Stock Reconciliation, instead use Stock Entry").format(item_code))
+
 			# docstatus should be < 2
 			validate_cancelled_item(item_code, item.docstatus, verbose=0)
 
diff --git a/erpnext/stock/utils.py b/erpnext/stock/utils.py
index 526b7c2..8fe5284 100644
--- a/erpnext/stock/utils.py
+++ b/erpnext/stock/utils.py
@@ -79,8 +79,7 @@
 			if not previous_sle:
 				return 0.0
 			previous_stock_queue = json.loads(previous_sle.get('stock_queue', '[]') or '[]')
-			in_rate = previous_stock_queue and \
-				get_fifo_rate(previous_stock_queue, args.get("qty") or 0) or 0
+			in_rate = get_fifo_rate(previous_stock_queue, args.get("qty") or 0) if previous_stock_queue else 0
 		elif valuation_method == 'Moving Average':
 			in_rate = previous_sle.get('valuation_rate') or 0
 
@@ -107,24 +106,25 @@
 		total = sum(f[0] for f in previous_stock_queue)
 		return total and sum(f[0] * f[1] for f in previous_stock_queue) / flt(total) or 0.0
 	else:
-		outgoing_cost = 0
+		available_qty_for_outgoing, outgoing_cost = 0, 0
 		qty_to_pop = abs(qty)
 		while qty_to_pop and previous_stock_queue:
 			batch = previous_stock_queue[0]
 			if 0 < batch[0] <= qty_to_pop:
 				# if batch qty > 0
 				# not enough or exactly same qty in current batch, clear batch
+				available_qty_for_outgoing += flt(batch[0])
 				outgoing_cost += flt(batch[0]) * flt(batch[1])
 				qty_to_pop -= batch[0]
 				previous_stock_queue.pop(0)
 			else:
 				# all from current batch
+				available_qty_for_outgoing += flt(qty_to_pop)
 				outgoing_cost += flt(qty_to_pop) * flt(batch[1])
 				batch[0] -= qty_to_pop
 				qty_to_pop = 0
 
-		# if queue gets blank and qty_to_pop remaining, get average rate of full queue
-		return outgoing_cost / (abs(qty) - qty_to_pop)
+		return outgoing_cost / available_qty_for_outgoing
 
 def get_valid_serial_nos(sr_nos, qty=0, item_code=''):
 	"""split serial nos, validate and return list of valid serial nos"""