# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and contributors
# For license information, please see license.txt

import copy
from collections import defaultdict

import frappe
from frappe.utils import cint, flt

from erpnext.buying.doctype.purchase_order.test_purchase_order import create_purchase_order
from erpnext.controllers.subcontracting_controller import (
	get_materials_from_supplier,
	make_rm_stock_entry,
)
from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom
from erpnext.stock.doctype.item.test_item import make_item
from erpnext.stock.doctype.serial_and_batch_bundle.test_serial_and_batch_bundle import (
	make_serial_batch_bundle,
)
from erpnext.stock.doctype.serial_no.serial_no import get_serial_nos
from erpnext.stock.doctype.stock_entry.test_stock_entry import make_stock_entry
from erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order import (
	make_subcontracting_receipt,
)
from erpnext.tests.utils import ERPNextTestSuite


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

	def test_remove_empty_rows(self):
		sco = get_subcontracting_order()
		len_before = len(sco.service_items)
		sco.service_items[0].item_code = None
		sco.remove_empty_rows()
		self.assertEqual((len_before - 1), len(sco.service_items))

	def test_calculate_additional_costs(self):
		sco = get_subcontracting_order(do_not_submit=1)

		rate_without_additional_cost = sco.items[0].rate
		amount_without_additional_cost = sco.items[0].amount

		additional_amount = 120
		sco.append(
			"additional_costs",
			{
				"expense_account": "Cost of Goods Sold - _TC",
				"description": "Test",
				"amount": additional_amount,
			},
		)
		sco.save()

		additional_cost_per_qty = additional_amount / sco.items[0].qty

		self.assertEqual(sco.items[0].additional_cost_per_qty, additional_cost_per_qty)
		self.assertEqual(rate_without_additional_cost + additional_cost_per_qty, sco.items[0].rate)
		self.assertEqual(amount_without_additional_cost + additional_amount, sco.items[0].amount)

		sco.additional_costs = []
		sco.save()

		self.assertEqual(sco.items[0].additional_cost_per_qty, 0)
		self.assertEqual(rate_without_additional_cost, sco.items[0].rate)
		self.assertEqual(amount_without_additional_cost, sco.items[0].amount)

	def test_create_raw_materials_supplied(self):
		sco = get_subcontracting_order()
		sco.supplied_items = None
		sco.create_raw_materials_supplied_or_received()
		self.assertIsNotNone(sco.supplied_items)

	@ERPNextTestSuite.change_settings("Buying Settings", {"allow_multiple_items": 1})
	def test_sco_with_bom(self):
		"""
		- Set backflush based on BOM.
		- Create SCO for the item Subcontracted Item SA1 and add same item two times.
		- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
		- Create SCR against the SCO and check serial nos and batch no.
		"""

		set_backflush_based_on("BOM")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA1",
				"fg_item_qty": 5,
			},
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 6,
				"rate": 100,
				"fg_item": "Subcontracted Item SA1",
				"fg_item_qty": 6,
			},
		]
		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)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name

		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()

		for key, value in get_supplied_items(scr).items():
			transferred_detais = itemwise_details.get(key)

			for field in ["qty", "serial_no", "batch_no"]:
				if value.get(field):
					transfer, consumed = (transferred_detais.get(field), value.get(field))
					if field == "serial_no":
						transfer, consumed = (sorted(transfer), sorted(consumed))

					self.assertEqual(transfer, consumed)

	def test_sco_with_material_transfer(self):
		"""
		- Set backflush based on Material Transfer.
		- Create SCO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
		- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
		- Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
		- Create partial SCR against the SCO and check serial nos and batch no.
		"""

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA1",
				"fg_item_qty": 5,
			},
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 5",
				"qty": 6,
				"rate": 100,
				"fg_item": "Subcontracted Item SA5",
				"fg_item_qty": 6,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items.append(
			{
				"main_item_code": "Subcontracted Item SA5",
				"item_code": "Subcontracted SRM Item 4",
				"qty": 6,
			}
		)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name

		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.remove(scr1.items[1])
		scr1.save()
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			transferred_detais = itemwise_details.get(key)

			for field in ["qty", "serial_no", "batch_no"]:
				if value.get(field):
					self.assertEqual(value.get(field), transferred_detais.get(field))

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

		for key, value in get_supplied_items(scr2).items():
			transferred_detais = itemwise_details.get(key)

			for field in ["qty", "serial_no", "batch_no"]:
				if value.get(field):
					self.assertEqual(value.get(field), transferred_detais.get(field))

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

	def test_subcontracting_with_same_components_different_fg(self):
		"""
		- Set backflush based on Material Transfer.
		- Create SCO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
		- Transfer the components from Stores to Supplier warehouse with serial nos.
		- Transfer extra qty of components for the item Subcontracted Item SA2.
		- Create partial SCR against the SCO and check serial nos.
		"""

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 2",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA2",
				"fg_item_qty": 5,
			},
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 3",
				"qty": 6,
				"rate": 100,
				"fg_item": "Subcontracted Item SA3",
				"fg_item_qty": 6,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items[0]["qty"] += 1
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name

		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.items[0].qty = 3
		scr1.remove(scr1.items[1])
		scr1.save()
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			transferred_detais = itemwise_details.get(key)

			self.assertEqual(value.qty, 4)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4]))

		scr2 = make_subcontracting_receipt(sco.name)
		scr2.items[0].qty = 2
		scr2.remove(scr2.items[1])
		scr2.save()
		scr2.submit()

		for key, value in get_supplied_items(scr2).items():
			transferred_detais = itemwise_details.get(key)

			self.assertEqual(value.qty, 2)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6]))

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

		for key, value in get_supplied_items(scr3).items():
			transferred_detais = itemwise_details.get(key)

			self.assertEqual(value.qty, 6)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))

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

	def test_return_non_consumed_batch_materials(self):
		"""
		- Set backflush based on Material Transfer.
		- Create SCO for item Subcontracted Item SA2.
		- Transfer the batched components from Stores to Supplier warehouse with serial nos.
		- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
		- Create SCR for full qty against the SCO and change the qty of raw material.
		- After that return the non consumed material back to the store from supplier's warehouse.
		"""

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		set_backflush_based_on("Material Transferred for Subcontract")
		service_item = make_item("Subcontracted Service FG Item A", properties={"is_stock_item": 0}).name
		fg_item = make_item(
			"Subcontracted FG Item SA2", properties={"is_stock_item": 1, "is_sub_contracted_item": 1}
		).name
		rm_item = make_item(
			"Subcontracted Batch RM Item SA2",
			properties={
				"is_stock_item": 1,
				"create_new_batch": 1,
				"has_batch_no": 1,
				"batch_number_series": "BATCH-RM-IRM-.####",
			},
		).name

		make_bom(item=fg_item, raw_materials=[rm_item], rate=100, currency="INR")

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": service_item,
				"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)
		rm_items[0]["qty"] += 1
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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.supplied_items[0].consumed_qty = 5
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			transferred_detais = itemwise_details.get(key)
			self.assertEqual(value.qty, 5)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))

		sco.load_from_db()
		self.assertEqual(sco.supplied_items[0].consumed_qty, 5)
		frappe.flags.args = frappe._dict(
			subcontract_order=sco.name,
			rm_details=[d.name for d in sco.supplied_items],
			order_doctype=sco.doctype,
		)
		doc = get_materials_from_supplier(sco.name)
		doc.save()
		self.assertEqual(doc.items[0].qty, 1)
		self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
		self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
		self.assertTrue(doc.items[0].batch_no)
		self.assertTrue(doc.items[0].use_serial_batch_fields)
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_return_non_consumed_materials(self):
		"""
		- Set backflush based on Material Transfer.
		- Create SCO for item Subcontracted Item SA2.
		- Transfer the components from Stores to Supplier warehouse with serial nos.
		- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
		- Create SCR for full qty against the SCO and change the qty of raw material.
		- After that return the non consumed material back to the store from supplier's warehouse.
		"""

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 2",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA2",
				"fg_item_qty": 5,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items[0]["qty"] += 1
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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.supplied_items[0].consumed_qty = 5
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			transferred_detais = itemwise_details.get(key)
			self.assertEqual(value.qty, 5)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))

		sco.load_from_db()
		self.assertEqual(sco.supplied_items[0].consumed_qty, 5)
		frappe.flags.args = frappe._dict(
			subcontract_order=sco.name,
			rm_details=[d.name for d in sco.supplied_items],
			order_doctype=sco.doctype,
		)
		doc = get_materials_from_supplier(sco.name)
		self.assertEqual(doc.items[0].qty, 1)
		self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
		self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
		self.assertEqual(
			get_serial_nos(doc.items[0].serial_no),
			itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
		)
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	def test_item_with_batch_based_on_bom(self):
		"""
		- Set backflush based on BOM.
		- Create SCO for item Subcontracted Item SA4 (has batch no).
		- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
		- Transfer the components in multiple batches.
		- Create the 3 SCR against the SCO and split Subcontracted Items into two batches.
		- Keep the qty as 2 for Subcontracted Item in the SCR.
		"""

		set_backflush_based_on("BOM")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 4",
				"qty": 10,
				"rate": 100,
				"fg_item": "Subcontracted Item SA4",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = [
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 1",
				"qty": 10.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 2",
				"qty": 10.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
		]
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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.items[0].qty = 2
		add_second_row_in_scr(scr1)
		scr1.flags.ignore_mandatory = True
		scr1.set_missing_values()
		scr1.save()
		scr1.submit()

		for _key, value in get_supplied_items(scr1).items():
			self.assertEqual(value.qty, 4)

		frappe.flags.add_debugger = True
		scr2 = make_subcontracting_receipt(sco.name)
		scr2.items[0].qty = 2
		add_second_row_in_scr(scr2)
		scr2.flags.ignore_mandatory = True
		scr2.set_missing_values()
		scr2.save()
		scr2.submit()

		for _key, value in get_supplied_items(scr2).items():
			self.assertEqual(value.qty, 4)

		scr3 = make_subcontracting_receipt(sco.name)
		scr3.items[0].qty = 2
		scr3.flags.ignore_mandatory = True
		scr3.set_missing_values()
		scr3.save()
		scr3.submit()

		for _key, value in get_supplied_items(scr3).items():
			self.assertEqual(value.qty, 2)

	def test_item_with_batch_based_on_material_transfer(self):
		"""
		- Set backflush based on Material Transferred for Subcontract.
		- Create SCO for item Subcontracted Item SA4 (has batch no).
		- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
		- Transfer the components in multiple batches with extra 2 qty for the batched item.
		- Create the 3 SCR against the SCO and split Subcontracted Items into two batches.
		- Keep the qty as 2 for Subcontracted Item in the SCR.
		- In the first SCR the batched raw materials will be consumed 2 extra qty.
		"""

		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 4",
				"qty": 10,
				"rate": 100,
				"fg_item": "Subcontracted Item SA4",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = [
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 1",
				"qty": 10.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 2",
				"qty": 10.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
			{
				"main_item_code": "Subcontracted Item SA4",
				"item_code": "Subcontracted SRM Item 3",
				"qty": 3.0,
				"rate": 100.0,
				"stock_uom": "Nos",
				"warehouse": "_Test Warehouse - _TC",
			},
		]
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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.items[0].qty = 2
		add_second_row_in_scr(scr1)
		scr1.flags.ignore_mandatory = True
		scr1.save()
		scr1.set_missing_values()
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			qty = 4 if key != "Subcontracted SRM Item 3" else 6
			self.assertEqual(value.qty, qty)

		scr2 = make_subcontracting_receipt(sco.name)
		scr2.items[0].qty = 2
		add_second_row_in_scr(scr2)
		scr2.flags.ignore_mandatory = True
		scr2.save()
		scr2.set_missing_values()
		scr2.submit()

		for value in get_supplied_items(scr2).values():
			self.assertEqual(value.qty, 4)

		scr3 = make_subcontracting_receipt(sco.name)
		scr3.items[0].qty = 2
		scr3.flags.ignore_mandatory = True
		scr3.save()
		scr3.set_missing_values()
		scr3.submit()

		for value in get_supplied_items(scr3).values():
			self.assertEqual(value.qty, 1)

	def test_partial_transfer_serial_no_components_based_on_material_transfer(self):
		"""
		- Set backflush based on Material Transferred for Subcontract.
		- Create SCO for the item Subcontracted Item SA2.
		- Transfer the partial components from Stores to Supplier warehouse with serial nos.
		- Create partial SCR against the SCO and change the qty manually.
		- Transfer the remaining components from Stores to Supplier warehouse with serial nos.
		- Create SCR for remaining qty against the SCO and change the qty manually.
		"""

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 2",
				"qty": 10,
				"rate": 100,
				"fg_item": "Subcontracted Item SA2",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items[0]["qty"] = 5
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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.items[0].qty = 5
		scr1.flags.ignore_mandatory = True
		scr1.save()
		scr1.set_missing_values()

		for key, value in get_supplied_items(scr1).items():
			details = itemwise_details.get(key)
			self.assertEqual(value.qty, 3)
			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no[0:3]))

		scr1.load_from_db()
		scr1.supplied_items[0].consumed_qty = 5
		scr1.save()
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			details = itemwise_details.get(key)
			self.assertEqual(value.qty, details.qty)
			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))

		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

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

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

		for key, value in get_supplied_items(scr2).items():
			details = itemwise_details.get(key)
			self.assertEqual(value.qty, details.qty)
			self.assertEqual(sorted(value.serial_no), sorted(details.serial_no))

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

	def test_incorrect_serial_no_components_based_on_material_transfer(self):
		"""
		- Set backflush based on Material Transferred for Subcontract.
		- Create SCO for the item Subcontracted Item SA2.
		- Transfer the serialized componenets to the supplier.
		- Create SCR and change the serial no which is not transferred.
		- System should throw the error and not allowed to save the SCR.
		"""

		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 0)
		serial_no = "ABC"
		if not frappe.db.exists("Serial No", serial_no):
			frappe.get_doc(
				{
					"doctype": "Serial No",
					"item_code": "Subcontracted SRM Item 2",
					"serial_no": serial_no,
					"company": "_Test Company",
				}
			).insert()

		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 2",
				"qty": 10,
				"rate": 100,
				"fg_item": "Subcontracted Item SA2",
				"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)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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()
		bundle = frappe.get_doc("Serial and Batch Bundle", scr1.supplied_items[0].serial_and_batch_bundle)
		original_serial_no = ""
		for row in bundle.entries:
			if row.idx == 1:
				original_serial_no = row.serial_no
				row.serial_no = "ABC"
				break

		self.assertRaises(frappe.ValidationError, bundle.save)

		bundle.load_from_db()
		for row in bundle.entries:
			if row.idx == 1:
				row.serial_no = original_serial_no
				break

		bundle.save()
		scr1.load_from_db()
		scr1.save()
		self.delete_bundle_from_scr(scr1)
		scr1.delete()
		frappe.db.set_single_value("Stock Settings", "use_serial_batch_fields", 1)

	@staticmethod
	def delete_bundle_from_scr(scr):
		for row in scr.supplied_items:
			if not row.serial_and_batch_bundle:
				continue

			frappe.delete_doc("Serial and Batch Bundle", row.serial_and_batch_bundle)

	def test_partial_transfer_batch_based_on_material_transfer(self):
		"""
		- Set backflush based on Material Transferred for Subcontract.
		- Create SCO for the item Subcontracted Item SA6.
		- Transfer the partial components from Stores to Supplier warehouse with batch.
		- Create partial SCR against the SCO and change the qty manually.
		- Transfer the remaining components from Stores to Supplier warehouse with batch.
		- Create SCR for remaining qty against the SCO and change the qty manually.
		"""

		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 6",
				"qty": 10,
				"rate": 100,
				"fg_item": "Subcontracted Item SA6",
				"fg_item_qty": 10,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items[0]["qty"] = 5
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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.items[0].qty = 5
		scr1.save()

		for key, value in get_supplied_items(scr1).items():
			details = itemwise_details.get(key)
			self.assertEqual(value.qty, 3)

		scr1.load_from_db()
		scr1.supplied_items[0].consumed_qty = 5
		scr1.save()
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			details = itemwise_details.get(key)
			self.assertEqual(value.qty, details.qty)
			self.assertEqual(value.batch_no, details.batch_no)

		itemwise_details = make_stock_in_entry(rm_items=rm_items)
		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name

		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.submit()

		for key, value in get_supplied_items(scr1).items():
			details = itemwise_details.get(key)
			self.assertEqual(value.qty, details.qty)
			self.assertEqual(value.batch_no, details.batch_no)

	def test_sco_supplied_qty(self):
		"""
		Check if 'Supplied Qty' in SCO's Supplied Items table is reset on submit/cancel.
		"""
		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA1",
				"fg_item_qty": 5,
			},
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 5",
				"qty": 6,
				"rate": 100,
				"fg_item": "Subcontracted Item SA5",
				"fg_item_qty": 6,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = [
			{"item_code": "Subcontracted SRM Item 1", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
			{"item_code": "Subcontracted SRM Item 2", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
			{"item_code": "Subcontracted SRM Item 3", "qty": 5, "main_item_code": "Subcontracted Item SA1"},
			{"item_code": "Subcontracted SRM Item 5", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
			{"item_code": "Subcontracted SRM Item 4", "qty": 6, "main_item_code": "Subcontracted Item SA5"},
		]
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name

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

		sco.reload()
		for item in sco.get("supplied_items"):
			self.assertIn(item.supplied_qty, [5.0, 6.0])

		se.cancel()
		sco.reload()
		for item in sco.get("supplied_items"):
			self.assertEqual(item.supplied_qty, 0.0)

	def test_sco_with_material_transfer_with_use_serial_batch_fields(self):
		"""
		- Set backflush based on Material Transfer.
		- Create SCO for the item Subcontracted Item SA1 and Subcontracted Item SA5.
		- Transfer the components from Stores to Supplier warehouse with batch no and serial nos.
		- Transfer extra item Subcontracted SRM Item 4 for the subcontract item Subcontracted Item SA5.
		- Create partial SCR against the SCO and check serial nos and batch no.
		"""

		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 1",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA1",
				"fg_item_qty": 5,
			},
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 5",
				"qty": 6,
				"rate": 100,
				"fg_item": "Subcontracted Item SA5",
				"fg_item_qty": 6,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items.append(
			{
				"main_item_code": "Subcontracted Item SA5",
				"item_code": "Subcontracted SRM Item 4",
				"qty": 6,
			}
		)
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name

		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.remove(scr1.items[1])
		scr1.save()
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			transferred_detais = itemwise_details.get(key)

			for field in ["qty", "serial_no", "batch_no"]:
				if value.get(field):
					data = value.get(field)
					if field == "serial_no":
						data = sorted(data)

					self.assertEqual(data, transferred_detais.get(field))

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

		for key, value in get_supplied_items(scr2).items():
			transferred_detais = itemwise_details.get(key)

			for field in ["qty", "serial_no", "batch_no"]:
				if value.get(field):
					data = value.get(field)
					if field == "serial_no":
						data = sorted(data)

					self.assertEqual(data, transferred_detais.get(field))

	def test_subcontracting_with_same_components_different_fg_with_serial_batch_fields(self):
		"""
		- Set backflush based on Material Transfer.
		- Create SCO for the item Subcontracted Item SA2 and Subcontracted Item SA3.
		- Transfer the components from Stores to Supplier warehouse with serial nos.
		- Transfer extra qty of components for the item Subcontracted Item SA2.
		- Create partial SCR against the SCO and check serial nos.
		"""

		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 2",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA2",
				"fg_item_qty": 5,
			},
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 3",
				"qty": 6,
				"rate": 100,
				"fg_item": "Subcontracted Item SA3",
				"fg_item_qty": 6,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items[0]["qty"] += 1
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["sco_rm_detail"] = sco.items[0].name if item.get("qty") == 5 else sco.items[1].name
			item["use_serial_batch_fields"] = 1

		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.items[0].qty = 3
		scr1.remove(scr1.items[1])
		scr1.save()
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			transferred_detais = itemwise_details.get(key)

			self.assertEqual(value.qty, 4)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:4]))

		scr2 = make_subcontracting_receipt(sco.name)
		scr2.items[0].qty = 2
		scr2.remove(scr2.items[1])
		scr2.save()
		scr2.submit()

		for key, value in get_supplied_items(scr2).items():
			transferred_detais = itemwise_details.get(key)

			self.assertEqual(value.qty, 2)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[4:6]))

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

		for key, value in get_supplied_items(scr3).items():
			transferred_detais = itemwise_details.get(key)

			self.assertEqual(value.qty, 6)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[6:12]))

	def test_return_non_consumed_materials_with_serial_batch_fields(self):
		"""
		- Set backflush based on Material Transfer.
		- Create SCO for item Subcontracted Item SA2.
		- Transfer the components from Stores to Supplier warehouse with serial nos.
		- Transfer extra qty of component for the subcontracted item Subcontracted Item SA2.
		- Create SCR for full qty against the SCO and change the qty of raw material.
		- After that return the non consumed material back to the store from supplier's warehouse.
		"""

		set_backflush_based_on("Material Transferred for Subcontract")
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 2",
				"qty": 5,
				"rate": 100,
				"fg_item": "Subcontracted Item SA2",
				"fg_item_qty": 5,
			},
		]
		sco = get_subcontracting_order(service_items=service_items)
		rm_items = get_rm_items(sco.supplied_items)
		rm_items[0]["qty"] += 1
		itemwise_details = make_stock_in_entry(rm_items=rm_items)

		for item in rm_items:
			item["use_serial_batch_fields"] = 1
			item["sco_rm_detail"] = sco.items[0].name

		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.supplied_items[0].consumed_qty = 5
		scr1.supplied_items[0].serial_no = "\n".join(
			sorted(itemwise_details.get("Subcontracted SRM Item 2").get("serial_no")[0:5])
		)
		scr1.submit()

		for key, value in get_supplied_items(scr1).items():
			transferred_detais = itemwise_details.get(key)
			self.assertTrue(value.use_serial_batch_fields)
			self.assertEqual(value.qty, 5)
			self.assertEqual(sorted(value.serial_no), sorted(transferred_detais.get("serial_no")[0:5]))

		sco.load_from_db()
		self.assertEqual(sco.supplied_items[0].consumed_qty, 5)
		frappe.flags.args = frappe._dict(
			subcontract_order=sco.name,
			rm_details=[d.name for d in sco.supplied_items],
			order_doctype=sco.doctype,
		)
		doc = get_materials_from_supplier(sco.name)
		self.assertEqual(doc.items[0].qty, 1)
		self.assertEqual(doc.items[0].s_warehouse, "_Test Warehouse 1 - _TC")
		self.assertEqual(doc.items[0].t_warehouse, "_Test Warehouse - _TC")
		self.assertEqual(
			get_serial_nos(doc.items[0].serial_no),
			itemwise_details.get(doc.items[0].item_code)["serial_no"][5:6],
		)

	def test_phantom_bom_explosion(self):
		from erpnext.manufacturing.doctype.bom.test_bom import create_tree_for_phantom_bom_tests

		expected = create_tree_for_phantom_bom_tests()
		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 11",
				"qty": 5,
				"rate": 100,
				"fg_item": "Top Level Parent",
				"fg_item_qty": 5,
			},
		]
		sco = get_subcontracting_order(service_items=service_items, do_not_submit=True)
		sco.items[0].include_exploded_items = 0
		sco.save()
		sco.submit()
		sco.reload()

		self.assertEqual([item.rm_item_code for item in sco.supplied_items], expected)

	def test_co_by_product(self):
		frappe.set_value("UOM", "Nos", "must_be_whole_number", 0)

		fg_item = make_item("FG Item", properties={"is_stock_item": 1, "is_sub_contracted_item": 1}).name
		rm_item = make_item("RM Item", properties={"is_stock_item": 1}).name
		scrap_item = make_item("Scrap Item", properties={"is_stock_item": 1}).name
		make_bom(
			item=fg_item, raw_materials=[rm_item], scrap_items=[scrap_item], process_loss_percentage=10
		).name

		service_items = [
			{
				"warehouse": "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 11",
				"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.get_secondary_items()
		scr1.save()

		self.assertEqual(scr1.items[0].received_qty, 5)
		self.assertEqual(scr1.items[0].process_loss_qty, 0.5)
		self.assertEqual(scr1.items[0].qty, 4.5)
		self.assertEqual(scr1.items[0].rate, 200)
		self.assertEqual(scr1.items[0].amount, 900)

		self.assertEqual(scr1.items[1].item_code, scrap_item)
		self.assertEqual(scr1.items[1].received_qty, 5)
		self.assertEqual(scr1.items[1].process_loss_qty, 0.5)
		self.assertEqual(scr1.items[1].qty, 4.5)
		self.assertEqual(flt(scr1.items[1].rate, 3), 11.111)
		self.assertEqual(scr1.items[1].amount, 50)

		frappe.set_value("UOM", "Nos", "must_be_whole_number", 1)


def add_second_row_in_scr(scr):
	item_dict = {}
	for column in [
		"item_code",
		"item_name",
		"qty",
		"uom",
		"bom",
		"warehouse",
		"stock_uom",
		"subcontracting_order",
		"subcontracting_order_finished_good_item",
		"conversion_factor",
		"rate",
		"expense_account",
		"sco_rm_detail",
	]:
		item_dict[column] = scr.items[0].get(column)

	scr.append("items", item_dict)


def get_supplied_items(scr_doc):
	supplied_items = {}
	for row in scr_doc.get("supplied_items"):
		if row.rm_item_code not in supplied_items:
			supplied_items.setdefault(
				row.rm_item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)})
			)

		details = supplied_items[row.rm_item_code]
		update_item_details(row, details)

	return supplied_items


