# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# See license.txt


import copy

import frappe
from frappe.utils import add_days, cint, flt, nowtime, today

import erpnext
from erpnext.accounts.doctype.account.test_account import get_inventory_account
from erpnext.accounts.utils import get_company_default
from erpnext.controllers.sales_and_purchase_return import make_return_doc
from erpnext.controllers.tests.test_subcontracting_controller import (
	get_rm_items,
	get_subcontracting_order,
	make_bom_for_subcontracted_items,
	make_raw_materials,
	make_service_items,
	make_stock_in_entry,
	make_stock_transfer_entry,
	make_subcontracted_item,
	make_subcontracted_items,
	set_backflush_based_on,
)
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.projects.doctype.project.test_project import make_project
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
	get_batch_from_bundle,
	make_serial_batch_bundle,
)
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.stock.doctype.stock_reconciliation.test_stock_reconciliation import (
	create_stock_reconciliation,
)
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
	make_subcontracting_receipt,
)
from erpnext.subcontracting.doctype.subcontracting_order.test_subcontracting_order import (
	make_subcontracted_purchase_order,
)
from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
	BOMQuantityError,
)
from erpnext.tests.utils import ERPNextTestSuite


class TestSubcontractingReceipt(ERPNextTestSuite):
	def setUp(self):
		make_subcontracted_items()
		make_raw_materials()
		make_service_items()
		make_bom_for_subcontracted_items()

	def test_project_is_carried_over_from_subcontracting_order(self):
		project = make_project({"project_name": "_Test SCR Project"}).name
		po = make_subcontracted_purchase_order(project)
		sco = get_subcontracting_order(po_name=po.name)

		scr = make_subcontracting_receipt(sco.name)

		self.assertEqual(scr.project, project)
		self.assertEqual(scr.items[0].project, project)

	def test_project_cannot_differ_from_subcontracting_order(self):
		project = make_project({"project_name": "_Test SCR Project"}).name
		other_project = make_project({"project_name": "_Test SCR Project 2"}).name
		po = make_subcontracted_purchase_order(project)
		sco = get_subcontracting_order(po_name=po.name)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].project = other_project
		self.assertRaises(frappe.ValidationError, scr.save)

	def test_supplied_items_ledger_takes_project_of_finished_good_row(self):
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		project = make_project({"project_name": "_Test SCR Project"}).name
		po = create_purchase_order(
			rm_items=[
				{
					"warehouse": "_Test Warehouse - _TC",
					"item_code": "Subcontracted Service Item 7",
					"qty": 10,
					"rate": 100,
					"fg_item": "Subcontracted Item SA7",
					"fg_item_qty": 10,
				},
			],
			is_subcontracted=1,
			supplier_warehouse="_Test Warehouse 1 - _TC",
			do_not_submit=1,
		)
		po.items[0].project = project
		po.submit()
		sco = get_subcontracting_order(po_name=po.name)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.submit()

		self.assertFalse(scr.project)
		self.assertEqual(scr.items[0].project, project)
		supplied_item_projects = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_no": scr.name, "warehouse": scr.supplier_warehouse},
			pluck="project",
		)
		self.assertTrue(supplied_item_projects)
		self.assertEqual(set(supplied_item_projects), {project})

	def test_subcontracting(self):
		set_backflush_based_on("BOM")
		make_stock_entry(item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)
		make_stock_entry(
			item_code="_Test Item Home Desktop 100",
			qty=100,
			target="_Test Warehouse 1 - _TC",
			basic_rate=100,
		)
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": "_Test FG Item",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()
		rm_supp_cost = sum(item.amount for item in scr.get("supplied_items"))
		self.assertEqual(scr.get("items")[0].rm_supp_cost, flt(rm_supp_cost))

	def test_available_qty_for_consumption(self):
		set_backflush_based_on("BOM")
		make_stock_entry(item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)
		make_stock_entry(
			item_code="_Test Item Home Desktop 100",
			qty=100,
			target="_Test Warehouse 1 - _TC",
			basic_rate=100,
		)
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": "_Test FG Item",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = [
			{
				"main_item_code": "_Test FG Item",
				"item_code": "_Test Item",
				"qty": 5.0,
				"rate": 100.0,
				"stock_uom": "_Test UOM",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "_Test FG Item",
				"item_code": "_Test Item Home Desktop 100",
				"qty": 10.0,
				"rate": 100.0,
				"stock_uom": "_Test UOM",
				"warehouse": "_Test Warehouse - _TC",
			},
		]
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()

	def test_subcontracting_gle_fg_item_rate_zero(self):
		from erpnext.stock.doctype.purchase_receipt.test_purchase_receipt import get_gl_entries

		set_backflush_based_on("BOM")
		make_stock_entry(
			item_code="_Test Item",
			target="Work In Progress - TCP1",
			qty=100,
			basic_rate=100,
			company="_Test Company with perpetual inventory",
		)
		make_stock_entry(
			item_code="_Test Item Home Desktop 100",
			target="Work In Progress - TCP1",
			qty=100,
			basic_rate=100,
			company="_Test Company with perpetual inventory",
		)
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 0,
				"fg_item": "_Test FG Item",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()

		gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
		self.assertFalse(gl_entries)

	def test_subcontracting_over_receipt(self):
		"""
		Behaviour: Raise multiple SCRs against one SCO that in total
		                receive more than the required qty in the SCO.
		Expected Result: Error Raised for Over Receipt against SCO.
		"""
		from erpnext.controllers.subcontracting_controller import (
			make_rm_stock_entry as make_subcontract_transfer_entry,
		)
		from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
			make_subcontracting_receipt,
		)

		set_backflush_based_on("Material Transferred for Subcontract")
		item_code = "_Test Subcontracted FG Item 1"
		make_subcontracted_item(item_code=item_code)
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": "_Test Subcontracted FG Item 1",
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(
			service_items=service_items,
			include_exploded_items=0,
		)

		# stock raw materials in a warehouse before transfer
		make_stock_entry(
			target="_Test Warehouse - _TC", item_code="_Test Extra Item 1", qty=10, basic_rate=100
		)
		make_stock_entry(target="_Test Warehouse - _TC", item_code="_Test FG Item", qty=1, basic_rate=100)
		make_stock_entry(target="_Test Warehouse - _TC", item_code="Test Extra Item 2", qty=1, basic_rate=100)

		rm_items = [
			{
				"item_code": item_code,
				"rm_item_code": sco.supplied_items[0].rm_item_code,
				"item_name": "_Test FG Item",
				"qty": sco.supplied_items[0].required_qty,
				"warehouse": "_Test Warehouse - _TC",
				"stock_uom": "Nos",
			},
			{
				"item_code": item_code,
				"rm_item_code": sco.supplied_items[1].rm_item_code,
				"item_name": "Test Extra Item 1",
				"qty": sco.supplied_items[1].required_qty,
				"warehouse": "_Test Warehouse - _TC",
				"stock_uom": "Nos",
			},
		]
		ste = frappe.get_doc(make_subcontract_transfer_entry(sco.name, rm_items))
		ste.to_warehouse = "_Test Warehouse 1 - _TC"
		ste.save()
		ste.submit()

		scr1 = make_subcontracting_receipt(sco.name)
		scr2 = make_subcontracting_receipt(sco.name)

		scr1.submit()
		self.assertRaises(frappe.ValidationError, scr2.submit)

	def test_subcontracting_receipt_partial_return(self):
		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr1 = make_subcontracting_receipt(sco.name)
		scr1.save()
		scr1.submit()

		scr1_return = make_return_subcontracting_receipt(scr_name=scr1.name, qty=-3)
		scr1.load_from_db()
		self.assertEqual(scr1_return.status, "Return")
		self.assertIsNotNone(scr1_return.items[0].bom)
		self.assertEqual(scr1.items[0].returned_qty, 3)

		scr2_return = make_return_subcontracting_receipt(scr_name=scr1.name, qty=-7)
		scr1.load_from_db()
		self.assertEqual(scr2_return.status, "Return")
		self.assertIsNotNone(scr2_return.items[0].bom)
		self.assertEqual(scr1.status, "Return Issued")
		self.assertEqual(scr1.items[0].returned_qty, 10)

	@ERPNextTestSuite.change_settings("Stock Settings", {"use_serial_batch_fields": 1})
	@ERPNextTestSuite.change_settings("Buying Settings", {"set_valuation_rate_for_rejected_materials": 0})
	def test_batch_return_uses_accepted_receipt_rate(self):
		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse

		fg_item = make_item(
			properties={"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1}
		).name
		make_bom(item=fg_item, raw_materials=[make_item(properties={"is_stock_item": 1}).name])
		batch_no = (
			frappe.get_doc({"doctype": "Batch", "batch_id": frappe.generate_hash(length=10), "item": fg_item})
			.insert()
			.name
		)
		frappe.db.set_value("Batch", batch_no, "use_batchwise_valuation", 0)
		sco = get_subcontracting_order(
			service_items=[
				{
					"warehouse": "_Test Warehouse - _TC",
					"item_code": "Subcontracted Service Item 1",
					"qty": 10,
					"rate": 50,
					"fg_item": fg_item,
					"fg_item_qty": 10,
				}
			]
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name, rm_items=rm_items, itemwise_details=copy.deepcopy(itemwise_details)
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].batch_no = batch_no
		scr.items[0].qty = 8
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = create_warehouse("_Test SCR Batch Return Rejected Warehouse")
		scr.submit()

		scr_return = make_return_subcontracting_receipt(scr_name=scr.name, qty=-4)

		sle = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_no": scr_return.name, "item_code": fg_item, "is_cancelled": 0},
			["stock_value_difference", "serial_and_batch_bundle"],
			as_dict=True,
		)
		bundle_value = frappe.db.get_value(
			"Serial and Batch Bundle", sle.serial_and_batch_bundle, "total_amount"
		)
		self.assertEqual(sle.stock_value_difference, -4 * scr.items[0].rate)
		self.assertEqual(bundle_value, sle.stock_value_difference)

	def test_subcontracting_receipt_over_return(self):
		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr1 = make_subcontracting_receipt(sco.name)
		scr1.save()
		scr1.submit()

		from erpnext.controllers.status_updater import OverAllowanceError

		args = frappe._dict(scr_name=scr1.name, qty=-15)
		self.assertRaises(OverAllowanceError, make_return_subcontracting_receipt, **args)

	def test_subcontracting_receipt_no_gl_entry(self):
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.append(
			"additional_costs",
			{
				"expense_account": "Expenses Included In Valuation - _TC",
				"description": "Test Additional Costs",
				"amount": 100,
			},
		)
		scr.save()
		scr.submit()

		stock_value_difference = frappe.db.get_value(
			"Stock Ledger Entry",
			{
				"voucher_type": "Subcontracting Receipt",
				"voucher_no": scr.name,
				"item_code": "Subcontracted Item SA7",
				"warehouse": "_Test Warehouse - _TC",
			},
			"stock_value_difference",
		)

		# Service Cost(100 * 10) + Raw Materials Cost(100 * 10) + Additional Costs(10 * 10) = 2100
		self.assertEqual(stock_value_difference, 2100)
		self.assertFalse(get_gl_entries("Subcontracting Receipt", scr.name))
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_subcontracting_receipt_gl_entry(self):
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		additional_costs_expense_account = "Expenses Included In Valuation - TCP1"
		scr.append(
			"additional_costs",
			{
				"expense_account": additional_costs_expense_account,
				"description": "Test Additional Costs",
				"amount": 100,
				"base_amount": 100,
			},
		)
		scr.save()
		scr.submit()

		self.assertEqual(cint(erpnext.is_perpetual_inventory_enabled(scr.company)), 1)

		gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
		self.assertTrue(gl_entries)

		fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
		expense_account = scr.items[0].expense_account
		expected_values = {
			fg_warehouse_ac: [2100.0, 1000],
			expense_account: [1100, 2100],
			additional_costs_expense_account: [0.0, 100.0],
		}

		for gle in gl_entries:
			self.assertEqual(expected_values[gle.account][0], gle.debit)
			self.assertEqual(expected_values[gle.account][1], gle.credit)

		scr.reload()
		scr.cancel()
		self.assertTrue(get_gl_entries("Subcontracting Receipt", scr.name))
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_return_takes_back_additional_costs(self):
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		additional_costs_account = "Expenses Included In Valuation - TCP1"
		scr = make_subcontracting_receipt(sco.name)
		scr.append(
			"additional_costs",
			{
				"expense_account": additional_costs_account,
				"description": "Test Additional Costs",
				"amount": 100,
				"base_amount": 100,
			},
		)
		scr.save()
		scr.submit()

		scr_return = make_return_subcontracting_receipt(
			scr_name=scr.name, qty=-4, supplier_warehouse=scr.supplier_warehouse
		)

		additional_costs = sum(
			gle.debit - gle.credit
			for gle in get_gl_entries("Subcontracting Receipt", scr_return.name)
			if gle.account == additional_costs_account
		)
		self.assertEqual(additional_costs, 40)

	def test_return_takes_back_additional_costs_that_net_to_zero(self):
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		for description, amount in (("Freight", 100), ("Rebate", -100)):
			scr.append(
				"additional_costs",
				{
					"expense_account": "Expenses Included In Valuation - TCP1",
					"description": description,
					"amount": amount,
					"base_amount": amount,
				},
			)
		scr.save()
		scr.submit()

		scr_return = make_return_subcontracting_receipt(
			scr_name=scr.name, supplier_warehouse=scr.supplier_warehouse
		)

		self.assertEqual([row.amount for row in scr_return.additional_costs], [-100, 100])

	def test_return_takes_back_additional_costs_allocated_by_amount(self):
		service_items = [
			{
				"warehouse": "Stores - TCP1",
				"item_code": service_item,
				"qty": 5,
				"rate": rate,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			}
			for service_item, fg_item, rate in (
				("Subcontracted Service Item 7", "Subcontracted Item SA7", 100),
				("Subcontracted Service Item 1", "Subcontracted Item SA4", 300),
			)
		]
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			service_items=service_items,
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		additional_costs_account = "Expenses Included In Valuation - TCP1"
		scr = make_subcontracting_receipt(sco.name)
		scr.distribute_additional_costs_based_on = "Amount"
		scr.append(
			"additional_costs",
			{
				"expense_account": additional_costs_account,
				"description": "Test Additional Costs",
				"amount": 100,
				"base_amount": 100,
			},
		)
		scr.save()
		scr.submit()

		scr_return = make_return_doc("Subcontracting Receipt", scr.name)
		scr_return.items = scr_return.items[:1]
		scr_return.save()
		scr_return.submit()

		additional_costs = sum(
			gle.debit - gle.credit
			for gle in get_gl_entries("Subcontracting Receipt", scr_return.name)
			if gle.account == additional_costs_account
		)
		self.assertEqual(additional_costs, flt(scr.items[0].additional_cost_per_qty * 5, 2))

	def make_amount_distributed_receipt_with_rejected_qty(self):
		service_items = [
			{
				"warehouse": "Stores - TCP1",
				"item_code": service_item,
				"qty": 5,
				"rate": rate,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			}
			for service_item, fg_item, rate in (
				("Subcontracted Service Item 7", "Subcontracted Item SA7", 100),
				("Subcontracted Service Item 8", "Subcontracted Item SA8", 300),
			)
		]
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			service_items=service_items,
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.distribute_additional_costs_based_on = "Amount"
		scr.items[0].qty = 3
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.append(
			"additional_costs",
			{
				"expense_account": "Expenses Included In Valuation - TCP1",
				"description": "Test Additional Costs",
				"amount": 3000,
				"base_amount": 3000,
			},
		)
		scr.save()
		scr.submit()
		return scr

	def test_partial_return_of_amount_distributed_receipt_balances_the_ledger(self):
		scr = self.make_amount_distributed_receipt_with_rejected_qty()

		scr_return = make_return_doc("Subcontracting Receipt", scr.name)
		scr_return.items = [scr_return.items[1]]
		scr_return.save()
		scr_return.submit()

		original_row = scr.items[1]
		self.assertEqual(
			flt(scr_return.total_additional_costs, 2),
			flt(-1 * original_row.qty * original_row.additional_cost_per_qty, 2),
		)

	def test_multi_row_return_keeps_original_additional_cost_per_qty(self):
		scr = self.make_amount_distributed_receipt_with_rejected_qty()

		scr_return = make_return_doc("Subcontracting Receipt", scr.name)
		scr_return.save()
		costs_after_save = [row.additional_cost_per_qty for row in scr_return.items]
		scr_return.get_secondary_items(recalculate_rate=True)

		for return_row, original_row, cost_after_save in zip(
			scr_return.items, scr.items, costs_after_save, strict=True
		):
			cost_per_accepted_qty = (
				original_row.additional_cost_per_qty * original_row.received_qty / original_row.qty
			)
			self.assertEqual(flt(cost_after_save, 2), flt(cost_per_accepted_qty, 2))
			self.assertEqual(flt(return_row.additional_cost_per_qty, 2), flt(cost_per_accepted_qty, 2))

	def test_return_takes_back_additional_costs_of_rows_with_rejected_qty(self):
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		additional_costs_account = "Expenses Included In Valuation - TCP1"
		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 8
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.append(
			"additional_costs",
			{
				"expense_account": additional_costs_account,
				"description": "Test Additional Costs",
				"amount": 100,
				"base_amount": 100,
			},
		)
		scr.save()
		scr.submit()

		scr_return = make_return_subcontracting_receipt(
			scr_name=scr.name, qty=-8, supplier_warehouse=scr.supplier_warehouse
		)

		additional_costs = sum(
			gle.debit - gle.credit
			for gle in get_gl_entries("Subcontracting Receipt", scr_return.name)
			if gle.account == additional_costs_account
		)
		self.assertEqual(additional_costs, 100)

	def test_rejected_only_row_posts_raw_material_gl_entry(self):
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 0
		scr.items[0].rejected_qty = 10
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.save()
		scr.submit()

		consumed_value = sum(
			frappe.get_all(
				"Stock Ledger Entry",
				filters={"voucher_no": scr.name, "warehouse": scr.supplier_warehouse},
				pluck="stock_value_difference",
			)
		)
		supplier_warehouse_account = get_inventory_account(scr.company, scr.supplier_warehouse)
		gl_value = sum(
			gle.debit - gle.credit
			for gle in get_gl_entries("Subcontracting Receipt", scr.name)
			if gle.account == supplier_warehouse_account
		)

		self.assertEqual(consumed_value, -1000)
		self.assertEqual(gl_value, consumed_value)

	def test_additional_and_landed_costs_with_rejected_qty(self):
		from erpnext.stock.doctype.landed_cost_voucher.test_landed_cost_voucher import (
			make_landed_cost_voucher,
		)

		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 8
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.append(
			"additional_costs",
			{
				"expense_account": "Expenses Included In Valuation - TCP1",
				"description": "Test Additional Costs",
				"amount": 100,
				"base_amount": 100,
			},
		)
		scr.save()
		scr.submit()

		sle_filters = {"voucher_no": scr.name, "warehouse": "Stores - TCP1", "is_cancelled": 0}
		self.assertEqual(
			frappe.db.get_value("Stock Ledger Entry", sle_filters, "stock_value_difference"), 2100
		)

		make_landed_cost_voucher(
			company=scr.company,
			receipt_document_type="Subcontracting Receipt",
			receipt_document=scr.name,
			charges=100,
			distribute_charges_based_on="Qty",
			expense_account="Expenses Included In Valuation - TCP1",
		)
		self.assertEqual(
			frappe.db.get_value("Stock Ledger Entry", sle_filters, "stock_value_difference"), 2200
		)

	def test_additional_costs_without_accepted_qty(self):
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 0
		scr.items[0].rejected_qty = 10
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.append(
			"additional_costs",
			{
				"expense_account": "Expenses Included In Valuation - TCP1",
				"description": "Test Additional Costs",
				"amount": 100,
				"base_amount": 100,
			},
		)
		scr.save()
		scr.submit()

		expense = sum(
			gle.debit - gle.credit
			for gle in get_gl_entries("Subcontracting Receipt", scr.name)
			if gle.account == scr.items[0].expense_account
		)
		self.assertEqual(expense, 1100)

	def test_full_return_of_receipt_with_process_loss_returns_the_accepted_qty(self):
		set_backflush_based_on("BOM")
		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 8
		scr.items[0].process_loss_qty = 2
		scr.save()
		scr.submit()

		scr_return = make_return_subcontracting_receipt(scr_name=scr.name, qty=-8)

		self.assertEqual(scr_return.items[0].process_loss_qty, 0)
		self.assertEqual(scr_return.items[0].received_qty, -8)

	def test_supplied_items_follow_changed_accepted_qty(self):
		set_backflush_based_on("BOM")
		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 5
		scr.save()

		self.assertEqual(scr.items[0].received_qty, 5)
		self.assertEqual(
			sum(row.consumed_qty for row in scr.supplied_items), sum(row["qty"] for row in rm_items) / 2
		)

	def test_additional_cost_without_base_amount(self):
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.append(
			"additional_costs",
			{
				"expense_account": "Expenses Included In Valuation - TCP1",
				"description": "Test Additional Costs",
				"amount": 100,
			},
		)
		scr.save()
		scr.submit()

		self.assertEqual(scr.additional_costs[0].base_amount, 100)
		self.assertEqual(
			sum(
				gle.credit
				for gle in get_gl_entries("Subcontracting Receipt", scr.name)
				if gle.account == "Expenses Included In Valuation - TCP1"
			),
			100,
		)

	def make_receipt_with_taxed_freight(self, cost_centers, freight_rows=1):
		"""Subcontracting Receipt of one order per cost center, each with freight and a tax on the first row."""
		set_backflush_based_on("BOM")
		orders = [get_subcontracting_order() for _ in cost_centers]
		for idx, (sco, cost_center) in enumerate(zip(orders, cost_centers, strict=True)):
			freights = [
				{
					"idx": row + 1,
					"charge_type": "Actual",
					"account_head": "_Test Account Shipping Charges - _TC",
					"description": f"Freight {idx}",
					"tax_amount": 10 ** (row + 1) * (idx + 1),
				}
				for row in range(freight_rows)
			]
			for charge in (
				*freights,
				{
					"idx": freight_rows + 1,
					"charge_type": "On Previous Row Amount",
					"row_id": "1",
					"rate": 10,
					"account_head": "_Test Account Excise Duty - _TC",
					"description": f"Freight Tax {idx}",
				},
			):
				frappe.get_doc(
					{
						"doctype": "Purchase Taxes and Charges",
						"parent": sco.purchase_order,
						"parenttype": "Purchase Order",
						"parentfield": "taxes",
						"category": "Total",
						"add_deduct_tax": "Add",
						"cost_center": cost_center,
						**charge,
					}
				).db_insert()
			rm_items = get_rm_items(sco.supplied_items)
			itemwise_details = make_stock_in_entry(rm_items=rm_items)
			make_stock_transfer_entry(
				sco_no=sco.name,
				rm_items=rm_items,
				itemwise_details=copy.deepcopy(itemwise_details),
			)

		scr = make_subcontracting_receipt(orders[0].name)
		return make_subcontracting_receipt(orders[1].name, scr)

	def test_purchase_receipt_keeps_charges_of_every_order(self):
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_purchase_receipt,
		)

		scr = self.make_receipt_with_taxed_freight(["Main - _TC", "Main - _TC"])
		scr.submit()

		pr = make_purchase_receipt(scr.name)
		self.assertEqual(
			[(t.description, t.tax_amount) for t in pr.taxes], [("Freight 0", 30), ("Freight Tax 0", 3)]
		)

	def test_purchase_receipt_merges_repeated_charges_by_position(self):
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_purchase_receipt,
		)

		scr = self.make_receipt_with_taxed_freight(["Main - _TC", "Main - _TC"], freight_rows=2)
		scr.submit()

		pr = make_purchase_receipt(scr.name)
		self.assertEqual(
			[t.tax_amount for t in pr.taxes],
			[30, 300, 3],
		)

	def test_return_of_accepted_qty_values_goods_at_receipt_rate(self):
		service_items = [
			{
				"warehouse": "Stores - TCP1",
				"item_code": "Subcontracted Service Item 7",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA7",
				"fg_item_qty": 5,
			}
		]
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			service_items=service_items,
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 3
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.save()
		scr.submit()

		combined_return = make_return_doc("Subcontracting Receipt", scr.name)
		combined_return.items[0].rejected_qty = -2
		combined_return.items[0].rejected_warehouse = "Finished Goods - TCP1"
		combined_return.save()
		self.assertLessEqual(abs(combined_return.items[0].amount), scr.items[0].amount)

		scr_return = make_return_doc("Subcontracting Receipt", scr.name)
		scr_return.save()
		scr_return.submit()

		self.assertEqual(flt(scr_return.items[0].amount, 2), flt(-1 * scr.items[0].amount, 2))
		stock_adjustment_account = frappe.get_cached_value("Company", scr.company, "stock_adjustment_account")
		self.assertFalse(
			[
				gle
				for gle in get_gl_entries("Subcontracting Receipt", scr_return.name)
				if gle.account == stock_adjustment_account
			]
		)

	@ERPNextTestSuite.change_settings(
		"Buying Settings",
		{
			"backflush_raw_materials_of_subcontract_based_on": "Material Transferred for Subcontract",
			"validate_consumed_qty": 0,
		},
	)
	def test_combined_accepted_and_rejected_return_with_additional_costs_balances_the_ledger(self):
		service_items = [
			{
				"warehouse": "Stores - TCP1",
				"item_code": "Subcontracted Service Item 7",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA7",
				"fg_item_qty": 5,
			}
		]
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			service_items=service_items,
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 3
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.append(
			"additional_costs",
			{
				"expense_account": "Expenses Included In Valuation - TCP1",
				"description": "Test Additional Costs",
				"amount": 100,
				"base_amount": 100,
			},
		)
		scr.save()
		scr.submit()

		scr_return = make_return_doc("Subcontracting Receipt", scr.name)
		scr_return.items[0].rejected_qty = -2
		scr_return.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr_return.save()
		scr_return.submit()

		self.assertEqual(flt(scr_return.total_additional_costs, 2), -100)

	def test_receipt_refuses_orders_whose_taxed_freight_cannot_merge(self):
		scr = self.make_receipt_with_taxed_freight(["Main - _TC", "_Test Cost Center - _TC"])

		self.assertRaisesRegex(frappe.ValidationError, "Make one Subcontracting Receipt for each", scr.save)

	def make_receipt_of_two_orders(
		self, taxes=None, discounts=None, cost_centers=("Main - _TC", "Main - _TC"), currencies=None
	):
		"""Subcontracting Receipt of two orders; taxes and discounts are keyed by the order's position."""
		set_backflush_based_on("BOM")
		orders = [get_subcontracting_order(), get_subcontracting_order()]
		for idx, sco in enumerate(orders):
			for tax_idx, rate in enumerate((taxes or {}).get(idx, []), 1):
				frappe.get_doc(
					{
						"doctype": "Purchase Taxes and Charges",
						"parent": sco.purchase_order,
						"parenttype": "Purchase Order",
						"parentfield": "taxes",
						"idx": tax_idx,
						"category": "Total",
						"add_deduct_tax": "Add",
						"charge_type": "On Net Total",
						"account_head": "_Test Account Excise Duty - _TC",
						"cost_center": cost_centers[idx],
						"description": f"Excise {rate}",
						"rate": rate,
					}
				).db_insert()
			if currency := (currencies or {}).get(idx):
				frappe.db.set_value("Purchase Order", sco.purchase_order, "currency", currency)
			if discount := (discounts or {}).get(idx):
				frappe.db.set_value(
					"Purchase Order",
					sco.purchase_order,
					{"apply_discount_on": "Net Total", "additional_discount_percentage": discount},
				)
			rm_items = get_rm_items(sco.supplied_items)
			itemwise_details = make_stock_in_entry(rm_items=rm_items)
			make_stock_transfer_entry(
				sco_no=sco.name,
				rm_items=rm_items,
				itemwise_details=copy.deepcopy(itemwise_details),
			)

		scr = make_subcontracting_receipt(orders[0].name)
		return make_subcontracting_receipt(orders[1].name, scr)

	def test_receipt_refuses_orders_with_different_taxes(self):
		scr = self.make_receipt_of_two_orders(taxes={1: [10]})

		self.assertRaisesRegex(frappe.ValidationError, "Make one Subcontracting Receipt for each", scr.save)

	def test_receipt_refuses_orders_with_different_discounts(self):
		scr = self.make_receipt_of_two_orders(discounts={0: 10})

		self.assertRaisesRegex(frappe.ValidationError, "Make one Subcontracting Receipt for each", scr.save)

	def test_receipt_refuses_orders_with_taxes_in_different_cost_centers(self):
		scr = self.make_receipt_of_two_orders(
			taxes={0: [10], 1: [10]}, cost_centers=("Main - _TC", "_Test Cost Center - _TC")
		)

		self.assertRaisesRegex(frappe.ValidationError, "Make one Subcontracting Receipt for each", scr.save)

	def test_receipt_refuses_orders_in_different_currencies(self):
		scr = self.make_receipt_of_two_orders(currencies={1: "USD"})

		self.assertRaisesRegex(frappe.ValidationError, "Make one Subcontracting Receipt for each", scr.save)

	def test_purchase_receipt_combines_orders_with_the_same_tax(self):
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_purchase_receipt,
		)

		scr = self.make_receipt_of_two_orders(taxes={0: [10], 1: [10]})
		scr.submit()

		pr = make_purchase_receipt(scr.name)
		self.assertEqual(len({item.purchase_order for item in pr.items}), 2)
		self.assertEqual([tax.description for tax in pr.taxes], ["Excise 10"])

	def test_subcontracting_receipt_gl_entry_with_different_rm_expense_accounts(self):
		service_items = [
			{
				"warehouse": "Stores - TCP1",
				"item_code": "Subcontracted Service Item 7",
				"qty": 10,
				"rate": 100,
				"fg_item": "Subcontracted Item SA4",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
			service_items=service_items,
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.supplied_items[1].expense_account = "_Test Write Off - TCP1"
		scr.save()
		scr.submit()

		for item in scr.supplied_items:
			self.assertTrue(item.expense_account)

		gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
		self.assertTrue(gl_entries)

		fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
		expense_account = scr.items[0].expense_account
		expected_values = {
			fg_warehouse_ac: [4000, 3000],
			expense_account: [2000, 4000],
			"_Test Write Off - TCP1": [1000, 0],
		}

		for gle in gl_entries:
			self.assertEqual(expected_values[gle.account][0], gle.debit)
			self.assertEqual(expected_values[gle.account][1], gle.credit)

	def test_subcontracting_receipt_for_service_expense_account(self):
		service_expense_account = set_service_expense_account("Subcontracted Service Item 10")
		scr = make_service_receipt("Subcontracted Service Item 10", "Subcontracted Item SA10", 10, 100, 10)

		for item in scr.items:
			self.assertEqual(item.service_expense_account, service_expense_account)

		gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
		self.assertTrue(gl_entries)

		fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
		expense_account = scr.items[0].expense_account
		expected_values = {
			fg_warehouse_ac: [2000, 1000],
			expense_account: [1000, 1000],
			service_expense_account: [0, 1000],
		}

		for gle in gl_entries:
			self.assertEqual(expected_values[gle.account][0], gle.debit)
			self.assertEqual(expected_values[gle.account][1], gle.credit)

	def test_service_cost_split_over_finished_goods_is_credited_in_full(self):
		service_expense_account = set_service_expense_account("Subcontracted Service Item 10")
		scr = make_service_receipt("Subcontracted Service Item 10", "Subcontracted Item SA10", 1, 100, 6)

		service_credit = frappe.get_all(
			"GL Entry",
			filters={"voucher_no": scr.name, "account": service_expense_account, "is_cancelled": 0},
			pluck="credit",
		)
		self.assertEqual(service_credit, [100])

	@ERPNextTestSuite.change_settings("Stock Settings", {"use_serial_batch_fields": 0})
	def test_subcontracting_receipt_with_zero_service_cost(self):
		warehouse = "Stores - TCP1"
		service_items = [
			{
				"warehouse": warehouse,
				"item_code": "Subcontracted Service Item 6",
				"qty": 10,
				"rate": 0,
				"fg_item": "Subcontracted Item SA7",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse=warehouse,
			supplier_warehouse="Work In Progress - TCP1",
			service_items=service_items,
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()

		gl_entries = get_gl_entries("Subcontracting Receipt", scr.name)
		self.assertTrue(gl_entries)

		fg_warehouse_ac = get_inventory_account(scr.company, scr.items[0].warehouse)
		expense_account = scr.items[0].expense_account
		expected_values = {
			fg_warehouse_ac: [1000, 1000],
			expense_account: [1000, 1000],
		}

		for gle in gl_entries:
			self.assertEqual(expected_values[gle.account][0], gle.debit)
			self.assertEqual(expected_values[gle.account][1], gle.credit)

		scr.reload()
		scr.cancel()

	def test_supplied_items_consumed_qty(self):
		# Set Backflush Based On as "Material Transferred for Subcontracting" to transfer RM's more than the required qty
		set_backflush_based_on("Material Transferred for Subcontract")

		# Create Material Receipt for RM's
		make_stock_entry(item_code="_Test Item", qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)
		make_stock_entry(
			item_code="_Test Item Home Desktop 100",
			qty=100,
			target="_Test Warehouse 1 - _TC",
			basic_rate=100,
		)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": "_Test FG Item",
				"fg_item_qty": 10,
			},
		]

		# Create Subcontracting Order
		sco = get_subcontracting_order(service_items=service_items)

		# Transfer RM's
		rm_items = get_rm_items(sco.supplied_items)
		rm_items[0]["qty"] = 20  # Extra 10 Qty
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		# Create Subcontracting Receipt
		scr = make_subcontracting_receipt(sco.name)
		scr.rejected_warehouse = "_Test Warehouse 1 - _TC"

		scr.items[0].qty = 5  # Accepted Qty
		scr.items[0].rejected_qty = 3
		scr.save()

		# consumed_qty should be (received_qty * (transfered_qty / qty)) = (8 * (20 / 10)) = 16
		self.assertEqual(scr.supplied_items[0].consumed_qty, 16)

		# Set Backflush Based On as "BOM"
		set_backflush_based_on("BOM")

		scr.items[0].qty = 6  # Accepted Qty
		scr.items[0].rejected_qty = 4
		scr.set_missing_values()
		scr.save()

		# consumed_qty should be (accepted_qty * qty_consumed_per_unit) = (6 * 1) = 6
		self.assertEqual(scr.supplied_items[0].consumed_qty, 10)

		# Do not transfer materials to the supplier warehouse and check whether system allows to consumed directly from the supplier's warehouse
		sco = get_subcontracting_order(service_items=service_items)

		# Transfer RM's
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items, warehouse="_Test Warehouse 1 - _TC")

		# Create Subcontracting Receipt
		scr = make_subcontracting_receipt(sco.name)
		scr.submit()
		self.assertEqual(scr.docstatus, 1)

		for item in scr.supplied_items:
			self.assertFalse(item.available_qty_for_consumption)

	@ERPNextTestSuite.change_settings("Buying Settings", {"allow_multiple_items": True})
	def test_supplied_items_consumed_qty_for_similar_finished_goods(self):
		"""
		Test that supplied raw material consumption is calculated correctly
		when multiple subcontracted service items use the same finished good
		but different BOMs.
		"""

		from erpnext.controllers.subcontracting_controller import (
			make_rm_stock_entry as make_subcontract_transfer_entry,
		)
		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom

		# Configuration: Backflush based on subcontract material transfer
		set_backflush_based_on("Material Transferred for Subcontract")

		# Create Raw Materials
		raw_material_1 = make_item("_RM Item 1", properties={"is_stock_item": 1}).name

		raw_material_2 = make_item("_RM Item 2", properties={"is_stock_item": 1}).name

		# Create Subcontracted Finished Good
		finished_good = make_item("_Finished Good Item", properties={"is_stock_item": 1})
		finished_good.is_sub_contracted_item = 1
		finished_good.save()

		# Receive Raw Materials into Warehouse
		for raw_material in (raw_material_1, raw_material_2):
			make_stock_entry(
				item_code=raw_material,
				qty=10,
				target="_Test Warehouse - _TC",
				basic_rate=100,
			)

		# Create BOMs for the same Finished Good with different RMs
		bom_rm_1 = make_bom(
			item=finished_good.name,
			quantity=1,
			raw_materials=[raw_material_1],
		).name

		_bom_rm_2 = make_bom(
			item=finished_good.name,
			quantity=1,
			raw_materials=[raw_material_2],
		).name

		# Define Subcontracted Service Items
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": finished_good.name,
				"fg_item_qty": 10,
			},
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 150,
				"fg_item": finished_good.name,
				"fg_item_qty": 10,
			},
		]

		# Create Subcontracting Order
		subcontracting_order = get_subcontracting_order(
			service_items=service_items,
			do_not_save=True,
		)

		# Assign BOM only to the first service item
		subcontracting_order.items[0].bom = bom_rm_1
		subcontracting_order.save()
		subcontracting_order.submit()

		# Prepare Raw Material Transfer Items
		raw_material_transfer_items = []
		for supplied_item in subcontracting_order.supplied_items:
			raw_material_transfer_items.append(
				{
					"item_code": supplied_item.main_item_code,
					"rm_item_code": supplied_item.rm_item_code,
					"qty": supplied_item.required_qty,
					"warehouse": "_Test Warehouse - _TC",
					"stock_uom": "Nos",
				}
			)

		# Transfer Raw Materials to Subcontractor Warehouse
		stock_entry = frappe.get_doc(
			make_subcontract_transfer_entry(
				subcontracting_order.name,
				raw_material_transfer_items,
			)
		)
		stock_entry.to_warehouse = "_Test Warehouse 1 - _TC"
		stock_entry.save()
		stock_entry.submit()

		# Create Subcontracting Receipt
		subcontracting_receipt = make_subcontracting_receipt(subcontracting_order.name)
		subcontracting_receipt.save()

		# Check consumed_qty for each supplied item
		self.assertEqual(len(subcontracting_receipt.supplied_items), 2)
		self.assertEqual(subcontracting_receipt.supplied_items[0].consumed_qty, 10)
		self.assertEqual(subcontracting_receipt.supplied_items[1].consumed_qty, 10)

	def test_supplied_items_cost_after_reposting(self):
		# Set Backflush Based On as "BOM"
		set_backflush_based_on("BOM")

		# Create Material Receipt for RM's
		make_stock_entry(
			item_code="_Test Item",
			qty=100,
			target="_Test Warehouse 1 - _TC",
			basic_rate=100,
			posting_date=add_days(today(), -2),
		)
		make_stock_entry(
			item_code="_Test Item Home Desktop 100",
			qty=100,
			target="_Test Warehouse 1 - _TC",
			basic_rate=100,
		)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": "_Test FG Item",
				"fg_item_qty": 10,
			},
		]

		# Create Subcontracting Order
		sco = get_subcontracting_order(service_items=service_items)

		# Transfer RM's
		rm_items = get_rm_items(sco.supplied_items)

		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		# Create Subcontracting Receipt
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()

		# Create Backdated Stock Reconciliation
		sr = create_stock_reconciliation(
			item_code=rm_items[0].get("item_code"),
			warehouse="_Test Warehouse 1 - _TC",
			qty=100,
			rate=50,
			posting_date=add_days(today(), -1),
		)

		# Cost should be updated in Subcontracting Receipt after reposting
		prev_cost = scr.supplied_items[0].rate
		scr.load_from_db()
		self.assertNotEqual(scr.supplied_items[0].rate, prev_cost)
		self.assertEqual(scr.supplied_items[0].rate, sr.items[0].valuation_rate)

	def test_subcontracting_receipt_for_batch_raw_materials_without_material_transfer(self):
		set_backflush_based_on("BOM")

		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item1 = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNGS-.####",
			}
		).name

		bom = make_bom(item=fg_item, raw_materials=[rm_item1])

		rm_batch_no = None
		for row in bom.items:
			se = make_stock_entry(
				item_code=row.item_code,
				qty=1,
				target="_Test Warehouse 1 - _TC",
				rate=300,
			)

			se.reload()
			rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.reload()

		bundle_doc = make_serial_batch_bundle(
			{
				"item_code": scr.supplied_items[0].rm_item_code,
				"warehouse": "_Test Warehouse 1 - _TC",
				"voucher_type": "Subcontracting Receipt",
				"posting_date": today(),
				"posting_time": nowtime(),
				"qty": -1,
				"batches": frappe._dict({rm_batch_no: 1}),
				"type_of_transaction": "Outward",
				"do_not_submit": True,
			}
		)

		scr.supplied_items[0].serial_and_batch_bundle = bundle_doc.name
		scr.submit()
		scr.reload()

		batch_no = get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle)
		self.assertEqual(batch_no, rm_batch_no)
		self.assertEqual(scr.items[0].rm_cost_per_qty, 300)
		self.assertEqual(scr.items[0].service_cost_per_qty, 100)

	def test_disabled_raw_material_batch_cannot_be_consumed(self):
		set_backflush_based_on("BOM")

		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNDS-.####",
			}
		).name
		make_bom(item=fg_item, raw_materials=[rm_item])

		se = make_stock_entry(item_code=rm_item, qty=1, target="_Test Warehouse 1 - _TC", rate=300)
		se.reload()
		rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.reload()

		bundle_doc = make_serial_batch_bundle(
			{
				"item_code": scr.supplied_items[0].rm_item_code,
				"warehouse": "_Test Warehouse 1 - _TC",
				"voucher_type": "Subcontracting Receipt",
				"posting_date": today(),
				"posting_time": nowtime(),
				"qty": -1,
				"batches": frappe._dict({rm_batch_no: 1}),
				"type_of_transaction": "Outward",
				"do_not_submit": True,
			}
		)
		scr.supplied_items[0].serial_and_batch_bundle = bundle_doc.name
		frappe.db.set_value("Batch", rm_batch_no, "disabled", 1)

		self.assertRaises(frappe.ValidationError, scr.submit)

	@ERPNextTestSuite.change_settings(
		"Stock Settings", {"auto_create_serial_and_batch_bundle_for_outward": 1}
	)
	def test_disabled_batch_of_auto_picked_serial_raw_material_cannot_be_consumed(self):
		set_backflush_based_on("BOM")

		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNDA-.####",
				"has_serial_no": 1,
				"serial_no_series": "SNDA-.####",
			}
		).name
		make_bom(item=fg_item, raw_materials=[rm_item])

		se = make_stock_entry(item_code=rm_item, qty=1, target="_Test Warehouse 1 - _TC", rate=300)
		se.reload()
		rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.reload()
		self.assertFalse(scr.supplied_items[0].serial_and_batch_bundle)
		frappe.db.set_value("Batch", rm_batch_no, "disabled", 1)

		self.assertRaises(frappe.ValidationError, scr.submit)

	def test_subcontracting_receipt_valuation_with_auto_created_serial_batch_bundle(self):
		set_backflush_based_on("BOM")

		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item1 = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNGS-.####",
			}
		).name

		rm_item2 = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"has_serial_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNGS0-.####",
				"serial_no_series": "BNSS90-.####",
			}
		).name

		rm_item3 = make_item(
			properties={
				"is_stock_item": 1,
				"has_serial_no": 1,
				"serial_no_series": "BSSSS-.####",
			}
		).name

		bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3])

		for row in bom.items:
			make_stock_entry(
				item_code=row.item_code,
				qty=1,
				target="_Test Warehouse 1 - _TC",
				rate=300,
			)

			make_stock_entry(
				item_code=row.item_code,
				qty=1,
				target="_Test Warehouse 1 - _TC",
				rate=400,
			)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)

		frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()
		scr.reload()

		for row in scr.supplied_items:
			self.assertEqual(row.rate, 300.00)
			self.assertTrue(row.serial_and_batch_bundle)
			serial_and_batch_bundle = frappe.db.get_value(
				"Stock Ledger Entry",
				{"voucher_no": scr.name, "voucher_detail_no": row.name},
				"serial_and_batch_bundle",
			)

			self.assertTrue(serial_and_batch_bundle)

		self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
		self.assertEqual(scr.items[0].service_cost_per_qty, 100)
		frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0)

	def test_subcontracting_receipt_valuation_for_fg_with_auto_created_serial_batch_bundle(self):
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		set_backflush_based_on("BOM")

		fg_item = make_item(
			properties={
				"is_stock_item": 1,
				"is_sub_contracted_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BSSNGS-.####",
			}
		).name

		rm_item1 = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNGS-.####",
			}
		).name

		rm_item2 = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"has_serial_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNGS91-.####",
				"serial_no_series": "BNSS91-.####",
			}
		).name

		rm_item3 = make_item(
			properties={
				"is_stock_item": 1,
				"has_serial_no": 1,
				"serial_no_series": "BSSSS-.####",
			}
		).name

		bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2, rm_item3])

		for row in bom.items:
			make_stock_entry(
				item_code=row.item_code,
				qty=1,
				target="_Test Warehouse 1 - _TC",
				rate=300,
			)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)

		frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 1)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()
		scr.reload()

		for row in scr.supplied_items:
			self.assertEqual(row.rate, 300.00)
			self.assertTrue(row.serial_and_batch_bundle)
			auto_created_serial_batch = frappe.db.get_value(
				"Stock Ledger Entry",
				{"voucher_no": scr.name, "voucher_detail_no": row.name},
				"auto_created_serial_and_batch_bundle",
			)

			self.assertTrue(auto_created_serial_batch)

		self.assertEqual(scr.items[0].rm_cost_per_qty, 900)
		self.assertEqual(scr.items[0].service_cost_per_qty, 100)
		self.assertEqual(scr.items[0].rate, 1000)
		valuation_rate = frappe.db.get_value(
			"Stock Ledger Entry",
			{"voucher_no": scr.name, "voucher_detail_no": scr.items[0].name},
			"valuation_rate",
		)

		self.assertEqual(flt(valuation_rate), flt(1000))

		frappe.db.set_single_value("Stock Settings", "auto_create_serial_and_batch_bundle_for_outward", 0)
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_subcontracting_receipt_raw_material_rate(self):
		# Step - 1: Set Backflush Based On as "BOM"
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		set_backflush_based_on("BOM")

		# Step - 2: Create FG and RM Items
		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item1 = make_item(properties={"is_stock_item": 1}).name
		rm_item2 = make_item(properties={"is_stock_item": 1}).name

		# Step - 3: Create BOM for FG Item
		bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2])
		for rm_item in bom.items:
			self.assertEqual(rm_item.rate, 0)
			self.assertEqual(rm_item.amount, 0)
		bom = bom.name

		# Step - 4: Create PO and SCO
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 100,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 100,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		for rm_item in sco.supplied_items:
			self.assertEqual(rm_item.rate, 0)
			self.assertEqual(rm_item.amount, 0)

		# Step - 5: Inward Raw Materials
		rm_items = get_rm_items(sco.supplied_items)
		for rm_item in rm_items:
			rm_item["rate"] = 100
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		# Step - 6: Transfer RM's to Subcontractor
		se = make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		for item in se.items:
			self.assertEqual(item.qty, 100)
			self.assertEqual(item.basic_rate, 100)
			self.assertEqual(item.amount, item.qty * item.basic_rate)

		# Step - 7: Create Subcontracting Receipt
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()
		scr.load_from_db()
		for rm_item in scr.supplied_items:
			self.assertEqual(rm_item.consumed_qty, 100)
			self.assertEqual(rm_item.rate, 100)
			self.assertEqual(rm_item.amount, rm_item.consumed_qty * rm_item.rate)

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_quality_inspection_for_subcontracting_receipt(self):
		from erpnext.stock.doctype.quality_inspection.test_quality_inspection import (
			create_quality_inspection,
		)

		set_backflush_based_on("BOM")
		fg_item = "Subcontracted Item SA1"
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 5,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr1 = make_subcontracting_receipt(sco.name)
		scr1.save()

		# Enable `Inspection Required before Purchase` in Item Master
		frappe.db.set_value("Item", fg_item, "inspection_required_before_purchase", 1)

		# ValidationError should be raised as Quality Inspection is not created/linked
		self.assertRaises(frappe.ValidationError, scr1.submit)

		qa = create_quality_inspection(
			reference_type="Subcontracting Receipt",
			reference_name=scr1.name,
			inspection_type="Incoming",
			item_code=fg_item,
		)
		scr1.reload()
		self.assertEqual(scr1.items[0].quality_inspection, qa.name)

		# SCR should be submitted successfully as Quality Inspection is set
		scr1.submit()
		scr1.reload()
		scr1.cancel()
		qa.reload()
		qa.cancel()

		scr2 = make_subcontracting_receipt(sco.name)
		scr2.save()

		# Disable `Inspection Required before Purchase` in Item Master
		frappe.db.set_value("Item", fg_item, "inspection_required_before_purchase", 0)

		# ValidationError should not be raised as `Inspection Required before Purchase` is disabled
		scr2.submit()

	@ERPNextTestSuite.change_settings(
		"Stock Settings", {"allow_to_make_quality_inspection_after_purchase_or_delivery": 1}
	)
	def test_quality_inspection_allowed_after_subcontracting_receipt(self):
		set_backflush_based_on("BOM")
		fg_item = "Subcontracted Item SA1"
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 5,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		frappe.db.set_value("Item", fg_item, "inspection_required_before_purchase", 1)

		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()

		self.assertEqual(scr.docstatus, 1)
		self.assertFalse(scr.items[0].quality_inspection)

		scr.run_method("onload")
		self.assertTrue(scr.get_onload().get("allow_to_make_qc_after_submission"))

	def test_secondary_items_for_subcontracting_receipt(self):
		set_backflush_based_on("BOM")

		fg_item = "Subcontracted Item SA1"

		# Create Raw Materials
		raw_materials = [
			make_item(properties={"is_stock_item": 1, "valuation_rate": 100}).name,
			make_item(properties={"is_stock_item": 1, "valuation_rate": 200}).name,
		]

		# Create Scrap Items
		secondary_item_1 = make_item(properties={"is_stock_item": 1, "valuation_rate": 10}).name
		secondary_item_2 = make_item(properties={"is_stock_item": 1, "valuation_rate": 20}).name
		secondary_items = [secondary_item_1, secondary_item_2]

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 10,
			},
		]

		# Create BOM with Scrap Items
		bom = make_bom(
			item=fg_item, raw_materials=raw_materials, rate=100, currency="INR", do_not_submit=True
		)
		for idx, item in enumerate(bom.items):
			item.qty = 1 * (idx + 1)
		for idx, item in enumerate(secondary_items):
			bom.append(
				"secondary_items",
				{
					"item_code": item,
					"secondary_item_type": "Scrap",
					"stock_qty": 1 * (idx + 1),
					"valuation_type": "Valuation Rate",
				},
			)
		manual_item = make_item(properties={"is_stock_item": 1}).name
		bom.append(
			"secondary_items",
			{
				"item_code": manual_item,
				"secondary_item_type": "By-Product",
				"stock_qty": 1,
				"valuation_type": "Manual",
				"cost": 30,
			},
		)
		percentage_item = make_item(properties={"is_stock_item": 1}).name
		bom.append(
			"secondary_items",
			{
				"item_code": percentage_item,
				"secondary_item_type": "Co-Product",
				"stock_qty": 1,
				"valuation_type": "% of Component Cost",
				"cost_allocation_per": 10,
			},
		)
		bom.save()
		bom.submit()

		# Create PO and SCO
		sco = get_subcontracting_order(service_items=service_items)

		# Inward Raw Materials
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		# Transfer RM's to Subcontractor
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		# Create Subcontracting Receipt
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.get_secondary_items()

		scr_secondary_items = set(
			[item.item_code for item in scr.items if item.secondary_item_type or item.valuation_type]
		)
		self.assertEqual(len(scr.items), 5)  # 1 FG Item + 4 Secondary Items
		self.assertEqual(scr_secondary_items, {*secondary_items, manual_item, percentage_item})

		# without a document level warehouse the rows fall back to the FG row's warehouse
		scr.set_warehouse = None
		scr.get_secondary_items()
		fg_warehouse = next(item.warehouse for item in scr.items if item.bom)
		for item in scr.items:
			if item.secondary_item_type or item.valuation_type:
				self.assertEqual(item.warehouse, fg_warehouse)

		# the percentage row allocates from the cost net of the own-cost rows, so the
		# received value equals the consumed value
		scr.save()
		fg_row = next(item for item in scr.items if item.bom)
		fg_gross = (
			flt(fg_row.rm_cost_per_qty)
			+ flt(fg_row.service_cost_per_qty)
			+ flt(fg_row.additional_cost_per_qty)
		) * flt(fg_row.received_qty)
		secondary_total = sum(
			flt(row.amount) for row in scr.items if row.secondary_item_type or row.valuation_type
		)
		self.assertAlmostEqual(flt(fg_row.amount) + secondary_total, fg_gross, places=2)

		# valuation rate rows are repriced at their warehouse on every save
		make_stock_entry(item_code=secondary_item_1, target="_Test Warehouse - _TC", qty=5, basic_rate=40)
		vr_row = next(item for item in scr.items if item.item_code == secondary_item_1)
		vr_row.warehouse = "_Test Warehouse - _TC"
		scr.save()
		vr_row = next(item for item in scr.items if item.item_code == secondary_item_1)
		self.assertEqual(vr_row.rate, 40)

		# the manual row starts at the BOM cost per unit and takes the user's own rate
		manual_row = next(item for item in scr.items if item.item_code == manual_item)
		self.assertEqual(manual_row.rate, 30)
		manual_row.rate = 45
		scr.save()
		manual_row = next(item for item in scr.items if item.item_code == manual_item)
		self.assertEqual(manual_row.rate, 45)
		self.assertEqual(manual_row.amount, 45 * manual_row.qty)

		# percentage rows reprice on save when the own-cost basis changes
		own_total = sum(
			flt(row.amount) for row in scr.items if row.valuation_type in ("Valuation Rate", "Manual")
		)
		percentage_row = next(item for item in scr.items if item.item_code == percentage_item)
		self.assertAlmostEqual(flt(percentage_row.amount), (fg_gross - own_total) * 0.10, places=2)

		# the finished good's rate is its cost allocation share of the net cost
		fg_row = next(item for item in scr.items if item.bom)
		self.assertTrue(fg_row.secondary_items_cost_per_qty > 0)
		fg_percent = flt(frappe.get_value("BOM", fg_row.bom, "cost_allocation_per")) / 100
		self.assertAlmostEqual(
			flt(fg_row.rate),
			(
				flt(fg_row.rm_cost_per_qty)
				+ flt(fg_row.service_cost_per_qty)
				+ flt(fg_row.additional_cost_per_qty)
				- flt(fg_row.secondary_items_cost_per_qty)
			)
			* fg_percent,
			places=2,
		)

		scr.submit()

	def test_subcontracting_receipt_cancel_with_batch(self):
		from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom

		# Step - 1: Set Backflush Based On as "BOM"
		set_backflush_based_on("BOM")

		# Step - 2: Create FG and RM Items
		fg_item = make_item(
			properties={"is_stock_item": 1, "is_sub_contracted_item": 1, "has_batch_no": 1}
		).name
		rm_item1 = make_item(properties={"is_stock_item": 1}).name
		rm_item2 = make_item(properties={"is_stock_item": 1}).name
		make_item("Subcontracted Service Item Test For Batch 1", {"is_stock_item": 0})

		# Step - 3: Create BOM for FG Item
		bom = make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2])
		for rm_item in bom.items:
			self.assertEqual(rm_item.rate, 0)
			self.assertEqual(rm_item.amount, 0)
		bom = bom.name

		# Step - 4: Create PO and SCO
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item Test For Batch 1",
				"qty": 100,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 100,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		for rm_item in sco.supplied_items:
			self.assertEqual(rm_item.rate, 0)
			self.assertEqual(rm_item.amount, 0)

		# Step - 5: Inward Raw Materials
		rm_items = get_rm_items(sco.supplied_items)
		for rm_item in rm_items:
			rm_item["rate"] = 100
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		# Step - 6: Transfer RM's to Subcontractor
		se = make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		for item in se.items:
			self.assertEqual(item.qty, 100)
			self.assertEqual(item.basic_rate, 100)
			self.assertEqual(item.amount, item.qty * item.basic_rate)

		batch_doc = frappe.get_doc(
			{
				"doctype": "Batch",
				"item": fg_item,
				"batch_id": frappe.generate_hash(length=10),
			}
		).insert(ignore_permissions=True)

		serial_batch_bundle = frappe.get_doc(
			{
				"doctype": "Serial and Batch Bundle",
				"company": sco.company,
				"item_code": fg_item,
				"warehouse": sco.items[0].warehouse,
				"has_batch_no": 1,
				"type_of_transaction": "Inward",
				"voucher_type": "Subcontracting Receipt",
				"entries": [{"batch_no": batch_doc.name, "qty": 100}],
			}
		).insert(ignore_permissions=True)

		# Step - 7: Create Subcontracting Receipt
		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].serial_and_batch_bundle = serial_batch_bundle.name
		scr.save()
		scr.submit()
		scr.load_from_db()

		# Step - 8: Cancel Subcontracting Receipt
		scr.cancel()
		self.assertTrue(scr.docstatus == 2)

	def test_subcontract_return_from_rejected_warehouse(self):
		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_subcontract_return_against_rejected_warehouse,
		)

		# Create subcontracted item
		fg_item = make_item(
			"_Test Subcontract Item Return from Rejected Warehouse",
			properties={
				"is_stock_item": 1,
				"is_sub_contracted_item": 1,
			},
		).name

		# Create service item
		service_item = make_item(
			"_Test Service Item Return from Rejected Warehouse", properties={"is_stock_item": 0}
		).name

		# Create BOM for the subcontracted item with required raw materials
		rm_item1 = make_item(
			"_Test RM Item 1 Return from Rejected Warehouse", properties={"is_stock_item": 1}
		).name

		rm_item2 = make_item(
			"_Test RM Item 2 Return from Rejected Warehouse", properties={"is_stock_item": 1}
		).name

		make_bom(item=fg_item, raw_materials=[rm_item1, rm_item2])

		# Create warehouses
		rejected_warehouse = create_warehouse("_Test Subcontract Rejected Warehouse Return Qty Warehouse")

		# Create service items for subcontracting order
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": service_item,
				"qty": 10,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 10,
			},
		]

		# Create Subcontracting Order
		sco = get_subcontracting_order(service_items=service_items)

		# Stock raw materials
		make_stock_entry(item_code=rm_item1, qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)
		make_stock_entry(item_code=rm_item2, qty=100, target="_Test Warehouse 1 - _TC", basic_rate=100)

		# Transfer raw materials
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		# Step 1: Create Subcontracting Receipt with rejected quantity
		sr = make_subcontracting_receipt(sco.name)
		sr.items[0].qty = 8  # Accepted quantity
		sr.items[0].rejected_qty = 2
		sr.items[0].rejected_warehouse = rejected_warehouse
		sr.save()
		sr.submit()

		# Verify initial state
		sr.reload()
		self.assertEqual(sr.items[0].qty, 8)
		self.assertEqual(sr.items[0].rejected_qty, 2)
		self.assertEqual(sr.items[0].rejected_warehouse, rejected_warehouse)

		# Step 2: Create Subcontract Return from Rejected Warehouse
		sr_return = make_subcontract_return_against_rejected_warehouse(sr.name)

		# Verify the return document properties
		self.assertEqual(sr_return.doctype, "Subcontracting Receipt")
		self.assertEqual(sr_return.is_return, 1)
		self.assertEqual(sr_return.return_against, sr.name)

		# Verify item details in return document
		self.assertEqual(len(sr_return.items), 1)
		self.assertEqual(sr_return.items[0].item_code, fg_item)
		self.assertEqual(sr_return.items[0].warehouse, rejected_warehouse)
		self.assertEqual(sr_return.items[0].qty, -2.0)  # Negative for return
		self.assertEqual(sr_return.items[0].rejected_qty, 0.0)
		self.assertEqual(sr_return.items[0].rejected_warehouse, "")

		# Check specific fields that should be set for subcontracting returns
		self.assertEqual(sr_return.items[0].subcontracting_order, sco.name)
		self.assertEqual(sr_return.items[0].subcontracting_order_item, sr.items[0].subcontracting_order_item)
		self.assertEqual(sr_return.items[0].return_qty_from_rejected_warehouse, 1)

		# For returns from rejected warehouse, supplied_items might be empty initially
		# They might get populated when the document is saved/submitted
		# Or they might not be needed since we're returning finished goods

		# Save and submit the return
		sr_return.save()
		sr_return.submit()

		# Verify final state
		sr_return.reload()
		self.assertEqual(sr_return.docstatus, 1)
		self.assertEqual(sr_return.status, "Return")

		# Verify stock ledger entries for the return
		sle = frappe.get_all(
			"Stock Ledger Entry",
			filters={
				"voucher_type": "Subcontracting Receipt",
				"voucher_no": sr_return.name,
				"warehouse": rejected_warehouse,
			},
			fields=["item_code", "actual_qty", "warehouse"],
		)

		self.assertEqual(len(sle), 1)
		self.assertEqual(sle[0].item_code, fg_item)
		self.assertEqual(sle[0].actual_qty, -2.0)  # Outward entry from rejected warehouse
		self.assertEqual(sle[0].warehouse, rejected_warehouse)

		# Verify that the original document's rejected quantity is not affected
		sr.reload()
		self.assertEqual(sr.items[0].rejected_qty, 2)  # Should remain the same

	def test_purchase_receipt_keeps_purchase_order_currency(self):
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_purchase_receipt,
		)

		po = create_purchase_order(
			rm_items=[
				{
					"warehouse": "_Test Warehouse - _TC",
					"item_code": "Subcontracted Service Item 1",
					"qty": 10,
					"rate": 100,
					"fg_item": "Subcontracted Item SA1",
					"fg_item_qty": 10,
				},
			],
			is_subcontracted=1,
			supplier="_Test Supplier USD",
			currency="USD",
			supplier_warehouse="_Test Warehouse 1 - _TC",
			do_not_save=True,
		)
		po.conversion_rate = 80
		po.set_missing_values()
		po.insert()
		po.submit()

		sco = get_subcontracting_order(po_name=po.name)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()

		pr = make_purchase_receipt(scr.name, save=True)

		self.assertEqual(pr.currency, "USD")
		self.assertEqual(pr.conversion_rate, 80)
		self.assertEqual(pr.grand_total, 1000)
		self.assertEqual(pr.base_grand_total, 80000)

	def test_rejected_return_brings_back_raw_materials_at_zero_value(self):
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_subcontract_return_against_rejected_warehouse,
		)

		sco = get_subcontracting_order(
			company="_Test Company with perpetual inventory",
			warehouse="Stores - TCP1",
			supplier_warehouse="Work In Progress - TCP1",
		)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 8
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "Finished Goods - TCP1"
		scr.save()
		scr.submit()

		scr_return = make_subcontract_return_against_rejected_warehouse(scr.name)
		scr_return.save()
		scr_return.reload()
		scr_return.submit()

		returned_values = frappe.get_all(
			"Stock Ledger Entry",
			filters={"voucher_no": scr_return.name, "warehouse": scr_return.supplier_warehouse},
			pluck="stock_value_difference",
		)
		self.assertTrue(returned_values)
		self.assertEqual(sum(returned_values), 0)

	def test_purchase_receipt_maps_all_receipt_rows(self):
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_purchase_receipt,
		)

		set_backflush_based_on("BOM")
		orders = [get_subcontracting_order(), get_subcontracting_order()]
		for sco in orders:
			rm_items = get_rm_items(sco.supplied_items)
			itemwise_details = make_stock_in_entry(rm_items=rm_items)
			make_stock_transfer_entry(
				sco_no=sco.name,
				rm_items=rm_items,
				itemwise_details=copy.deepcopy(itemwise_details),
			)

		scr = make_subcontracting_receipt(orders[0].name)
		scr.items[0].qty = 6
		scr.append("items", {**scr.items[0].as_dict(), "name": None, "idx": None, "qty": 4})
		scr = make_subcontracting_receipt(orders[1].name, scr)
		scr.save()
		scr.submit()

		make_purchase_receipt(scr.name).submit()

		for sco in orders:
			self.assertEqual(frappe.db.get_value("Purchase Order", sco.purchase_order, "per_received"), 100)

	def test_purchase_receipt_only_once_from_submitted_receipt(self):
		from erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt import (
			make_purchase_receipt,
		)

		set_backflush_based_on("BOM")
		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		self.assertRaises(frappe.ValidationError, make_purchase_receipt, scr.name)

		scr.submit()
		purchase_receipt = make_purchase_receipt(scr.name, save=True)
		self.assertRaises(frappe.ValidationError, make_purchase_receipt, scr.name)
		self.assertRaisesRegex(
			frappe.ValidationError,
			"is already made against Subcontracting Receipt",
			frappe.copy_doc(purchase_receipt).insert,
		)

	@ERPNextTestSuite.change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
	def test_auto_create_purchase_receipt(self):
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		fg_item = "Subcontracted Item SA1"
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			},
		]

		po = create_purchase_order(
			rm_items=service_items,
			is_subcontracted=1,
			supplier_warehouse="_Test Warehouse 1 - _TC",
			do_not_submit=True,
		)
		po.append(
			"taxes",
			{
				"account_head": "_Test Account Excise Duty - _TC",
				"charge_type": "On Net Total",
				"cost_center": "_Test Cost Center - _TC",
				"description": "Excise Duty",
				"doctype": "Purchase Taxes and Charges",
				"rate": 10,
			},
		)
		po.save()
		po.submit()

		sco = get_subcontracting_order(po_name=po.name)

		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 3
		scr.save()
		scr.submit()

		pr_details = frappe.get_all(
			"Purchase Receipt",
			filters={"subcontracting_receipt": scr.name},
			fields=["name", "total_taxes_and_charges"],
		)

		self.assertTrue(pr_details)

		pr_qty = frappe.db.get_value("Purchase Receipt Item", {"parent": pr_details[0]["name"]}, "qty")
		self.assertEqual(pr_qty, 6)

		self.assertEqual(pr_details[0]["total_taxes_and_charges"], 60)

	@ERPNextTestSuite.change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
	def test_auto_create_purchase_receipt_with_tax_withholding_row(self):
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		fg_item = "Subcontracted Item SA1"
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			},
		]

		po = create_purchase_order(
			rm_items=service_items,
			is_subcontracted=1,
			supplier_warehouse="_Test Warehouse 1 - _TC",
			do_not_submit=True,
		)
		# withheld against the full PO value, and not recomputed on a partial receipt
		po.append(
			"taxes",
			{
				"account_head": "_Test Account Excise Duty - _TC",
				"charge_type": "Actual",
				"add_deduct_tax": "Deduct",
				"cost_center": "_Test Cost Center - _TC",
				"description": "TDS on Contract",
				"doctype": "Purchase Taxes and Charges",
				"tax_amount": 800,
				"is_tax_withholding_account": 1,
			},
		)
		po.save()
		po.submit()
		self.assertEqual(po.grand_total, 200)

		sco = get_subcontracting_order(po_name=po.name)

		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 3
		scr.save()

		# carrying the withholding row over would deduct 800 from a 600 receipt,
		# and Purchase Receipt rejects the resulting negative Grand Total
		scr.submit()

		pr_name = frappe.db.get_value("Purchase Receipt", {"subcontracting_receipt": scr.name})
		self.assertTrue(pr_name)

		pr = frappe.get_doc("Purchase Receipt", pr_name)
		self.assertEqual(pr.items[0].qty, 6)
		self.assertEqual(pr.net_total, 600)
		self.assertFalse([row for row in pr.taxes if row.is_tax_withholding_account])
		self.assertEqual(pr.grand_total, 600)

	@ERPNextTestSuite.change_settings("Buying Settings", {"auto_create_purchase_receipt": 1})
	def test_auto_create_purchase_receipt_with_no_reference_of_po_item(self):
		from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order

		fg_item = "Subcontracted Item SA1"
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			},
		]

		po = create_purchase_order(
			rm_items=service_items,
			is_subcontracted=1,
			supplier_warehouse="_Test Warehouse 1 - _TC",
			do_not_submit=True,
		)
		po.append(
			"taxes",
			{
				"account_head": "_Test Account Excise Duty - _TC",
				"charge_type": "On Net Total",
				"cost_center": "_Test Cost Center - _TC",
				"description": "Excise Duty",
				"doctype": "Purchase Taxes and Charges",
				"rate": 10,
			},
		)
		po.save()
		po.submit()

		sco = get_subcontracting_order(po_name=po.name)
		for row in sco.items:
			row.db_set("purchase_order_item", None)

		sco.reload()

		for row in sco.items:
			self.assertFalse(row.purchase_order_item)

		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		scr = make_subcontracting_receipt(sco.name)
		for row in scr.items:
			self.assertFalse(row.purchase_order_item)

		scr.items[0].qty = 3
		scr.save()
		scr.submit()

		pr_details = frappe.get_all(
			"Purchase Receipt",
			filters={"subcontracting_receipt": scr.name},
			fields=["name", "total_taxes_and_charges"],
		)

		self.assertTrue(pr_details)

		pr_qty = frappe.db.get_value("Purchase Receipt Item", {"parent": pr_details[0]["name"]}, "qty")
		self.assertEqual(pr_qty, 6)

		self.assertEqual(pr_details[0]["total_taxes_and_charges"], 60)

	def test_use_serial_batch_fields_for_subcontracting_receipt(self):
		fg_item = make_item(
			"Test Subcontracted Item With Batch No",
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BATCH-BNGS-.####",
				"is_sub_contracted_item": 1,
			},
		).name

		make_item(
			"Test Subcontracted Item With Batch No Service Item 1",
			properties={"is_stock_item": 0},
		)

		make_bom(
			item=fg_item,
			raw_materials=[
				make_item(
					"Test Subcontracted Item With Batch No RM Item 1",
					properties={
						"is_stock_item": 1,
						"has_batch_no": 1,
						"create_new_batch": 1,
						"batch_number_series": "BATCH-RM-BNGS-.####",
					},
				).name
			],
		)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Test Subcontracted Item With Batch No Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		batch_no = "BATCH-BNGS-0001"
		if not frappe.db.exists("Batch", batch_no):
			frappe.get_doc(
				{
					"doctype": "Batch",
					"batch_id": batch_no,
					"item": fg_item,
				}
			).insert()

		scr = make_subcontracting_receipt(sco.name)
		self.assertFalse(scr.items[0].serial_and_batch_bundle)
		scr.items[0].use_serial_batch_fields = 1
		scr.items[0].batch_no = batch_no

		scr.save()
		scr.submit()
		scr.reload()
		self.assertTrue(scr.items[0].serial_and_batch_bundle)

	def test_use_serial_batch_fields_for_subcontracting_receipt_with_rejected_qty(self):
		from erpnext.stock.doctype.warehouse.test_warehouse import create_warehouse

		fg_item = make_item(
			"Test Subcontracted Item With Batch No for Rejected Qty",
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BATCH-REJ-BNGS-.####",
				"is_sub_contracted_item": 1,
			},
		).name

		make_item(
			"Test Subcontracted Item With Batch No Service Item 2",
			properties={"is_stock_item": 0},
		)

		make_bom(
			item=fg_item,
			raw_materials=[
				make_item(
					"Test Subcontracted Item With Batch No RM Item 2",
					properties={
						"is_stock_item": 1,
						"has_batch_no": 1,
						"create_new_batch": 1,
						"batch_number_series": "BATCH-REJ-RM-BNGS-.####",
					},
				).name
			],
		)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Test Subcontracted Item With Batch No Service Item 2",
				"qty": 10,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		batch_no = "BATCH-REJ-BNGS-0001"
		if not frappe.db.exists("Batch", batch_no):
			frappe.get_doc(
				{
					"doctype": "Batch",
					"batch_id": batch_no,
					"item": fg_item,
				}
			).insert()

		rej_warehouse = create_warehouse("_Test Subcontract Warehouse For Rejected Qty")

		scr = make_subcontracting_receipt(sco.name)
		self.assertFalse(scr.items[0].serial_and_batch_bundle)
		scr.items[0].use_serial_batch_fields = 1
		scr.items[0].batch_no = batch_no
		scr.items[0].received_qty = 10
		scr.items[0].rejected_qty = 2
		scr.items[0].qty = 8
		scr.items[0].rejected_warehouse = rej_warehouse

		scr.save()
		scr.submit()
		scr.reload()
		self.assertTrue(scr.items[0].serial_and_batch_bundle)
		self.assertTrue(scr.items[0].rejected_serial_and_batch_bundle)

	def test_subcontracting_receipt_for_batch_materials_without_use_serial_batch_fields(self):
		from erpnext.controllers.subcontracting_controller import make_rm_stock_entry

		set_backflush_based_on("Material Transferred for Subcontract")

		fg_item = make_item(
			"Test Subcontracted FG Item With Batch No and Without Use Serial Batch Fields",
			properties={"is_stock_item": 1, "is_sub_contracted_item": 1},
		).name

		rm_item1 = make_item(
			"Test Subcontracted RM Item With Batch No and Without Use Serial Batch Fields",
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BATCH-RM-BNGS-.####",
			},
		).name

		make_item(
			"Subcontracted Service Item 21",
			properties={
				"is_stock_item": 0,
			},
		)

		bom = make_bom(item=fg_item, raw_materials=[rm_item1])

		rm_batch_no = None
		for row in bom.items:
			se = make_stock_entry(
				item_code=row.item_code,
				qty=10,
				target="_Test Warehouse - _TC",
				rate=300,
			)

			se.reload()
			rm_batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 21",
				"qty": 10,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		self.assertTrue(sco.docstatus)
		rm_items = [
			{
				"name": sco.supplied_items[0].name,
				"item_code": rm_item1,
				"rm_item_code": rm_item1,
				"item_name": rm_item1,
				"qty": 10,
				"warehouse": "_Test Warehouse - _TC",
				"rate": 100,
				"stock_uom": frappe.get_cached_value("Item", rm_item1, "stock_uom"),
				"use_serial_batch_fields": 1,
			},
		]
		se = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
		se.items[0].subcontracted_item = fg_item
		se.items[0].s_warehouse = "_Test Warehouse - _TC"
		se.items[0].t_warehouse = "_Test Warehouse 1 - _TC"
		se.items[0].use_serial_batch_fields = 1
		se.items[0].batch_no = rm_batch_no
		se.submit()

		self.assertEqual(se.items[0].batch_no, rm_batch_no)
		self.assertEqual(se.items[0].use_serial_batch_fields, 1)

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 2
		scr.save()
		scr.submit()

		self.assertEqual(scr.supplied_items[0].consumed_qty, 2)
		self.assertEqual(scr.supplied_items[0].batch_no, rm_batch_no)
		self.assertEqual(get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle), rm_batch_no)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 2
		scr.save()
		scr.submit()

		self.assertEqual(scr.supplied_items[0].consumed_qty, 2)
		self.assertEqual(scr.supplied_items[0].batch_no, rm_batch_no)
		self.assertEqual(get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle), rm_batch_no)

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 6
		scr.save()
		scr.submit()

		self.assertEqual(scr.supplied_items[0].consumed_qty, 6)
		self.assertEqual(scr.supplied_items[0].batch_no, rm_batch_no)
		self.assertEqual(get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle), rm_batch_no)

		sco.reload()
		self.assertEqual(sco.status, "Completed")

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_change_batch_for_raw_materials(self):
		set_backflush_based_on("BOM")

		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item1 = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNGS-.####",
			}
		).name

		bom = make_bom(item=fg_item, raw_materials=[rm_item1])
		second_batch_no = None
		for row in bom.items:
			se = make_stock_entry(
				item_code=row.item_code,
				qty=1,
				target="_Test Warehouse 1 - _TC",
				rate=300,
			)

			se.reload()
			se1 = make_stock_entry(
				item_code=row.item_code,
				qty=1,
				target="_Test Warehouse 1 - _TC",
				rate=300,
			)

			se1.reload()

			second_batch_no = get_batch_from_bundle(se1.items[0].serial_and_batch_bundle)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.reload()

		scr.supplied_items[0].batch_no = second_batch_no
		scr.supplied_items[0].use_serial_batch_fields = 1
		scr.submit()
		scr.reload()

		batch_no = get_batch_from_bundle(scr.supplied_items[0].serial_and_batch_bundle)
		self.assertEqual(batch_no, second_batch_no)
		self.assertEqual(scr.items[0].rm_cost_per_qty, 300)
		self.assertEqual(scr.items[0].service_cost_per_qty, 100)

	@ERPNextTestSuite.change_settings("Stock Settings", {"use_serial_batch_fields": 0})
	def test_bundle_batch_not_supplied_against_order(self):
		from erpnext.controllers.subcontracting_controller import make_rm_stock_entry

		set_backflush_based_on("Material Transferred for Subcontract")

		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BNSO-.####",
			}
		).name
		make_bom(item=fg_item, raw_materials=[rm_item])

		supplied_batch, other_batch = (
			get_batch_from_bundle(
				make_stock_entry(item_code=rm_item, qty=5, target="_Test Warehouse - _TC", rate=100)
				.items[0]
				.serial_and_batch_bundle
			)
			for _ in range(2)
		)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 5,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 5,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = [
			{
				"name": sco.supplied_items[0].name,
				"item_code": rm_item,
				"rm_item_code": rm_item,
				"item_name": rm_item,
				"qty": 5,
				"warehouse": "_Test Warehouse - _TC",
				"rate": 100,
				"stock_uom": "Nos",
			},
		]
		se = frappe.get_doc(make_rm_stock_entry(sco.name, rm_items))
		se.items[0].subcontracted_item = fg_item
		se.items[0].s_warehouse = "_Test Warehouse - _TC"
		se.items[0].t_warehouse = sco.supplier_warehouse
		se.items[0].use_serial_batch_fields = 1
		se.items[0].batch_no = supplied_batch
		se.submit()

		make_stock_entry(
			item_code=rm_item,
			qty=5,
			source="_Test Warehouse - _TC",
			target=sco.supplier_warehouse,
			batch_no=other_batch,
			use_serial_batch_fields=1,
		)

		scr = make_subcontracting_receipt(sco.name)
		scr.save()

		row = scr.supplied_items[0]
		row.batch_no = None
		row.serial_and_batch_bundle = make_serial_batch_bundle(
			{
				"item_code": rm_item,
				"warehouse": sco.supplier_warehouse,
				"voucher_type": "Subcontracting Receipt",
				"qty": -row.consumed_qty,
				"batches": frappe._dict({other_batch: row.consumed_qty}),
				"type_of_transaction": "Outward",
				"do_not_submit": 1,
			}
		).name

		self.assertRaisesRegex(frappe.ValidationError, "has not supplied", scr.save)

	def test_bom_required_qty_validation_based_on_bom(self):
		set_backflush_based_on("BOM")
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

		fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item1 = make_item(
			properties={
				"is_stock_item": 1,
				"has_batch_no": 1,
				"create_new_batch": 1,
				"batch_number_series": "BRQV-.####",
			}
		).name

		make_bom(item=fg_item, raw_materials=[rm_item1], rm_qty=2)
		se = make_stock_entry(
			item_code=rm_item1,
			qty=1,
			target="_Test Warehouse 1 - _TC",
			rate=300,
		)

		batch_no = get_batch_from_bundle(se.items[0].serial_and_batch_bundle)

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 1,
				"rate": 100,
				"fg_item": fg_item,
				"fg_item_qty": 1,
			},
		]

		sco = get_subcontracting_order(service_items=service_items)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.reload()

		self.assertEqual(scr.supplied_items[0].batch_no, batch_no)
		self.assertEqual(scr.supplied_items[0].consumed_qty, 1)
		self.assertEqual(scr.supplied_items[0].required_qty, 2)

		self.assertRaises(BOMQuantityError, scr.submit)

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)

	def test_bom_backflush_return_of_accepted_and_rejected_qty(self):
		set_backflush_based_on("BOM")
		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 3
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "_Test Warehouse 2 - _TC"
		scr.save()
		scr.submit()

		scr_return = make_return_doc("Subcontracting Receipt", scr.name)
		scr_return.items[0].rejected_qty = -2
		scr_return.items[0].rejected_warehouse = "_Test Warehouse 2 - _TC"
		scr_return.save()
		scr_return.submit()

		self.assertEqual(scr_return.docstatus, 1)
		self.assertEqual(sum(row.consumed_qty for row in scr_return.supplied_items), -5)

	def test_return_of_rows_sharing_a_raw_material(self):
		set_backflush_based_on("BOM")
		rm_item = make_item(properties={"is_stock_item": 1}).name
		service_items = []
		for service_item, qty in (("Subcontracted Service Item 1", 3), ("Subcontracted Service Item 2", 2)):
			fg_item = make_item(properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
			make_bom(item=fg_item, raw_materials=[rm_item])
			service_items.append(
				{
					"warehouse": "_Test Warehouse - _TC",
					"item_code": service_item,
					"qty": qty,
					"rate": 100,
					"fg_item": fg_item,
					"fg_item_qty": qty,
				}
			)

		make_stock_entry(item_code=rm_item, qty=5, target="_Test Warehouse 1 - _TC", basic_rate=100)
		sco = get_subcontracting_order(service_items=service_items)
		scr = make_subcontracting_receipt(sco.name)
		scr.save()
		scr.submit()

		scr_return = make_return_doc("Subcontracting Receipt", scr.name)
		scr_return.save()
		scr_return.submit()

		self.assertEqual(sum(row.consumed_qty for row in scr_return.supplied_items), -5)

	def test_bom_required_qty_validation_based_on_transfer(self):
		from erpnext.controllers.subcontracting_controller import (
			make_rm_stock_entry as make_subcontract_transfer_entry,
		)

		set_backflush_based_on("Material Transferred for Subcontract")
		frappe.db.set_single_value("Buying Settings", "validate_consumed_qty", 1)

		item_code = "_Test Subcontracted Validation FG Item 1"
		rm_item1 = make_item(
			properties={
				"is_stock_item": 1,
			}
		).name

		make_subcontracted_item(item_code=item_code, raw_materials=[rm_item1])
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": item_code,
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(
			service_items=service_items,
			include_exploded_items=0,
		)

		# inward raw material stock
		make_stock_entry(target="_Test Warehouse - _TC", item_code=rm_item1, qty=10, basic_rate=100)

		rm_items = [
			{
				"item_code": item_code,
				"rm_item_code": sco.supplied_items[0].rm_item_code,
				"qty": sco.supplied_items[0].required_qty - 5,
				"warehouse": "_Test Warehouse - _TC",
				"stock_uom": "Nos",
			},
		]

		# transfer partial raw materials
		ste = frappe.get_doc(make_subcontract_transfer_entry(sco.name, rm_items))
		ste.to_warehouse = "_Test Warehouse 1 - _TC"
		ste.save()
		ste.submit()

		scr = make_subcontracting_receipt(sco.name)
		scr.save()

		self.assertRaises(BOMQuantityError, scr.submit)

	def test_bom_required_qty_validation_over_all_rows(self):
		from erpnext.controllers.subcontracting_controller import make_rm_stock_entry

		set_backflush_based_on("Material Transferred for Subcontract")
		frappe.db.set_single_value("Buying Settings", "validate_consumed_qty", 1)

		item_code = "_Test Subcontracted Split Rows FG Item"
		rm_item = make_item(properties={"is_stock_item": 1}).name
		make_subcontracted_item(item_code=item_code, raw_materials=[rm_item])
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": item_code,
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items, include_exploded_items=0)
		make_stock_entry(target="_Test Warehouse - _TC", item_code=rm_item, qty=10, basic_rate=100)

		ste = frappe.get_doc(make_rm_stock_entry(sco.name))
		ste.to_warehouse = "_Test Warehouse 1 - _TC"
		ste.save()
		ste.submit()

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = scr.items[0].received_qty = 6
		scr.append(
			"items", {**scr.items[0].as_dict(), "name": None, "idx": None, "qty": 4, "received_qty": 4}
		)
		scr.save()

		scr.supplied_items = [row for row in scr.supplied_items if row.reference_name == scr.items[0].name]
		scr.save()

		self.assertRaises(BOMQuantityError, scr.submit)

	def test_transfer_based_backflush_consumes_material_for_rejected_qty(self):
		from erpnext.controllers.subcontracting_controller import make_rm_stock_entry

		set_backflush_based_on("Material Transferred for Subcontract")

		item_code = "_Test Subcontracted Rejected Qty FG Item"
		rm_item = make_item(properties={"is_stock_item": 1}).name
		make_subcontracted_item(item_code=item_code, raw_materials=[rm_item])
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 10,
				"rate": 100,
				"fg_item": item_code,
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items, include_exploded_items=0)
		make_stock_entry(target="_Test Warehouse - _TC", item_code=rm_item, qty=10, basic_rate=100)

		ste = frappe.get_doc(make_rm_stock_entry(sco.name))
		ste.to_warehouse = "_Test Warehouse 1 - _TC"
		ste.save()
		ste.submit()

		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].qty = 8
		scr.items[0].rejected_qty = 2
		scr.items[0].rejected_warehouse = "_Test Warehouse 2 - _TC"
		scr.save()

		self.assertEqual(sum(row.consumed_qty for row in scr.supplied_items), ste.items[0].transfer_qty)

	@ERPNextTestSuite.change_settings("Buying Settings", {"over_transfer_allowance": 20})
	@ERPNextTestSuite.change_settings("Stock Settings", {"over_delivery_receipt_allowance": 20})
	def test_over_receipt(self):
		from erpnext.controllers.subcontracting_controller import make_rm_stock_entry

		set_backflush_based_on("BOM")

		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)

		rm_items[0]["qty"] = 2
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		ste_dict = make_rm_stock_entry(sco.name)
		doc = frappe.get_doc(ste_dict)
		self.assertEqual(doc.items[0].qty, 0)
		doc.items[0].qty = 2
		doc.submit()

		frappe.flags["args"] = {"items": [{"name": sco.items[0].name, "qty": 2}]}
		scr = make_subcontracting_receipt(sco.name)
		self.assertEqual(scr.items[0].qty, 2)
		scr.submit()
		frappe.flags["args"].pop("items", None)

	def test_inventory_dimensions(self):
		"""
		The subcontracting controller resets the supplied items table on each save causing the inventory dimensions to be lost.
		This test ensures that the inventory dimensions are retained on each save.
		"""
		from erpnext.stock.doctype.inventory_dimension.test_inventory_dimension import (
			create_inventory_dimension,
		)

		create_inventory_dimension(
			apply_to_all_doctypes=1,
			dimension_name="Inv Site",
			reference_document="Inv Site",
			document_type="Inv Site",
		)

		set_backflush_based_on("BOM")

		sco = get_subcontracting_order()
		rm_items = get_rm_items(sco.supplied_items)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		make_stock_transfer_entry(
			sco_no=sco.name,
			rm_items=rm_items,
			itemwise_details=copy.deepcopy(itemwise_details),
		)
		scr = make_subcontracting_receipt(sco.name)
		scr.items[0].inv_site = "Site 1"
		scr.save()

		scr.supplied_items[0].inv_site = "Site 1"
		scr.save()

		self.assertEqual(scr.supplied_items[0].inv_site, "Site 1")


