From f4729fa6cdbc14bf6a70e1170fb96029f74fa94b Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Mon, 24 Aug 2015 14:32:38 +0530
Subject: [PATCH 1/4] Update delivered / ordered qty in sales/purchase order
 from return entry Reserved Qty logic cleaned up

---
 .../doctype/sales_invoice/sales_invoice.py    | 50 +++++-----------
 .../doctype/purchase_order/purchase_order.py  | 19 ++----
 .../controllers/sales_and_purchase_return.py  | 17 +++++-
 erpnext/controllers/selling_controller.py     | 29 +--------
 erpnext/controllers/stock_controller.py       | 59 ++++++++++++-------
 .../doctype/sales_order/sales_order.py        | 47 ++++++++-------
 .../doctype/delivery_note/delivery_note.py    | 30 ++--------
 .../material_request/material_request.py      | 19 ++----
 .../purchase_receipt/purchase_receipt.py      | 16 ++---
 9 files changed, 120 insertions(+), 166 deletions(-)

diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index f82590a60947..20c766aaa9d4 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -81,9 +81,13 @@ def on_submit(self):
 
 		self.check_prev_docstatus()
 
+		if self.is_return:
+			self.status_updater = []
+					
+		self.update_status_updater_args()
+		self.update_prevdoc_status()
+			
 		if not self.is_return:
-			self.update_status_updater_args()
-			self.update_prevdoc_status()
 			self.update_billing_status_for_zero_amount_refdoc("Sales Order")
 			self.check_credit_limit()
 
@@ -107,9 +111,13 @@ def on_cancel(self):
 		from erpnext.accounts.utils import remove_against_link_from_jv
 		remove_against_link_from_jv(self.doctype, self.name)
 
+		if self.is_return:
+			self.status_updater = []
+			
+		self.update_status_updater_args()
+		self.update_prevdoc_status()
+		
 		if not self.is_return:
-			self.update_status_updater_args()
-			self.update_prevdoc_status()
 			self.update_billing_status_for_zero_amount_refdoc("Sales Order")
 
 		self.validate_c_form_on_cancel()
@@ -294,8 +302,6 @@ def add_remarks(self):
 
 	def so_dn_required(self):
 		"""check in manage account if sales order / delivery note required or not."""
-		if self.is_return:
-			return
 		dic = {'Sales Order':'so_required','Delivery Note':'dn_required'}
 		for i in dic:
 			if frappe.db.get_value('Selling Settings', None, dic[i]) == 'Yes':
@@ -408,35 +414,11 @@ def on_update(self):
 
 	def check_prev_docstatus(self):
 		for d in self.get('items'):
-			if d.sales_order:
-				submitted = frappe.db.sql("""select name from `tabSales Order`
-					where docstatus = 1 and name = %s""", d.sales_order)
-				if not submitted:
-					frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
+			if d.sales_order and frappe.db.get_value("Sales Order", d.sales_order, "docstatus") != 1:
+				frappe.throw(_("Sales Order {0} is not submitted").format(d.sales_order))
 
-			if d.delivery_note:
-				submitted = frappe.db.sql("""select name from `tabDelivery Note`
-					where docstatus = 1 and name = %s""", d.delivery_note)
-				if not submitted:
-					throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
-
-	def update_stock_ledger(self):
-		sl_entries = []
-		for d in self.get_item_list():
-			if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 and d.warehouse and flt(d['qty']):
-				self.update_reserved_qty(d)
-
-				incoming_rate = 0
-				if cint(self.is_return) and self.return_against and self.docstatus==1:
-					incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code,
-						self.return_against)
-
-				sl_entries.append(self.get_sl_entries(d, {
-					"actual_qty": -1*flt(d.qty),
-					"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
-					"incoming_rate": incoming_rate
-				}))
-		self.make_sl_entries(sl_entries)
+			if d.delivery_note and frappe.db.get_value("Delivery Note", d.delivery_note, "docstatus") != 1:
+				throw(_("Delivery Note {0} is not submitted").format(d.delivery_note))
 
 	def make_gl_entries(self, repost_future_gle=True):
 		gl_entries = self.get_gl_entries()
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 9887ddb41687..b1c0a58a7717 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -9,6 +9,7 @@
 from frappe.model.mapper import get_mapped_doc
 from erpnext.controllers.buying_controller import BuyingController
 from erpnext.stock.doctype.item.item import get_last_purchase_details