def make_stock_in_entry(**args):
	args = frappe._dict(args)

	items = {}
	for row in args.rm_items:
		row = frappe._dict(row)

		doc = make_stock_entry(
			target=row.warehouse or "_Test Warehouse - _TC",
			item_code=row.item_code,
			qty=row.qty or 1,
			basic_rate=row.rate or 100,
		)

		if row.item_code not in items:
			items.setdefault(
				row.item_code, frappe._dict({"qty": 0, "serial_no": [], "batch_no": defaultdict(float)})
			)

		child_row = doc.items[0]
		details = items[child_row.item_code]
		update_item_details(child_row, details)

	return items


def update_item_details(child_row, details):
	details.qty += (
		child_row.get("qty") if child_row.doctype == "Stock Entry Detail" else child_row.get("consumed_qty")
	)

	details.use_serial_batch_fields = child_row.get("use_serial_batch_fields")
	if child_row.serial_and_batch_bundle:
		doc = frappe.get_doc("Serial and Batch Bundle", child_row.serial_and_batch_bundle)
		for row in doc.get("entries"):
			if row.serial_no:
				details.serial_no.append(row.serial_no)

			if row.batch_no:
				details.batch_no[row.batch_no] += row.qty * (
					-1 if doc.type_of_transaction == "Outward" else 1
				)
	else:
		if child_row.serial_no:
			details.serial_no.extend(get_serial_nos(child_row.serial_no))

		if child_row.batch_no:
			details.batch_no[child_row.batch_no] += child_row.get("qty") or child_row.get("consumed_qty")