def make_return_subcontracting_receipt(**args):
	args = frappe._dict(args)
	return_doc = make_return_doc("Subcontracting Receipt", args.scr_name)
	return_doc.supplier_warehouse = args.supplier_warehouse or args.warehouse or "_Test Warehouse 1 - _TC"

	if args.qty:
		for item in return_doc.items:
			item.qty = args.qty

	if not args.do_not_save:
		return_doc.save()
		if not args.do_not_submit:
			return_doc.submit()

	return_doc.load_from_db()
	return return_doc


def get_items(**args):
	args = frappe._dict(args)
	return [
		{
			"conversion_factor": 1.0,
			"description": "_Test Item",
			"doctype": "Subcontracting Receipt Item",
			"item_code": "_Test Item",
			"item_name": "_Test Item",
			"parentfield": "items",
			"qty": 5.0,
			"rate": 50.0,
			"received_qty": 5.0,
			"rejected_qty": 0.0,
			"stock_uom": "_Test UOM",
			"warehouse": args.warehouse or "_Test Warehouse - _TC",
			"cost_center": args.cost_center or "Main - _TC",
		},
		{
			"conversion_factor": 1.0,
			"description": "_Test Item Home Desktop 100",
			"doctype": "Subcontracting Receipt Item",
			"item_code": "_Test Item Home Desktop 100",
			"item_name": "_Test Item Home Desktop 100",
			"parentfield": "items",
			"qty": 5.0,
			"rate": 50.0,
			"received_qty": 5.0,
			"rejected_qty": 0.0,
			"stock_uom": "_Test UOM",
			"warehouse": args.warehouse or "_Test Warehouse 1 - _TC",
			"cost_center": args.cost_center or "Main - _TC",
		},
	]


