fix: purchase invoice qty change not recalculate the consumed qty and added test cases for purchase invoice
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 1907885..0b0da5f 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -58,6 +58,11 @@
 		if self.doctype in ("Purchase Receipt", "Purchase Invoice"):
 			self.update_valuation_rate()
 
+	def onload(self):
+		super(BuyingController, self).onload()
+		self.set_onload("backflush_based_on", frappe.db.get_single_value('Buying Settings',
+			'backflush_raw_materials_of_subcontract_based_on'))
+
 	def set_missing_values(self, for_validate=False):
 		super(BuyingController, self).set_missing_values(for_validate)
 
diff --git a/erpnext/controllers/subcontracting.py b/erpnext/controllers/subcontracting.py
index e81c0f5..db84162 100644
--- a/erpnext/controllers/subcontracting.py
+++ b/erpnext/controllers/subcontracting.py
@@ -40,9 +40,10 @@
 		self.purchase_orders = [d.purchase_order for d in self.items if d.purchase_order]
 
 	def __identify_change_in_item_table(self):
-		self.changed_name = []
+		self.__changed_name = []
+		self.__reference_name = []
 
-		if self.doctype == 'Purchase Order' or not self.get(self.raw_material_table):
+		if self.doctype == 'Purchase Order' or self.is_new():
 			self.set(self.raw_material_table, [])
 			return
 
@@ -51,17 +52,18 @@
 			return True
 
 		for n_row in self.items:
+			self.__reference_name.append(n_row.name)
 			if (n_row.name not in item_dict) or (n_row.item_code, n_row.qty) != item_dict[n_row.name]:
-				self.changed_name.append(n_row.name)
+				self.__changed_name.append(n_row.name)
 
 			if item_dict.get(n_row.name):
 				del item_dict[n_row.name]
 
-		self.changed_name.extend(item_dict.keys())
+		self.__changed_name.extend(item_dict.keys())
 
 	def __get_data_before_save(self):
 		item_dict = {}
-		if self.doctype == 'Purchase Receipt' and self._doc_before_save:
+		if self.doctype in ['Purchase Receipt', 'Purchase Invoice'] and self._doc_before_save:
 			for row in self._doc_before_save.get('items'):
 				item_dict[row.name] = (row.item_code, row.qty)
 
@@ -149,7 +151,7 @@
 
 	def __get_received_items(self, doctype):
 		fields = []
-		self.po_field = 'purchase_order' if doctype == 'Purchase Receipt' else 'po_detail'
+		self.po_field = 'purchase_order'
 
 		for field in ['name', self.po_field, 'parent']:
 			fields.append(f'`tab{doctype} Item`.`{field}`')
@@ -161,9 +163,9 @@
 		return frappe.get_all(f'{doctype}', fields = fields, filters = filters)
 
 	def __get_consumed_items(self, doctype, pr_items):
-		return frappe.get_all(f'{doctype} Item Supplied',
+		return frappe.get_all('Purchase Receipt Item Supplied',
 			fields = ['serial_no', 'rm_item_code', 'reference_name', 'batch_no', 'consumed_qty', 'main_item_code'],
-			filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items))})
+			filters = {'docstatus': 1, 'reference_name': ('in', list(pr_items)), 'parenttype': doctype})
 
 	def __set_alternative_item_details(self, row):
 		if row.get('original_item'):
@@ -196,13 +198,16 @@
 		return frappe.get_all('BOM', fields = fields, filters=filters, order_by = f'`tab{doctype}`.`idx`') or []
 
 	def __remove_changed_rows(self):
-		if not self.changed_name:
+		if not self.__changed_name:
 			return
 
 		i=1
 		self.set(self.raw_material_table, [])
 		for d in self._doc_before_save.supplied_items:
-			if d.reference_name in self.changed_name:
+			if d.reference_name in self.__changed_name:
+				continue
+
+			if (d.reference_name not in self.__reference_name):
 				continue
 
 			d.idx = i
@@ -215,8 +220,8 @@
 
 		has_supplied_items = True if self.get(self.raw_material_table) else False
 		for row in self.items:
-			if (self.doctype != 'Purchase Order' and ((self.changed_name and row.name not in self.changed_name)
-				or (has_supplied_items and not self.changed_name))):
+			if (self.doctype != 'Purchase Order' and ((self.__changed_name and row.name not in self.__changed_name)
+				or (has_supplied_items and not self.__changed_name))):
 				continue
 
 			if self.doctype == 'Purchase Order' or self.backflush_based_on == 'BOM':
@@ -305,16 +310,18 @@
 				self.available_materials[key]['serial_no'].remove(sn)
 
 	def set_consumed_qty_in_po(self):
+		# Update consumed qty back in the purchase order
 		if self.is_subcontracted != 'Yes':
 			return
 
 		self.__get_purchase_orders()
-		consumed_items, pr_items = self.__update_consumed_materials(self.doctype, return_consumed_items=True)
-
 		itemwise_consumed_qty = defaultdict(float)
-		for row in consumed_items:
-			key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
-			itemwise_consumed_qty[key] += row.consumed_qty
+		for doctype in ['Purchase Receipt', 'Purchase Invoice']:
+			consumed_items, pr_items = self.__update_consumed_materials(doctype, return_consumed_items=True)
+
+			for row in consumed_items:
+				key = (row.rm_item_code, row.main_item_code, pr_items.get(row.reference_name))
+				itemwise_consumed_qty[key] += row.consumed_qty
 
 		self.__update_consumed_qty_in_po(itemwise_consumed_qty)