def make_stock_transfer_entry(**args):
	args = frappe._dict(args)

	items = []
	for row in args.rm_items:
		row = frappe._dict(row)

		item = {
			"item_code": row.main_item_code or args.main_item_code,
			"rm_item_code": row.item_code,
			"qty": row.qty or 1,
			"item_name": row.item_code,
			"rate": row.rate or 100,
			"stock_uom": row.stock_uom or "Nos",
			"warehouse": row.warehouse or "_Test Warehouse - _TC",
			"use_serial_batch_fields": row.get("use_serial_batch_fields"),
		}

		item_details = args.itemwise_details.get(row.item_code)

		serial_nos = []
		batches = defaultdict(float)
		if item_details and item_details.serial_no:
			serial_nos = item_details.serial_no[0 : cint(row.qty)]
			item_details.serial_no = list(set(item_details.serial_no) - set(serial_nos))

		if item_details and item_details.batch_no:
			for batch_no, batch_qty in item_details.batch_no.items():
				if batch_qty >= row.qty:
					batches[batch_no] = row.qty
					item_details.batch_no[batch_no] -= row.qty
					if row.get("use_serial_batch_fields"):
						item["batch_no"] = batch_no

					break

		if not row.get("use_serial_batch_fields") and (serial_nos or batches):
			item["serial_and_batch_bundle"] = make_serial_batch_bundle(
				frappe._dict(
					{
						"item_code": row.item_code,
						"warehouse": row.warehouse or "_Test Warehouse - _TC",
						"qty": (row.qty or 1) * -1,
						"batches": batches,
						"serial_nos": serial_nos,
						"voucher_type": "Delivery Note",
						"type_of_transaction": "Outward",
						"do_not_submit": True,
					}
				)
			).name

		if serial_nos and row.get("use_serial_batch_fields"):
			item["serial_no"] = "\n".join(serial_nos)

		items.append(item)

	ste_dict = make_rm_stock_entry(args.sco_no, items)
	doc = frappe.get_doc(ste_dict)
	doc.insert()
	doc.submit()

	return doc