def set_service_expense_account(service_item):
	service_expense_account = (
		frappe.get_doc(
			{
				"doctype": "Account",
				"account_name": "_Test Service Expense",
				"account_type": "Expense Account",
				"company": "_Test Company with perpetual inventory",
				"is_group": 0,
				"parent_account": "Indirect Expenses - TCP1",
			}
		)
		.insert(ignore_if_duplicate=True)
		.name
	)

	service_item_doc = frappe.get_doc("Item", service_item)
	service_item_doc.append(
		"item_defaults",
		{
			"company": "_Test Company with perpetual inventory",
			"expense_account": service_expense_account,
			"default_warehouse": "Stores - TCP1",
		},
	)
	service_item_doc.save()

	return service_expense_account


def make_service_receipt(service_item, fg_item, qty, rate, fg_item_qty):
	service_items = [
		{
			"warehouse": "Stores - TCP1",
			"item_code": service_item,
			"qty": qty,
			"rate": rate,
			"fg_item": fg_item,
			"fg_item_qty": fg_item_qty,
		},
	]
	sco = get_subcontracting_order(
		company="_Test Company with perpetual inventory",
		warehouse="Stores - TCP1",
		supplier_warehouse="Work In Progress - TCP1",
		service_items=service_items,
	)
	rm_items = get_rm_items(sco.supplied_items)
	itemwise_details = make_stock_in_entry(rm_items=rm_items)
	make_stock_transfer_entry(
		sco_no=sco.name,
		rm_items=rm_items,
		itemwise_details=copy.deepcopy(itemwise_details),
	)

	scr = make_subcontracting_receipt(sco.name)
	scr.submit()
	return scr
