Multi-UOM for sales/purchase return (#13132)

* Multi-UOM for sales/purchase return

* Update sales_and_purchase_return.py
diff --git a/erpnext/controllers/sales_and_purchase_return.py b/erpnext/controllers/sales_and_purchase_return.py
index d16f063..4b8bbee 100644
--- a/erpnext/controllers/sales_and_purchase_return.py
+++ b/erpnext/controllers/sales_and_purchase_return.py
@@ -53,8 +53,9 @@
 
 	valid_items = frappe._dict()
 
-	select_fields = "item_code, qty, rate, parenttype" if doc.doctype=="Purchase Invoice" \
-		else "item_code, qty, rate, serial_no, batch_no, parenttype"
+	select_fields = "item_code, qty, stock_qty, rate, parenttype, conversion_factor"
+	if doc.doctype != 'Purchase Invoice':
+		select_fields += ",serial_no, batch_no"
 
 	if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
 		select_fields += ",rejected_qty, received_qty"
@@ -111,7 +112,7 @@
 		frappe.throw(_("Atleast one item should be entered with negative quantity in return document"))
 
 def validate_quantity(doc, args, ref, valid_items, already_returned_items):
-	fields = ['qty']
+	fields = ['stock_qty']
 	if doc.doctype in ['Purchase Receipt', 'Purchase Invoice']:
 		fields.extend(['received_qty', 'rejected_qty'])
 
@@ -119,16 +120,19 @@
 
 	for column in fields:
 		returned_qty = flt(already_returned_data.get(column, 0)) if len(already_returned_data) > 0 else 0
-		reference_qty = ref.get(column)
+		reference_qty = (ref.get(column) if column == 'stock_qty'
+			else ref.get(column) * ref.get("conversion_factor", 1.0))
+
 		max_returnable_qty = flt(reference_qty) - returned_qty
 		label = column.replace('_', ' ').title()
+
 		if reference_qty:	
 			if flt(args.get(column)) > 0:
 				frappe.throw(_("{0} must be negative in return document").format(label))
 			elif returned_qty >= reference_qty and args.get(column):
 				frappe.throw(_("Item {0} has already been returned")
 					.format(args.item_code), StockOverReturnError)
-			elif abs(args.get(column)) > max_returnable_qty:
+			elif (abs(args.get(column)) * args.get("conversion_factor", 1.0)) > max_returnable_qty:
 				frappe.throw(_("Row # {0}: Cannot return more than {1} for Item {2}")
 					.format(args.idx, reference_qty, args.item_code), StockOverReturnError)
 
@@ -138,6 +142,7 @@
 	valid_items.setdefault(ref_item_row.item_code, frappe._dict({
 		"qty": 0,
 		"rate": 0,
+		"stock_qty": 0,
 		"rejected_qty": 0,
 		"received_qty": 0,
 		"serial_no": [],
@@ -145,6 +150,7 @@
 	}))
 	item_dict = valid_items[ref_item_row.item_code]
 	item_dict["qty"] += ref_item_row.qty
+	item_dict["stock_qty"] += ref_item_row.get('stock_qty', 0)
 	if ref_item_row.get("rate", 0) > item_dict["rate"]:
 		item_dict["rate"] = ref_item_row.get("rate", 0)
 
@@ -161,9 +167,10 @@
 	return valid_items
 
 def get_already_returned_items(doc):
-	column = 'child.item_code, sum(abs(child.qty)) as qty'
+	column = 'child.item_code, sum(abs(child.qty)) as qty, sum(abs(child.stock_qty)) as stock_qty'
 	if doc.doctype in ['Purchase Invoice', 'Purchase Receipt']:
-		column += ', sum(abs(child.rejected_qty)) as rejected_qty, sum(abs(child.received_qty)) as received_qty'
+		column += """, sum(abs(child.rejected_qty) * child.conversion_factor) as rejected_qty,
+			sum(abs(child.received_qty) * child.conversion_factor) as received_qty"""
 
 	data = frappe.db.sql("""
 		select {0}
@@ -180,6 +187,7 @@
 	for d in data:
 		items.setdefault(d.item_code, frappe._dict({
 			"qty": d.get("qty"),
+			"stock_qty": d.get("stock_qty"),
 			"received_qty": d.get("received_qty"),
 			"rejected_qty": d.get("rejected_qty")
 		}))
diff --git a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
index b656c3f..29caea1 100644
--- a/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
+++ b/erpnext/stock/doctype/purchase_receipt/test_purchase_receipt.py
@@ -11,7 +11,7 @@
 from erpnext import set_perpetual_inventory
 from erpnext.stock.doctype.serial_no.serial_no import SerialNoDuplicateError
 from erpnext.accounts.doctype.account.test_account import get_inventory_account
-
+from erpnext.stock.doctype.item.test_item import make_item
 
 class TestPurchaseReceipt(unittest.TestCase):
 	def setUp(self):
@@ -203,6 +203,22 @@
 			"delivery_document_no": return_pr.name
 		})
 
+	def test_purchase_return_for_multi_uom(self):
+		item_code = "_Test Purchase Return For Multi-UOM"
+		if not frappe.db.exists('Item', item_code):
+			item = make_item(item_code, {'stock_uom': 'Box'})
+			row = item.append('uoms', {
+				'uom': 'Unit',
+				'conversion_factor': 0.1
+			})
+			row.db_update()
+
+		pr = make_purchase_receipt(item_code=item_code, qty=1, uom="Box", conversion_factor=1.0)
+		return_pr = make_purchase_receipt(item_code=item_code, qty=-10, uom="Unit",
+			stock_uom="Box", conversion_factor=0.1, is_return=1, return_against=pr.name)
+
+		self.assertEquals(abs(return_pr.items[0].stock_qty), 1.0)
+
 	def test_closed_purchase_receipt(self):
 		from erpnext.stock.doctype.purchase_receipt.purchase_receipt import update_purchase_receipt_status
 
@@ -255,7 +271,6 @@
 
 	def test_not_accept_duplicate_serial_no(self):
 		from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
-		from erpnext.stock.doctype.item.test_item import make_item
 		from erpnext.stock.doctype.delivery_note.test_delivery_note import create_delivery_note
 
 		item_code = frappe.db.get_value('Item', {'has_serial_no': 1})
@@ -307,9 +322,10 @@
 		"rejected_qty": rejected_qty,
 		"rejected_warehouse": args.rejected_warehouse or "_Test Rejected Warehouse - _TC" if rejected_qty != 0 else "",
 		"rate": args.rate or 50,
-		"conversion_factor": 1.0,
+		"conversion_factor": args.conversion_factor or 1.0,
 		"serial_no": args.serial_no,
-		"stock_uom": "_Test UOM"
+		"stock_uom": args.stock_uom or "_Test UOM",
+		"uom": args.uom or "_Test UOM"
 	})
 
 	if not args.do_not_save:
diff --git a/erpnext/utilities/transaction_base.py b/erpnext/utilities/transaction_base.py
index 341c511..77ba694 100644
--- a/erpnext/utilities/transaction_base.py
+++ b/erpnext/utilities/transaction_base.py
@@ -71,6 +71,8 @@
 		validate_uom_is_integer(self, uom_field, qty_fields)
 
 	def validate_with_previous_doc(self, ref):
+		self.exclude_fields = ["conversion_factor", "uom"] if self.get('is_return') else []
+
 		for key, val in ref.items():
 			is_child = val.get("is_child_table")
 			ref_doc = {}
@@ -101,7 +103,7 @@
 					frappe.throw(_("Invalid reference {0} {1}").format(reference_doctype, reference_name))
 
 				for field, condition in fields:
-					if prevdoc_values[field] is not None:
+					if prevdoc_values[field] is not None and field not in self.exclude_fields:
 						self.validate_value(field, condition, prevdoc_values[field], doc)