def make_subcontracted_items():
	sub_contracted_items = {
		"Subcontracted Item SA1": {},
		"Subcontracted Item SA2": {},
		"Subcontracted Item SA3": {},
		"Subcontracted Item SA4": {
			"has_batch_no": 1,
			"create_new_batch": 1,
			"batch_number_series": "SBAT.####",
		},
		"Subcontracted Item SA5": {},
		"Subcontracted Item SA6": {},
		"Subcontracted Item SA7": {},
		"Subcontracted Item SA8": {},
		"Subcontracted Item SA9": {"stock_uom": "Litre"},
		"Subcontracted Item SA10": {
			"has_batch_no": 1,
			"create_new_batch": 1,
			"batch_number_series": "SBAT.####",
		},
		"Top Level Parent": {},
	}

	for item, properties in sub_contracted_items.items():
		if not frappe.db.exists("Item", item):
			properties.update({"is_stock_item": 1, "is_sub_contracted_item": 1})
			make_item(item, properties)


def make_raw_materials():
	raw_materials = {
		"Subcontracted SRM Item 1": {},
		"Subcontracted SRM Item 2": {"has_serial_no": 1, "serial_no_series": "SRI.####"},
		"Subcontracted SRM Item 3": {
			"has_batch_no": 1,
			"create_new_batch": 1,
			"batch_number_series": "BAT.####",
		},
		"Subcontracted SRM Item 4": {"has_serial_no": 1, "serial_no_series": "SRII.####"},
		"Subcontracted SRM Item 5": {"has_serial_no": 1, "serial_no_series": "SRIID.####"},
		"Subcontracted SRM Item 8": {},
		"Subcontracted SRM Item 9": {"stock_uom": "Litre"},
		"Subcontracted SRM Item 10": {},
	}

	for item, properties in raw_materials.items():
		if not frappe.db.exists("Item", item):
			properties.update({"is_stock_item": 1})
			properties.update({"valuation_rate": 100})
			make_item(item, properties)


