Merge pull request #26020 from marination/subcontracted-rm-to-transfer-report-develop

fix: Report Subcontracted Raw Materials to be Transferred
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
index de2ae8f..68426ab 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/subcontracted_raw_materials_to_be_transferred.py
@@ -9,10 +9,10 @@
 	if filters.from_date >= filters.to_date:
 		frappe.msgprint(_("To Date must be greater than From Date"))
 
-	data = []
 	columns = get_columns()
-	get_data(data , filters)
-	return columns, data
+	data = get_data(filters)
+
+	return columns, data or []
 
 def get_columns():
 	return [
@@ -21,13 +21,12 @@
 			"fieldtype": "Link",
 			"fieldname": "purchase_order",
 			"options": "Purchase Order",
-			"width": 150
+			"width": 200
 		},
 		{
 			"label": _("Date"),
 			"fieldtype": "Date",
 			"fieldname": "date",
-			"hidden": 1,
 			"width": 150
 		},
 		{
@@ -41,97 +40,58 @@
 			"label": _("Item Code"),
 			"fieldtype": "Data",
 			"fieldname": "rm_item_code",
-			"width": 100
+			"width": 150
 		},
 		{
 			"label": _("Required Quantity"),
 			"fieldtype": "Float",
-			"fieldname": "r_qty",
-			"width": 100
+			"fieldname": "reqd_qty",
+			"width": 150
 		},
 		{
 			"label": _("Transferred Quantity"),
 			"fieldtype": "Float",
-			"fieldname": "t_qty",
-			"width": 100
+			"fieldname": "transferred_qty",
+			"width": 200
 		},
 		{
 			"label": _("Pending Quantity"),
 			"fieldtype": "Float",
 			"fieldname": "p_qty",
-			"width": 100
+			"width": 150
 		}
 	]
 
-def get_data(data, filters):
-	po = get_po(filters)
-	po_transferred_qty_map = frappe._dict(get_transferred_quantity([v.name for v in po]))
+def get_data(filters):
+	po_rm_item_details = get_po_items_to_supply(filters)
 
-	sub_items = get_purchase_order_item_supplied([v.name for v in po])
+	data = []
+	for row in po_rm_item_details:
+		transferred_qty = row.get("transferred_qty") or 0
+		if transferred_qty < row.get("reqd_qty", 0):
+			pending_qty = frappe.utils.flt(row.get("reqd_qty", 0) - transferred_qty)
+			row.p_qty = pending_qty if pending_qty > 0 else 0
+			data.append(row)
 
-	for order in po:
-		for item in sub_items:
-			if order.name == item.parent and order.name in po_transferred_qty_map and \
-				item.required_qty != po_transferred_qty_map.get(order.name).get(item.rm_item_code):
-				transferred_qty = po_transferred_qty_map.get(order.name).get(item.rm_item_code) \
-				if po_transferred_qty_map.get(order.name).get(item.rm_item_code) else 0
-				row ={
-					'purchase_order': item.parent,
-					'date': order.transaction_date,
-					'supplier': order.supplier,
-					'rm_item_code': item.rm_item_code,
-					'r_qty': item.required_qty,
-					't_qty':transferred_qty,
-					'p_qty':item.required_qty - transferred_qty
-				}
+	return data
 
