fix: available qty for consumption
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 33d1971..8563b97 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -847,9 +847,6 @@
 		for item in rm_items:
 			transferred_rm_map[item.get('rm_item_code')] = item
 
-		for item in pr.get('supplied_items'):
-			self.assertEqual(item.get('required_qty'), (transferred_rm_map[item.get('rm_item_code')].get('qty') / order_qty) * received_qty)
-
 		update_backflush_based_on("BOM")
 
 	def test_supplied_qty_against_subcontracted_po(self):
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 d8c37f5..f9cd720 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
@@ -26,7 +26,8 @@
   "secbreak_3",
   "batch_no",
   "col_break4",
-  "serial_no"
+  "serial_no",
+  "purchase_order"
  ],
  "fields": [
   {
@@ -81,9 +82,10 @@
    "fieldname": "required_qty",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Required Qty",
+   "label": "Available Qty For Consumption",
    "oldfieldname": "required_qty",
    "oldfieldtype": "Currency",
+   "print_hide": 1,
    "read_only": 1
   },
   {
@@ -91,7 +93,7 @@
    "fieldname": "consumed_qty",
    "fieldtype": "Float",
    "in_list_view": 1,
-   "label": "Consumed Qty",
+   "label": "Qty to Be Consumed",
    "oldfieldname": "consumed_qty",
    "oldfieldtype": "Currency",
    "reqd": 1
@@ -190,12 +192,22 @@
    "fieldtype": "Data",
    "label": "Item Name",
    "read_only": 1
+  },
+  {
+   "fieldname": "purchase_order",
+   "fieldtype": "Link",
+   "hidden": 1,
+   "label": "Purchase Order",
+   "no_copy": 1,
+   "options": "Purchase Order",
+   "print_hide": 1,
+   "read_only": 1
   }
  ],
  "idx": 1,
  "istable": 1,
  "links": [],
- "modified": "2021-05-29 17:22:14.977117",
+ "modified": "2021-06-19 19:33:04.431213",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Purchase Receipt Item Supplied",
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 0b0da5f..6a550e0 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -292,11 +292,13 @@
 				if item in self.sub_contracted_items and not item.bom:
 					frappe.throw(_("Please select BOM in BOM field for Item {0}").format(item.item_code))
 
-			if self.doctype == "Purchase Order":
-				for supplied_item in self.get("supplied_items"):
-					if not supplied_item.reserve_warehouse:
-						frappe.throw(_("Reserved Warehouse is mandatory for Item {0} in Raw Materials supplied").format(frappe.bold(supplied_item.rm_item_code)))
+			if self.doctype != "Purchase Order":
+				return
 
+			for row in self.get("supplied_items"):
+				if not row.reserve_warehouse:
+					msg = f"Reserved Warehouse is mandatory for the Item {frappe.bold(row.rm_item_code)} in Raw Materials supplied"
+					frappe.throw(_(msg))
 		else:
 			for item in self.get("items"):
 				if item.bom:
diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py
index db84162..36ae110 100644
--- a/erpnext/controllers/subcontracting.py
+++ b/erpnext/controllers/subcontracting.py
@@ -1,6 +1,7 @@
 import frappe
+import copy
 from frappe import _
-from frappe.utils import flt, cint
+from frappe.utils import flt, cint, get_link_to_form
 from collections import defaultdict
 from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
 
@@ -12,7 +13,7 @@
 		self.raw_material_table = raw_material_table
 		self.__identify_change_in_item_table()
 		self.__prepare_supplied_items()
-		self.__validate_consumed_qty()
+		self.__validate_supplied_items()
 
 	def __prepare_supplied_items(self):
 		self.initialized_fields()
@@ -24,6 +25,7 @@
 
 	def initialized_fields(self):
 		self.available_materials = frappe._dict()
+		self.__transferred_items = frappe._dict()
 		self.alternative_item_details = frappe._dict()
 		self.__get_backflush_based_on()
 
@@ -100,6 +102,7 @@
 
 			self.__set_alternative_item_details(row)
 