def make_service_item(item, properties=None):
	if properties is None:
		properties = {}
	if not frappe.db.exists("Item", item):
		properties.update({"is_stock_item": 0})
		make_item(item, properties)


def make_service_items():
	service_items = {
		"Subcontracted Service Item 1": {},
		"Subcontracted Service Item 2": {},
		"Subcontracted Service Item 3": {},
		"Subcontracted Service Item 4": {},
		"Subcontracted Service Item 5": {},
		"Subcontracted Service Item 6": {},
		"Subcontracted Service Item 7": {},
		"Subcontracted Service Item 8": {},
		"Subcontracted Service Item 9": {},
		"Subcontracted Service Item 10": {},
		"Subcontracted Service Item 11": {},
	}

	for item, properties in service_items.items():
		make_service_item(item, properties)


def make_bom_for_subcontracted_items():
	boms = {
		"Subcontracted Item SA1": [
			"Subcontracted SRM Item 1",
			"Subcontracted SRM Item 2",
			"Subcontracted SRM Item 3",
		],
		"Subcontracted Item SA2": ["Subcontracted SRM Item 2"],
		"Subcontracted Item SA3": ["Subcontracted SRM Item 2"],
		"Subcontracted Item SA4": [
			"Subcontracted SRM Item 1",
			"Subcontracted SRM Item 2",
			"Subcontracted SRM Item 3",
		],
		"Subcontracted Item SA5": ["Subcontracted SRM Item 5"],
		"Subcontracted Item SA6": ["Subcontracted SRM Item 3"],
		"Subcontracted Item SA7": ["Subcontracted SRM Item 1"],
		"Subcontracted Item SA8": ["Subcontracted SRM Item 8"],
		"Subcontracted Item SA10": ["Subcontracted SRM Item 10"],
		"Subcontracted Service Item 11": ["Top Level Parent"],
	}

	for item_code, raw_materials in boms.items():
		if not frappe.db.exists("BOM", {"item": item_code}):
			make_bom(item=item_code, raw_materials=raw_materials, rate=100, currency="INR")