+from erpnext.utilities.repost_stock import update_bin_qty, get_ordered_qty
 
 
 form_grid_templates = {
@@ -136,20 +137,6 @@ def update_requested_qty(self):
 
 	def update_ordered_qty(self, po_item_rows=None):
 		"""update requested qty (before ordered_qty is updated)"""
-		from erpnext.stock.utils import get_bin
-
-		def _update_ordered_qty(item_code, warehouse):
-			ordered_qty = frappe.db.sql("""
-				select sum((po_item.qty - ifnull(po_item.received_qty, 0))*po_item.conversion_factor)
-				from `tabPurchase Order Item` po_item, `tabPurchase Order` po
-				where po_item.item_code=%s and po_item.warehouse=%s
-				and po_item.qty > ifnull(po_item.received_qty, 0) and po_item.parent=po.name
-				and po.status!='Stopped' and po.docstatus=1""", (item_code, warehouse))
-
-			bin_doc = get_bin(item_code, warehouse)
-			bin_doc.ordered_qty = flt(ordered_qty[0][0]) if ordered_qty else 0
-			bin_doc.save()
-
 		item_wh_list = []
 		for d in self.get("items"):
 			if (not po_item_rows or d.name in po_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
@@ -157,7 +144,9 @@ def _update_ordered_qty(item_code, warehouse):
 				item_wh_list.append([d.item_code, d.warehouse])
 
 		for item_code, warehouse in item_wh_list:
-			_update_ordered_qty(item_code, warehouse)
+			update_bin_qty(item_code, warehouse, {
+				"ordered_qty": get_ordered_qty(item_code, warehouse)
+			})
 
 	def check_modified_date(self):
 		mod_db = frappe.db.sql("select modified from `tabPurchase Order` where name = %s",
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index f429c67983d0..2e4ecaa8c118 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -137,9 +137,24 @@ def update_item(source_doc, target_doc, source_parent):
 		target_doc.qty = -1* source_doc.qty
 		if doctype == "Purchase Receipt":
 			target_doc.received_qty = -1* source_doc.qty
+			target_doc.prevdoc_doctype = source_doc.prevdoc_doctype
+			target_doc.prevdoc_docname = source_doc.prevdoc_docname
+			target_doc.prevdoc_detail_docname = source_doc.prevdoc_detail_docname
 		elif doctype == "Purchase Invoice":
+			target_doc.purchase_order = source_doc.purchase_order
 			target_doc.purchase_receipt = source_doc.purchase_receipt
+			target_doc.po_detail = source_doc.po_detail
 			target_doc.pr_detail = source_doc.pr_detail
+		elif doctype == "Delivery Note":
+			target_doc.against_sales_order = source_doc.against_sales_order
+			target_doc.against_sales_invoice = source_doc.against_sales_invoice
+			target_doc.so_detail = source_doc.so_detail
+			target_doc.si_detail = source_doc.si_detail
+		elif doctype == "Sales Invoice":
+			target_doc.sales_order = source_doc.sales_order
+			target_doc.delivery_note = source_doc.delivery_note
+			target_doc.so_detail = source_doc.so_detail
+			target_doc.dn_detail = source_doc.dn_detail
 
 	doclist = get_mapped_doc(doctype, source_name,	{
 		doctype: {
@@ -152,8 +167,6 @@ def update_item(source_doc, target_doc, source_parent):
 		doctype +" Item": {
 			"doctype": doctype + " Item",
 			"field_map": {
-				"purchase_order": "purchase_order",
-				"purchase_receipt": "purchase_receipt",
 				"serial_no": "serial_no",
 				"batch_no": "batch_no"
 			},
diff --git a/erpnext/controllers/selling_controller.py b/erpnext/controllers/selling_controller.py
index 9e2bf1d76be2..569bb82afef9 100644
--- a/erpnext/controllers/selling_controller.py
+++ b/erpnext/controllers/selling_controller.py
@@ -163,42 +163,17 @@ def validate_max_discount(self):
 	def get_item_list(self):
 		il = []
 		for d in self.get("items"):
-			reserved_warehouse = ""
-			reserved_qty_for_main_item = 0
-
 			if d.qty is None:
 				frappe.throw(_("Row {0}: Qty is mandatory").format(d.idx))
-
-			if self.doctype == "Sales Order":
-				reserved_warehouse = d.warehouse
-				if flt(d.qty) > flt(d.delivered_qty):
-					reserved_qty_for_main_item = flt(d.qty) - flt(d.delivered_qty)
-
-			elif (((self.doctype == "Delivery Note" and d.against_sales_order) 
-					or (self.doctype == "Sales Invoice" and d.sales_order and self.update_stock)) 
-					and not self.is_return):
-				# if SO qty is 10 and there is tolerance of 20%, then it will allow DN of 12.
-				# But in this case reserved qty should only be reduced by 10 and not 12
-
-				already_delivered_qty = self.get_already_delivered_qty(self.name,
-					d.against_sales_order if self.doctype=="Delivery Note" else d.sales_order, d.so_detail)
-				so_qty, reserved_warehouse = self.get_so_qty_and_warehouse(d.so_detail)
-
-				if already_delivered_qty + d.qty > so_qty:
-					reserved_qty_for_main_item = -(so_qty - already_delivered_qty)
-				else:
-					reserved_qty_for_main_item = -flt(d.qty)
-					
+														
 			if self.has_product_bundle(d.item_code):
 				for p in self.get("packed_items"):
 					if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
 						# the packing details table's qty is already multiplied with parent's qty
 						il.append(frappe._dict({
 							'warehouse': p.warehouse,
-							'reserved_warehouse': reserved_warehouse,
 							'item_code': p.item_code,
 							'qty': flt(p.qty),
-							'reserved_qty': (flt(p.qty)/flt(d.qty)) * reserved_qty_for_main_item,
 							'uom': p.uom,
 							'batch_no': cstr(p.batch_no).strip(),
 							'serial_no': cstr(p.serial_no).strip(),
@@ -207,10 +182,8 @@ def get_item_list(self):
 			else:
 				il.append(frappe._dict({
 					'warehouse': d.warehouse,
-					'reserved_warehouse': reserved_warehouse,
 					'item_code': d.item_code,
 					'qty': d.qty,
-					'reserved_qty': reserved_qty_for_main_item,
 					'uom': d.stock_uom,
 					'stock_uom': d.stock_uom,
 					'batch_no': cstr(d.get("batch_no")).strip(),
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index cfde04b57200..396d088a53a3 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -6,11 +6,9 @@
 from frappe.utils import cint, flt, cstr
 from frappe import msgprint, _
 import frappe.defaults
-
-from erpnext.controllers.accounts_controller import AccountsController
 from erpnext.accounts.general_ledger import make_gl_entries, delete_gl_entries, process_gl_map
-from erpnext.stock.utils import update_bin
 
+from erpnext.controllers.accounts_controller import AccountsController
 
 class StockController(AccountsController):
 	def make_gl_entries(self, repost_future_gle=True):
@@ -229,23 +227,44 @@ def get_incoming_rate_for_sales_return(self, item_code, against_document):
 			incoming_rate = incoming_rate[0][0] if incoming_rate else 0.0
 
 		return incoming_rate
-
-	def update_reserved_qty(self, d):
-		if d['reserved_qty'] < 0 :
-			# Reduce reserved qty from reserved warehouse mentioned in so
-			if not d["reserved_warehouse"]:
-				frappe.throw(_("Delivery Warehouse is missing in Sales Order"))
-
-			args = {
-				"item_code": d['item_code'],
-				"warehouse": d["reserved_warehouse"],
-				"voucher_type": self.doctype,
-				"voucher_no": self.name,
-				"reserved_qty": (self.docstatus==1 and 1 or -1)*flt(d['reserved_qty']),
-				"posting_date": self.posting_date,
-				"is_amended": self.amended_from and 'Yes' or 'No'
-			}
-			update_bin(args)
+			
+	def update_reserved_qty(self):
+		so_map = {}
+		for d in self.get("items"):
+			if d.so_detail:
+				if self.doctype == "Delivery Note" and d.against_sales_order:
+					so_map.setdefault(d.against_sales_order, []).append(d.so_detail)
+				elif self.doctype == "Sales Invoice" and d.sales_order and self.update_stock:
+					so_map.setdefault(d.sales_order, []).append(d.so_detail)
+
+		for so, so_item_rows in so_map.items():
+			if so and so_item_rows:
+				sales_order = frappe.get_doc("Sales Order", so)
+
+				if sales_order.status in ["Stopped", "Cancelled"]:
+					frappe.throw(_("Sales Order {0} is cancelled or stopped").format(so), frappe.InvalidStatusError)
+				
+				sales_order.update_reserved_qty(so_item_rows)
+				
+	def update_stock_ledger(self):
+		self.update_reserved_qty()
+		
+		sl_entries = []
+		for d in self.get_item_list():
+			if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 \
+					and d.warehouse and flt(d['qty']):
+				
+				incoming_rate = 0
+				if cint(self.is_return) and self.return_against and self.docstatus==1:
+					incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against)
+					
+				sl_entries.append(self.get_sl_entries(d, {
+					"actual_qty": -1*flt(d['qty']),
+					"stock_uom": frappe.db.get_value("Item", d.item_code, "stock_uom"),
+					"incoming_rate": incoming_rate
+				}))
+
+		self.make_sl_entries(sl_entries)
 
 def update_gl_entries_after(posting_date, posting_time, for_warehouses=None, for_items=None,
 		warehouse_account=None):
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index b05c009fa0d7..2e57a115355c 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -8,6 +8,7 @@
 from frappe.utils import cstr, flt, getdate, comma_and
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
+from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty
 
 from erpnext.controllers.selling_controller import SellingController
 
@@ -151,7 +152,7 @@ def on_submit(self):
 		super(SalesOrder, self).on_submit()
 
 		self.check_credit_limit()
-		self.update_stock_ledger(update_stock = 1)
+		self.update_reserved_qty()
 
 		frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.base_grand_total, self)
 
@@ -164,7 +165,7 @@ def on_cancel(self):
 			frappe.throw(_("Stopped order cannot be cancelled. Unstop to cancel."))
 
 		self.check_nextdoc_docstatus()
-		self.update_stock_ledger(update_stock = -1)
+		self.update_reserved_qty()
 
 		self.update_prevdoc_status('cancel')
 
@@ -213,32 +214,38 @@ def check_modified_date(self):
 
 	def stop_sales_order(self):
 		self.check_modified_date()
-		self.update_stock_ledger(-1)
+		self.update_reserved_qty()
 		frappe.db.set(self, 'status', 'Stopped')
 		frappe.msgprint(_("{0} {1} status is Stopped").format(self.doctype, self.name))
 		self.notify_modified()
 
 	def unstop_sales_order(self):
 		self.check_modified_date()
-		self.update_stock_ledger(1)
+		self.update_reserved_qty()
 		frappe.db.set(self, 'status', 'Submitted')
 		frappe.msgprint(_("{0} {1} status is Unstopped").format(self.doctype, self.name))
-
-
-	def update_stock_ledger(self, update_stock):
-		from erpnext.stock.utils import update_bin
-		for d in self.get_item_list():
-			if frappe.db.get_value("Item", d['item_code'], "is_stock_item")==1:
-				args = {
-					"item_code": d['item_code'],
-					"warehouse": d['reserved_warehouse'],
-					"reserved_qty": flt(update_stock) * flt(d['reserved_qty']),
-					"posting_date": self.transaction_date,
-					"voucher_type": self.doctype,
-					"voucher_no": self.name,
-					"is_amended": self.amended_from and 'Yes' or 'No'
-				}
-				update_bin(args)
+				
+	def update_reserved_qty(self, so_item_rows=None):
+		"""update requested qty (before ordered_qty is updated)"""
+		item_wh_list = []
+		def _valid_for_reserve(item_code, warehouse):
+			if item_code and warehouse and [item_code, warehouse] not in item_wh_list \
+				and frappe.db.get_value("Item", item_code, "is_stock_item"):
+					item_wh_list.append([item_code, warehouse])
+		
+		for d in self.get("items"):
+			if (not so_item_rows or d.name in so_item_rows):
+				_valid_for_reserve(d.item_code, d.warehouse)
+						
+				if self.has_product_bundle(d.item_code):
+					for p in self.get("packed_items"):
+						if p.parent_detail_docname == d.name and p.parent_item == d.item_code:
+							_valid_for_reserve(p.item_code, p.warehouse)
+
+		for item_code, warehouse in item_wh_list:
+			update_bin_qty(item_code, warehouse, {
+				"reserved_qty": get_reserved_qty(item_code, warehouse)
+			})
 
 	def on_update(self):
 		pass
diff --git a/erpnext/stock/doctype/delivery_note/delivery_note.py b/erpnext/stock/doctype/delivery_note/delivery_note.py
index 5e11962ea56e..201546e45739 100644
--- a/erpnext/stock/doctype/delivery_note/delivery_note.py
+++ b/erpnext/stock/doctype/delivery_note/delivery_note.py
@@ -83,7 +83,7 @@ def set_actual_qty(self):
 
 	def so_required(self):
 		"""check in manage account if sales order required or not"""
-		if not self.is_return and frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes':
+		if frappe.db.get_value("Selling Settings", None, 'so_required') == 'Yes':
 			 for d in self.get('items'):
 				 if not d.against_sales_order:
 					 frappe.throw(_("Sales Order required for Item {0}").format(d.item_code))
@@ -174,10 +174,10 @@ def on_submit(self):
 		# Check for Approving Authority
 		frappe.get_doc('Authorization Control').validate_approving_authority(self.doctype, self.company, self.base_grand_total, self)
 
+		# update delivered qty in sales order
+		self.update_prevdoc_status()
+		
 		if not self.is_return:
-			# update delivered qty in sales order
-			self.update_prevdoc_status()
-
 			self.check_credit_limit()
 
 		self.update_stock_ledger()
@@ -190,8 +190,7 @@ def on_cancel(self):
 		self.check_stop_sales_order("against_sales_order")
 		self.check_next_docstatus()
 
-		if not self.is_return:
-			self.update_prevdoc_status()
+		self.update_prevdoc_status()
 
 		self.update_stock_ledger()
 
@@ -242,25 +241,6 @@ def cancel_packing_slips(self):
 				ps.cancel()
 			frappe.msgprint(_("Packing Slip(s) cancelled"))
 
-
-	def update_stock_ledger(self):
-		sl_entries = []
-		for d in self.get_item_list():
-			if frappe.db.get_value("Item", d.item_code, "is_stock_item") == 1 \
-					and d.warehouse and flt(d['qty']):
-				self.update_reserved_qty(d)
-				
-				incoming_rate = 0
-				if cint(self.is_return) and self.return_against and self.docstatus==1:
-					incoming_rate = self.get_incoming_rate_for_sales_return(d.item_code, self.return_against)
-					
-				sl_entries.append(self.get_sl_entries(d, {
-					"actual_qty": -1*flt(d['qty']),
-					"incoming_rate": incoming_rate
-				}))
-
-		self.make_sl_entries(sl_entries)
-
 def get_list_context(context=None):
 	from erpnext.controllers.website_list_for_contact import get_list_context
 	list_context = get_list_context(context)
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index fd6f943a954f..03af0722a341 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -10,9 +10,11 @@
 from frappe.utils import cstr, flt, getdate
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
+from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty
 
 from erpnext.controllers.buying_controller import BuyingController
 
+
 form_grid_templates = {
 	"items": "templates/form_grid/material_request_grid.html"
 }
@@ -136,19 +138,6 @@ def update_completed_qty(self, mr_items=None):
 
 	def update_requested_qty(self, mr_item_rows=None):
 		"""update requested qty (before ordered_qty is updated)"""
-		from erpnext.stock.utils import get_bin
-
-		def _update_requested_qty(item_code, warehouse):
-			requested_qty = frappe.db.sql("""select sum(mr_item.qty - ifnull(mr_item.ordered_qty, 0))
-				from `tabMaterial Request Item` mr_item, `tabMaterial Request` mr
-				where mr_item.item_code=%s and mr_item.warehouse=%s
-				and mr_item.qty > ifnull(mr_item.ordered_qty, 0) and mr_item.parent=mr.name
-				and mr.status!='Stopped' and mr.docstatus=1""", (item_code, warehouse))
-
-			bin_doc = get_bin(item_code, warehouse)
-			bin_doc.indented_qty = flt(requested_qty[0][0]) if requested_qty else 0
-			bin_doc.save()
-
 		item_wh_list = []
 		for d in self.get("items"):
 			if (not mr_item_rows or d.name in mr_item_rows) and [d.item_code, d.warehouse] not in item_wh_list \
@@ -156,7 +145,9 @@ def _update_requested_qty(item_code, warehouse):
 				item_wh_list.append([d.item_code, d.warehouse])
 
 		for item_code, warehouse in item_wh_list:
-			_update_requested_qty(item_code, warehouse)
+			update_bin_qty(item_code, warehouse, {
+				"indented_qty": get_indented_qty(item_code, warehouse)
+			})
 
 def update_completed_and_requested_qty(stock_entry, method):
 	if stock_entry.doctype == "Stock Entry":
diff --git a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
index 2979c124525b..f4273b27ed74 100644
--- a/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/purchase_receipt.py
@@ -117,7 +117,7 @@ def validate_with_previous_doc(self):
 			self.validate_rate_with_reference_doc([["Purchase Order", "prevdoc_docname", "prevdoc_detail_docname"]])
 
 	def po_required(self):
-		if not self.is_return and frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
+		if frappe.db.get_value("Buying Settings", None, "po_required") == 'Yes':
 			 for d in self.get('items'):
 				 if not d.prevdoc_docname:
 					 frappe.throw(_("Purchase Order number required for Item {0}").format(d.item_code))
@@ -221,9 +221,10 @@ def on_submit(self):
 		# Set status as Submitted
 		frappe.db.set(self, 'status', 'Submitted')
 
+		self.update_prevdoc_status()
+		self.update_ordered_qty()
+		
 		if not self.is_return:
-			self.update_prevdoc_status()
-			self.update_ordered_qty()
 			purchase_controller.update_last_purchase_rate(self, 1)
 
 		self.update_stock_ledger()
@@ -257,12 +258,11 @@ def on_cancel(self):
 
 		self.update_stock_ledger()
 
+		self.update_prevdoc_status()
+		# Must be called after updating received qty in PO
+		self.update_ordered_qty()
+		
 		if not self.is_return:
-			self.update_prevdoc_status()
-
-			# Must be called after updating received qty in PO
-			self.update_ordered_qty()
-
 			pc_obj.update_last_purchase_rate(self, 0)
 
 		self.make_gl_entries_on_cancel()

From bdb735df14399e598282fe6271ba5fb6f97cf8f6 Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Mon, 24 Aug 2015 16:10:43 +0530
Subject: [PATCH 2/4] Update order reference in return entries and repost
 reserved / ordered qty

---
 erpnext/patches.txt                           |  1 +
 ...pdate_order_reference_in_return_entries.py | 76 +++++++++++++++++++
 .../doctype/sales_order/sales_order.py        |  4 +-
 3 files changed, 79 insertions(+), 2 deletions(-)
 create mode 100644 erpnext/patches/v5_7/update_order_reference_in_return_entries.py

diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index 21656c6d6594..ea10054d525e 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -193,3 +193,4 @@ execute:frappe.db.sql("update `tabProduction Order` pro set description = (selec
 erpnext.patches.v5_7.item_template_attributes
 erpnext.patches.v4_2.repost_reserved_qty #2015-08-20
 erpnext.patches.v5_4.update_purchase_cost_against_project
+erpnext.patches.v5_7.update_order_reference_in_return_entries
\ No newline at end of file
diff --git a/erpnext/patches/v5_7/update_order_reference_in_return_entries.py b/erpnext/patches/v5_7/update_order_reference_in_return_entries.py
new file mode 100644
index 000000000000..c957242260b6
--- /dev/null
+++ b/erpnext/patches/v5_7/update_order_reference_in_return_entries.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors
+# License: GNU General Public License v3. See license.txt
+
+from __future__ import unicode_literals
+import frappe
+
+def execute():
+	# sales return
+	return_entries = list(frappe.db.sql("""
+		select dn.name as name, dn_item.name as row_id, dn.return_against, 
+			dn_item.item_code, "Delivery Note" as doctype
+		from `tabDelivery Note Item` dn_item, `tabDelivery Note` dn
+		where dn_item.parent=dn.name and dn.is_return=1 and dn.docstatus < 2
+	""", as_dict=1))
+	
+	return_entries += list(frappe.db.sql("""
+		select si.name as name, si_item.name as row_id, si.return_against, 
+			si_item.item_code, "Sales Invoice" as doctype
+		from `tabSales Invoice Item` si_item, `tabSales Invoice` si
+		where si_item.parent=si.name and si.is_return=1 and si.update_stock=1 and si.docstatus < 2
+	""", as_dict=1))
+	
+	for d in return_entries:
+		ref_field = "against_sales_order" if d.doctype == "Delivery Note" else "sales_order"
+		order_details = frappe.db.sql("""
+			select {0} as sales_order, so_detail 
+			from `tab{1} Item` item
+			where 
+				parent=%s and item_code=%s 
+				and ifnull(so_detail, '') !=''
+			order by
+				(select transaction_date from `tabSales Order` where name=item.{3}) DESC
+		""".format(ref_field, d.doctype, ref_field, ref_field), (d.return_against, d.item_code), as_dict=1)
+		
+		if order_details:
+			frappe.db.sql("""
+				update `tab{0} Item`
+				set {1}=%s, so_detail=%s
+				where name=%s
+			""".format(d.doctype, ref_field), 
+			(order_details[0].sales_order, order_details[0].so_detail, d.row_id))
+			
+			doc = frappe.get_doc(d.doctype, d.name)
+			doc.update_reserved_qty()
+			
+	
+	#--------------------------
+	# purchase return
+	return_entries = frappe.db.sql("""
+		select pr.name as name, pr_item.name as row_id, pr.return_against, pr_item.item_code
+		from `tabPurchase Receipt Item` pr_item, `tabPurchase Receipt` pr
+		where pr_item.parent=pr.name and pr.is_return=1 and pr.docstatus < 2
+	""", as_dict=1)
+	
+	for d in return_entries:
+		order_details = frappe.db.sql("""
+			select prevdoc_docname as purchase_order, prevdoc_detail_docname as po_detail 
+			from `tabPurchase Receipt Item` item
+			where 
+				parent=%s and item_code=%s 
+				and ifnull(prevdoc_detail_docname, '') !='' 
+				and ifnull(prevdoc_doctype, '') = 'Purchase Order' and ifnull(prevdoc_detail_docname, '') != ''
+			order by
+				(select transaction_date from `tabPurchase Order` where name=item.prevdoc_detail_docname) DESC
+		""", (d.return_against, d.item_code), as_dict=1)
+		
+		if order_details:
+			frappe.db.sql("""
+				update `tabPurchase Receipt Item`
+				set prevdoc_doctype='Purchase Order', prevdoc_docname=%s, prevdoc_detail_docname=%s
+				where name=%s
+			""", (order_details[0].purchase_order, order_details[0].po_detail, d.row_id))
+			
+			pr = frappe.get_doc("Purchase Receipt", d.name)
+			pr.update_ordered_qty()
+	
\ No newline at end of file
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 2e57a115355c..4f9d54dfbf21 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -214,15 +214,15 @@ def check_modified_date(self):
 
 	def stop_sales_order(self):
 		self.check_modified_date()
-		self.update_reserved_qty()
 		frappe.db.set(self, 'status', 'Stopped')
+		self.update_reserved_qty()
 		frappe.msgprint(_("{0} {1} status is Stopped").format(self.doctype, self.name))
 		self.notify_modified()
 
 	def unstop_sales_order(self):
 		self.check_modified_date()
-		self.update_reserved_qty()
 		frappe.db.set(self, 'status', 'Submitted')
+		self.update_reserved_qty()
 		frappe.msgprint(_("{0} {1} status is Unstopped").format(self.doctype, self.name))
 				
 	def update_reserved_qty(self, so_item_rows=None):

From 30bb2df0a464452814800b396321f8da6d17dcdc Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Mon, 24 Aug 2015 16:16:29 +0530
Subject: [PATCH 3/4] utilities/repost_stock file renamed to
 stock/stock_balance

---
 erpnext/buying/doctype/purchase_order/purchase_order.py         | 2 +-
 .../patches/repair_tools/set_stock_balance_as_per_serial_no.py  | 2 +-
 erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py   | 2 +-
 erpnext/patches/v4_2/repost_reserved_qty.py                     | 2 +-
 erpnext/patches/v4_2/update_requested_and_ordered_qty.py        | 2 +-
 erpnext/patches/v5_0/repost_requested_qty.py                    | 2 +-
 .../patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py   | 2 +-
 erpnext/selling/doctype/sales_order/sales_order.py              | 2 +-
 erpnext/stock/doctype/item/item.py                              | 2 +-
 erpnext/stock/doctype/material_request/material_request.py      | 2 +-
 erpnext/stock/doctype/warehouse/warehouse.py                    | 2 +-
 erpnext/{utilities/repost_stock.py => stock/stock_balance.py}   | 0
 12 files changed, 11 insertions(+), 11 deletions(-)
 rename erpnext/{utilities/repost_stock.py => stock/stock_balance.py} (100%)

diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index b1c0a58a7717..58b1d1938dc0 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -9,7 +9,7 @@
 from frappe.model.mapper import get_mapped_doc
 from erpnext.controllers.buying_controller import BuyingController
 from erpnext.stock.doctype.item.item import get_last_purchase_details
-from erpnext.utilities.repost_stock import update_bin_qty, get_ordered_qty
+from erpnext.stock.stock_balance import update_bin_qty, get_ordered_qty
 
 
 form_grid_templates = {
diff --git a/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py b/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py
index 4a4266e7fd21..5a421d146fec 100644
--- a/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py
+++ b/erpnext/patches/repair_tools/set_stock_balance_as_per_serial_no.py
@@ -5,7 +5,7 @@
 import frappe
 
 def execute():
-	from erpnext.utilities.repost_stock import set_stock_balance_as_per_serial_no
+	from erpnext.stock.stock_balance import set_stock_balance_as_per_serial_no
 	frappe.db.auto_commit_on_many_writes = 1
 
 	set_stock_balance_as_per_serial_no()
diff --git a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
index 73a56a8e9875..0df5801c4257 100644
--- a/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
+++ b/erpnext/patches/v4_2/fix_gl_entries_for_stock_transactions.py
@@ -6,7 +6,7 @@
 from frappe.utils import flt
 
 def execute():
-	from erpnext.utilities.repost_stock import repost
+	from erpnext.stock.stock_balance import repost
 	repost(allow_zero_rate=True, only_actual=True)
 	
 	frappe.reload_doctype("Account")
diff --git a/erpnext/patches/v4_2/repost_reserved_qty.py b/erpnext/patches/v4_2/repost_reserved_qty.py
index a2cd4d8ad676..74f4b79f93bf 100644
--- a/erpnext/patches/v4_2/repost_reserved_qty.py
+++ b/erpnext/patches/v4_2/repost_reserved_qty.py
@@ -3,7 +3,7 @@
 
 from __future__ import unicode_literals
 import frappe
-from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty
+from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
 
 def execute():
 	repost_for = frappe.db.sql("""
diff --git a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
index 240b11dae55f..7bb49e64dfe5 100644
--- a/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
+++ b/erpnext/patches/v4_2/update_requested_and_ordered_qty.py
@@ -5,7 +5,7 @@
 import frappe
 
 def execute():
-	from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty, get_ordered_qty
+	from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty, get_ordered_qty
 
 	count=0
 	for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse from
diff --git a/erpnext/patches/v5_0/repost_requested_qty.py b/erpnext/patches/v5_0/repost_requested_qty.py
index 8c1d6f7fbb18..6af71f3fc49a 100644
--- a/erpnext/patches/v5_0/repost_requested_qty.py
+++ b/erpnext/patches/v5_0/repost_requested_qty.py
@@ -5,7 +5,7 @@
 import frappe
 
 def execute():
-	from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty
+	from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
 
 	count=0
 	for item_code, warehouse in frappe.db.sql("""select distinct item_code, warehouse 
diff --git a/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py b/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py
index 251dd56acbe0..6eb3994c7c51 100644
--- a/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py
+++ b/erpnext/patches/v5_4/fix_reserved_qty_and_sle_for_packed_items.py
@@ -3,7 +3,7 @@
 
 from __future__ import unicode_literals
 import frappe
-from erpnext.utilities.repost_stock import repost_actual_qty
+from erpnext.stock.stock_balance import repost_actual_qty
 
 def execute():
 	cancelled_invoices = frappe.db.sql_list("""select name from `tabSales Invoice` 
diff --git a/erpnext/selling/doctype/sales_order/sales_order.py b/erpnext/selling/doctype/sales_order/sales_order.py
index 4f9d54dfbf21..dd21f0553e9e 100644
--- a/erpnext/selling/doctype/sales_order/sales_order.py
+++ b/erpnext/selling/doctype/sales_order/sales_order.py
@@ -8,7 +8,7 @@
 from frappe.utils import cstr, flt, getdate, comma_and
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
-from erpnext.utilities.repost_stock import update_bin_qty, get_reserved_qty
+from erpnext.stock.stock_balance import update_bin_qty, get_reserved_qty
 
 from erpnext.controllers.selling_controller import SellingController
 
diff --git a/erpnext/stock/doctype/item/item.py b/erpnext/stock/doctype/item/item.py
index f9b41f16577a..0087f18cbed7 100644
--- a/erpnext/stock/doctype/item/item.py
+++ b/erpnext/stock/doctype/item/item.py
@@ -269,7 +269,7 @@ def set_last_purchase_rate(self, newdn):
 		frappe.db.set_value("Item", newdn, "last_purchase_rate", last_purchase_rate)
 
 	def recalculate_bin_qty(self, newdn):
-		from erpnext.utilities.repost_stock import repost_stock
+		from erpnext.stock.stock_balance import repost_stock
 		frappe.db.auto_commit_on_many_writes = 1
 		existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 03af0722a341..e4183669bf48 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -10,7 +10,7 @@
 from frappe.utils import cstr, flt, getdate
 from frappe import _
 from frappe.model.mapper import get_mapped_doc
-from erpnext.utilities.repost_stock import update_bin_qty, get_indented_qty
+from erpnext.stock.stock_balance import update_bin_qty, get_indented_qty
 
 from erpnext.controllers.buying_controller import BuyingController
 
diff --git a/erpnext/stock/doctype/warehouse/warehouse.py b/erpnext/stock/doctype/warehouse/warehouse.py
index 5a3976399fec..610c7b88a3b9 100644
--- a/erpnext/stock/doctype/warehouse/warehouse.py
+++ b/erpnext/stock/doctype/warehouse/warehouse.py
@@ -140,7 +140,7 @@ def after_rename(self, olddn, newdn, merge=False):
 			self.recalculate_bin_qty(newdn)
 
 	def recalculate_bin_qty(self, newdn):
-		from erpnext.utilities.repost_stock import repost_stock
+		from erpnext.stock.stock_balance import repost_stock
 		frappe.db.auto_commit_on_many_writes = 1
 		existing_allow_negative_stock = frappe.db.get_value("Stock Settings", None, "allow_negative_stock")
 		frappe.db.set_value("Stock Settings", None, "allow_negative_stock", 1)
diff --git a/erpnext/utilities/repost_stock.py b/erpnext/stock/stock_balance.py
similarity index 100%
rename from erpnext/utilities/repost_stock.py
rename to erpnext/stock/stock_balance.py

From c14c6a3765fa8e98d8bbb6e65a5ecceeb4ce026a Mon Sep 17 00:00:00 2001
From: Nabin Hait <nabinhait@gmail.com>
Date: Mon, 24 Aug 2015 16:46:00 +0530
Subject: [PATCH 4/4] Allow different rate in Return Entry

---
 erpnext/controllers/sales_and_purchase_return.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index 2e4ecaa8c118..bb3b63cdd3b7 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -83,9 +83,10 @@ def validate_returned_items(doc):
 				elif abs(d.qty) > max_return_qty:
 					frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
 						.format(d.idx, ref.qty, d.item_code), StockOverReturnError)
-				elif ref.rate and flt(d.rate) != ref.rate:
-					frappe.throw(_("Row # {0}: Rate must be same as {1} {2}")
-						.format(d.idx, doc.doctype, doc.return_against))
+				elif ref.rate and (doc.doctype in ("Delivery Note", "Purchase Receipt") \
+					or (doc.doctype=="Sales Invoice" and doc.update_stock==1)) and flt(d.rate) > ref.rate:
+						frappe.throw(_("Row # {0}: Rate cannot be greater than {1} {2}")
+							.format(d.idx, doc.doctype, doc.return_against))
 				elif ref.batch_no and d.batch_no != ref.batch_no:
 					frappe.throw(_("Row # {0}: Batch No must be same as {1} {2}")
 						.format(d.idx, doc.doctype, doc.return_against))