-				data.append(row)
-
-	return(data)
-
-def get_po(filters):
-	record_filters = [
-			["is_subcontracted", "=", "Yes"],
-			["supplier", "=", filters.supplier],
-			["transaction_date", "<=", filters.to_date],
-			["transaction_date", ">=", filters.from_date],
-			["docstatus", "=", 1]
-		]
-	return frappe.get_all("Purchase Order", filters=record_filters, fields=["name", "transaction_date", "supplier"])
-
-def get_transferred_quantity(po_name):
-	stock_entries = get_stock_entry(po_name)
-	stock_entries_detail = get_stock_entry_detail([v.name for v in stock_entries])
-	po_transferred_qty_map = {}
-
-
-	for entry in stock_entries:
-		for details in stock_entries_detail:
-			if details.parent == entry.name:
-				details["Purchase_order"] = entry.purchase_order
-				if entry.purchase_order not in po_transferred_qty_map:
-					po_transferred_qty_map[entry.purchase_order] = {}
-					po_transferred_qty_map[entry.purchase_order][details.item_code] = details.qty
-				else:
-					po_transferred_qty_map[entry.purchase_order][details.item_code] = po_transferred_qty_map[entry.purchase_order].get(details.item_code, 0) + details.qty
-
-	return po_transferred_qty_map
-
-
-def get_stock_entry(po):
-	return frappe.get_all("Stock Entry", filters=[
-			('purchase_order', 'IN', po),
-			('stock_entry_type', '=', 'Send to Subcontractor'),
-			('docstatus', '=', 1)
-	], fields=["name", "purchase_order"])
-
-def get_stock_entry_detail(se):
-	return frappe.get_all("Stock Entry Detail", filters=[
-			["parent", "in", se]
+def get_po_items_to_supply(filters):
+	return frappe.db.get_all(
+		"Purchase Order",
+		fields=[
+			"name as purchase_order",
+			"transaction_date as date",
+			"supplier as supplier",
+			"`tabPurchase Order Item Supplied`.rm_item_code as rm_item_code",
+			"`tabPurchase Order Item Supplied`.required_qty as reqd_qty",
+			"`tabPurchase Order Item Supplied`.supplied_qty as transferred_qty"
 		],
-		fields=["parent", "item_code", "qty"])
-
-def get_purchase_order_item_supplied(po):
-	return frappe.get_all("Purchase Order Item Supplied", filters=[
-			('parent', 'IN', po)
-	], fields=['parent', 'rm_item_code', 'required_qty'])
+		filters = [
+			["Purchase Order", "per_received", "<", "100"],
+			["Purchase Order", "is_subcontracted", "=", "Yes"],
+			["Purchase Order", "supplier", "=", filters.supplier],
+			["Purchase Order", "transaction_date", "<=", filters.to_date],
+			["Purchase Order", "transaction_date", ">=", filters.from_date],
+			["Purchase Order", "docstatus", "=", 1]
+		]
+	)
\ No newline at end of file
diff --git a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
index c1fc6fb..2448e17 100644
--- a/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
+++ b/erpnext/buying/report/subcontracted_raw_materials_to_be_transferred/test_subcontracted_raw_materials_to_be_transferred.py
@@ -12,34 +12,80 @@
 class TestSubcontractedItemToBeTransferred(unittest.TestCase):
 
 	def test_pending_and_transferred_qty(self):
-		po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes')
+		po = create_purchase_order(item_code='_Test FG Item', is_subcontracted='Yes', supplier_warehouse="_Test Warehouse 1 - _TC")
+
+		# Material Receipt of RMs
 		make_stock_entry(item_code='_Test Item', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
 		make_stock_entry(item_code='_Test Item Home Desktop 100', target='_Test Warehouse - _TC', qty=100, basic_rate=100)
-		transfer_subcontracted_raw_materials(po.name)
-		col, data = execute(filters=frappe._dict({'supplier': po.supplier,
-		   'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
-		   'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))}))
-		self.assertEqual(data[0]['purchase_order'], po.name)
-		self.assertIn(data[0]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
-		self.assertIn(data[0]['p_qty'], [9, 18])
-		self.assertIn(data[0]['t_qty'], [1, 2])
 
-		self.assertEqual(data[1]['purchase_order'], po.name)
-		self.assertIn(data[1]['rm_item_code'], ['_Test Item', '_Test Item Home Desktop 100'])
-		self.assertIn(data[1]['p_qty'], [9, 18])
-		self.assertIn(data[1]['t_qty'], [1, 2])
+		se = transfer_subcontracted_raw_materials(po)
 
+		col, data = execute(filters=frappe._dict(
+			{
+				'supplier': po.supplier,
+				'from_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=-10)),
+				'to_date': frappe.utils.get_datetime(frappe.utils.add_to_date(po.transaction_date, days=10))
+			}
+		))
+		po.reload()
+
+		po_data = [row for row in data if row.get('purchase_order') == po.name]
+		# Alphabetically sort to be certain of order
+		po_data = sorted(po_data, key = lambda i: i['rm_item_code'])
+
+		self.assertEqual(len(po_data), 2)
+		self.assertEqual(po_data[0]['purchase_order'], po.name)
+
+		self.assertEqual(po_data[0]['rm_item_code'], '_Test Item')
+		self.assertEqual(po_data[0]['p_qty'], 8)
+		self.assertEqual(po_data[0]['transferred_qty'], 2)
+
+		self.assertEqual(po_data[1]['rm_item_code'], '_Test Item Home Desktop 100')
+		self.assertEqual(po_data[1]['p_qty'], 19)
+		self.assertEqual(po_data[1]['transferred_qty'], 1)
+
+		se.cancel()
+		po.cancel()
 
 def transfer_subcontracted_raw_materials(po):
+	# Order of supplied items fetched in PO is flaky
+	transfer_qty_map = {
+		'_Test Item': 2,
+		'_Test Item Home Desktop 100': 1
+	}
+
+	item_1 = po.supplied_items[0].rm_item_code
+	item_2 = po.supplied_items[1].rm_item_code
+
 	rm_item = [
-	 {'item_code': '_Test Item', 'rm_item_code': '_Test Item', 'item_name': '_Test Item', 'qty': 1,
-		'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 100, 'stock_uom': 'Nos'},
-	 {'item_code': '_Test Item Home Desktop 100', 'rm_item_code': '_Test Item Home Desktop 100', 'item_name': '_Test Item Home Desktop 100', 'qty': 2,
-		'warehouse': '_Test Warehouse - _TC', 'rate': 100, 'amount': 200, 'stock_uom': 'Nos'}]
+		{
+			'name': po.supplied_items[0].name,
+			'item_code': item_1,
+			'rm_item_code': item_1,
+			'item_name': item_1,
+			'qty': transfer_qty_map[item_1],
+			'warehouse': '_Test Warehouse - _TC',
+			'rate': 100,
+			'amount': 100 * transfer_qty_map[item_1],
+			'stock_uom': 'Nos'
+		},
+		{
+			'name': po.supplied_items[1].name,
+			'item_code': item_2,
+			'rm_item_code': item_2,
+			'item_name': item_2,
+			'qty': transfer_qty_map[item_2],
+			'warehouse': '_Test Warehouse - _TC',
+			'rate': 100,
+			'amount': 100 * transfer_qty_map[item_2],
+			'stock_uom': 'Nos'
+		}
+	]
 	rm_item_string = json.dumps(rm_item)
-	se = frappe.get_doc(make_rm_stock_entry(po, rm_item_string))
-	se.from_warehouse = '_Test Warehouse 1 - _TC'
-	se.to_warehouse = '_Test Warehouse 1 - _TC'
+	se = frappe.get_doc(make_rm_stock_entry(po.name, rm_item_string))
+	se.from_warehouse = '_Test Warehouse - _TC'
+	se.to_warehouse = '_Test Warehouse - _TC'
 	se.stock_entry_type = 'Send to Subcontractor'
 	se.save()
 	se.submit()
+	return se