def set_backflush_based_on(based_on):
	frappe.db.set_single_value("Buying Settings", "backflush_raw_materials_of_subcontract_based_on", based_on)


def get_subcontracting_order(**args):
	from erpnext.subcontracting.doctype.subcontracting_order.test_subcontracting_order import (
		create_subcontracting_order,
	)

	args = frappe._dict(args)

	if args.get("po_name"):
		po = frappe.get_doc("Purchase Order", args.get("po_name"))

		if po.is_subcontracted:
			return create_subcontracting_order(**args)

	if not args.service_items:
		service_items = [
			{
				"warehouse": args.warehouse or "_Test Warehouse - _TC",
				"item_code": "Subcontracted Service Item 7",
				"qty": 10,
				"rate": 100,
				"fg_item": "Subcontracted Item SA7",
				"fg_item_qty": 10,
			},
		]
	else:
		service_items = args.service_items

	po = create_purchase_order(
		rm_items=service_items,
		is_subcontracted=1,
		supplier_warehouse=args.supplier_warehouse or "_Test Warehouse 1 - _TC",
		company=args.company,
	)

	return create_subcontracting_order(po_name=po.name, **args)


def get_rm_items(supplied_items):
	rm_items = []

	for item in supplied_items:
		rm_items.append(
			{
				"main_item_code": item.main_item_code,
				"item_code": item.rm_item_code,
				"qty": item.required_qty,
				"rate": item.rate,
				"stock_uom": item.stock_uom,
				"warehouse": item.reserve_warehouse,
				"use_serial_batch_fields": 0,
			}
		)

	return rm_items


def make_subcontracted_item(**args):
	from erpnext.manufacturing.doctype.production_plan.test_production_plan import make_bom

	args = frappe._dict(args)

	if not frappe.db.exists("Item", args.item_code):
		make_item(
			args.item_code,
			{
				"is_stock_item": 1,
				"is_sub_contracted_item": 1,
				"has_batch_no": args.get("has_batch_no") or 0,
			},
		)

	if not args.raw_materials:
		if not frappe.db.exists("Item", "Test Extra Item 1"):
			make_item(
				"Test Extra Item 1",
				{
					"is_stock_item": 1,
				},
			)

		if not frappe.db.exists("Item", "Test Extra Item 2"):
			make_item(
				"Test Extra Item 2",
				{
					"is_stock_item": 1,
				},
			)

		args.raw_materials = ["_Test Extra Item 1", "Test Extra Item 2"]

	if not frappe.db.get_value("BOM", {"item": args.item_code}, "name"):
		make_bom(item=args.item_code, raw_materials=args.get("raw_materials"))