+		self.__transferred_items = copy.deepcopy(self.available_materials)
 		for doctype in ['Purchase Receipt', 'Purchase Invoice']:
 			self.__update_consumed_materials(doctype)
 
@@ -254,6 +257,8 @@
 
 		if self.qty_to_be_received:
 			qty = (flt(item_row.qty) * flt(transfer_item.qty)) / flt(self.qty_to_be_received.get(key, 0))
+			transfer_item.item_details.required_qty = transfer_item.qty
+
 			if (transfer_item.serial_no or frappe.get_cached_value('UOM',
 				transfer_item.item_details.stock_uom, 'must_be_whole_number')):
 				return frappe.utils.ceil(qty)
@@ -272,12 +277,15 @@
 		if self.doctype == 'Purchase Order':
 			rm_obj.required_qty = qty
 		else:
+			rm_obj.consumed_qty = 0
+			rm_obj.purchase_order = item_row.purchase_order
 			self.__set_batch_nos(bom_item, item_row, rm_obj, qty)
 
 	def __set_batch_nos(self, bom_item, item_row, rm_obj, qty):
 		key = (rm_obj.rm_item_code, item_row.item_code, item_row.purchase_order)
 
 		if (self.available_materials.get(key) and self.available_materials[key]['batch_no']):
+			new_rm_obj = None
 			for batch_no, batch_qty in self.available_materials[key]['batch_no'].items():
 				if batch_qty >= qty:
 					self.__set_batch_no_as_per_qty(item_row, rm_obj, batch_no, qty)
@@ -290,13 +298,21 @@
 					new_rm_obj.reference_name = item_row.name
 					self.__set_batch_no_as_per_qty(item_row, new_rm_obj, batch_no, batch_qty)
 					self.available_materials[key]['batch_no'][batch_no] = 0
+
+			if abs(qty) > 0 and not new_rm_obj:
+				self.__set_consumed_qty(rm_obj, qty)
 		else:
-			rm_obj.required_qty = qty
-			rm_obj.consumed_qty = qty
+			self.__set_consumed_qty(rm_obj, qty, bom_item.required_qty or qty)
 			self.__set_serial_nos(item_row, rm_obj)
 
+	def __set_consumed_qty(self, rm_obj, consumed_qty, required_qty=0):
+		rm_obj.required_qty = required_qty
+		rm_obj.consumed_qty = consumed_qty
+
 	def __set_batch_no_as_per_qty(self, item_row, rm_obj, batch_no, qty):
-		rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no, 'required_qty': qty})
+		rm_obj.update({'consumed_qty': qty, 'batch_no': batch_no,
+			'required_qty': qty, 'purchase_order': item_row.purchase_order})
+
 		self.__set_serial_nos(item_row, rm_obj)
 
 	def __set_serial_nos(self, item_row, rm_obj):
@@ -339,9 +355,39 @@
 			itemwise_consumed_qty[key] -= consumed_qty
 			frappe.db.set_value('Purchase Order Item Supplied', row.name, 'consumed_qty', consumed_qty)
 
-	def __validate_consumed_qty(self):
-		for row in self.get(self.raw_material_table):
-			if flt(row.consumed_qty) == 0.0 and row.get('serial_no'):
-				msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
+	def __validate_supplied_items(self):
+		if self.doctype not in ['Purchase Invoice', 'Purchase Receipt']:
+			return
 
-				frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
\ No newline at end of file
+		for row in self.get(self.raw_material_table):
+			self.__validate_consumed_qty(row)
+
+			key = (row.rm_item_code, row.main_item_code, row.purchase_order)
+			if not self.__transferred_items or not self.__transferred_items.get(key):
+				return
+
+			self.__validate_batch_no(row, key)
+			self.__validate_serial_no(row, key)
+
+	def __validate_consumed_qty(self, row):
+		if self.backflush_based_on != 'BOM' and flt(row.consumed_qty) == 0.0:
+			msg = f'Row {row.idx}: the consumed qty cannot be zero for the item {frappe.bold(row.rm_item_code)}'
+
+			frappe.throw(_(msg),title=_('Consumed Items Qty Check'))
+
+	def __validate_batch_no(self, row, key):
+		if row.get('batch_no') and row.get('batch_no') not in self.__transferred_items.get(key).get('batch_no'):
+			link = get_link_to_form('Purchase Order', row.purchase_order)
+			msg = f'The Batch No {frappe.bold(row.get("batch_no"))} has not supplied against the Purchase Order {link}'
+			frappe.throw(_(msg), title=_("Incorrect Batch Consumed"))
+
+	def __validate_serial_no(self, row, key):
+		if row.get('serial_no'):
+			serial_nos = get_serial_nos(row.get('serial_no'))
+			incorrect_sn = set(serial_nos).difference(self.__transferred_items.get(key).get('serial_no'))
+
+			if incorrect_sn:
+				incorrect_sn = "\n".join(incorrect_sn)
+				link = get_link_to_form('Purchase Order', row.purchase_order)
+				msg = f'The Serial Nos {incorrect_sn} has not supplied against the Purchase Order {link}'
+				frappe.throw(_(msg), title=_("Incorrect Serial Number Consumed"))
\ No newline at end of file
diff --git a/erpnext/stock/stock_ledger.py b/erpnext/stock/stock_ledger.py
index fb2ecab..9fe89c3 100644
--- a/erpnext/stock/stock_ledger.py
+++ b/erpnext/stock/stock_ledger.py
@@ -485,7 +485,7 @@
 
 		# Recalculate subcontracted item's rate in case of subcontracted purchase receipt/invoice
 		if frappe.get_cached_value(sle.voucher_type, sle.voucher_no, "is_subcontracted") == 'Yes':
-			doc = frappe.get_cached_doc(sle.voucher_type, sle.voucher_no)
+			doc = frappe.get_doc(sle.voucher_type, sle.voucher_no)
 			doc.update_valuation_rate(reset_outgoing_rate=False)
 			for d in (doc.items + doc.supplied_items):
 				d.db_update()
diff --git a/erpnext/tests/test_subcontracting.py b/erpnext/tests/test_subcontracting.py
index d2438f8..8b0ce09 100644
--- a/erpnext/tests/test_subcontracting.py
+++ b/erpnext/tests/test_subcontracting.py
@@ -395,6 +395,37 @@
 			self.assertEqual(value.qty, details.qty)
 			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))
 
+	def test_incorrect_serial_no_components_based_on_material_transfer(self):
+		'''
+			- Set backflush based on Material Transferred for Subcontract
+			- Create subcontracted PO for the item Subcontracted Item SA2.
+			- Transfer the serialized componenets to the supplier.
+			- Create purchase receipt and change the serial no which is not transferred.
+			- System should throw the error and not allowed to save the purchase receipt.
+		'''
+
+		set_backflush_based_on('Material Transferred for Subcontract')
+		item_code = 'Subcontracted Item SA2'
+		items = [{'warehouse': '_Test Warehouse - _TC', 'item_code': item_code, 'qty': 10, 'rate': 100}]
+
+		rm_items = [{'item_code': 'Subcontracted SRM Item 2', 'qty': 10}]
+
+		itemwise_details = make_stock_in_entry(rm_items=rm_items)
+		po = create_purchase_order(rm_items = items, is_subcontracted="Yes",
+			supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		for d in rm_items:
+			d['po_detail'] = po.items[0].name
+
+		make_stock_transfer_entry(po_no = po.name, main_item_code=item_code,
+			rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details))
+
+		pr1 = make_purchase_receipt(po.name)
+		pr1.save()
+		pr1.supplied_items[0].serial_no = 'ABCD'
+		self.assertRaises(frappe.ValidationError, pr1.save)
+		pr1.delete()
+
 	def test_partial_transfer_batch_based_on_material_transfer(self):
 		'''
 			- Set backflush based on Material Transferred for Subcontract