Merge pull request #36799 from deepeshgarg007/tds_on_debit_note

fix: Tax withholding reversal on Debit Notes
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json
index a8afb55..3a3b6e3 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/ae_uae_chart_template_standard.json
@@ -437,12 +437,20 @@
             },
             "Sales": {
                 "Sales from Other Regions": {
-                    "Sales from Other Region": {}
+                    "Sales from Other Region": {
+                        "account_type": "Income Account"
+                    }
                 },
                 "Sales of same region": {
-                    "Management Consultancy Fees 1": {},
-                    "Sales Account": {},
-                    "Sales of I/C": {}
+                    "Management Consultancy Fees 1": {
+                        "account_type": "Income Account"
+                    },
+                    "Sales Account": {
+                        "account_type": "Income Account"
+                    },
+                    "Sales of I/C": {
+                        "account_type": "Income Account"
+                    }
                 }
             },
             "root_type": "Income"
diff --git a/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json b/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json
index d1a0def..fb97476 100644
--- a/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json
+++ b/erpnext/accounts/doctype/account/chart_of_accounts/verified/id_chart_of_accounts.json
@@ -69,8 +69,7 @@
                 "Persediaan Barang": {
                     "Persediaan Barang": {
                         "account_number": "1141.000", 
-                        "account_type": "Stock", 
-                        "is_group": 1
+                        "account_type": "Stock"
                     }, 
                     "Uang Muka Pembelian": {
                         "Uang Muka Pembelian": {
@@ -670,7 +669,8 @@
             }, 
             "Penjualan Barang Dagangan": {
                 "Penjualan": {
-                    "account_number": "4110.000"
+                    "account_number": "4110.000",
+                    "account_type": "Income Account"
                 }, 
                 "Potongan Penjualan": {
                     "account_number": "4130.000"
diff --git a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
index 8f9f7ce..c8bf664 100644
--- a/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
+++ b/erpnext/accounts/doctype/payment_entry/test_payment_entry.py
@@ -702,7 +702,50 @@
 		pe2.submit()
 
 		# create return entry against si1
-		create_sales_invoice(is_return=1, return_against=si1.name, qty=-1)
+		cr_note = create_sales_invoice(is_return=1, return_against=si1.name, qty=-1)
+		si1_outstanding = frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount")
+
+		# create JE(credit note) manually against si1 and cr_note
+		je = frappe.get_doc(
+			{
+				"doctype": "Journal Entry",
+				"company": si1.company,
+				"voucher_type": "Credit Note",
+				"posting_date": nowdate(),
+			}
+		)
+		je.append(
+			"accounts",
+			{
+				"account": si1.debit_to,
+				"party_type": "Customer",
+				"party": si1.customer,
+				"debit": 0,
+				"credit": 100,
+				"debit_in_account_currency": 0,
+				"credit_in_account_currency": 100,
+				"reference_type": si1.doctype,
+				"reference_name": si1.name,
+				"cost_center": si1.items[0].cost_center,
+			},
+		)
+		je.append(
+			"accounts",
+			{
+				"account": cr_note.debit_to,
+				"party_type": "Customer",
+				"party": cr_note.customer,
+				"debit": 100,
+				"credit": 0,
+				"debit_in_account_currency": 100,
+				"credit_in_account_currency": 0,
+				"reference_type": cr_note.doctype,
+				"reference_name": cr_note.name,
+				"cost_center": cr_note.items[0].cost_center,
+			},
+		)
+		je.save().submit()
+
 		si1_outstanding = frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount")
 		self.assertEqual(si1_outstanding, -100)
 
diff --git a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
index fc6dbba..ce9579e 100644
--- a/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
+++ b/erpnext/accounts/doctype/payment_ledger_entry/test_payment_ledger_entry.py
@@ -294,7 +294,7 @@
 		cr_note1.return_against = si3.name
 		cr_note1 = cr_note1.save().submit()
 
-		pl_entries = (
+		pl_entries_si3 = (
 			qb.from_(ple)
 			.select(
 				ple.voucher_type,
@@ -309,7 +309,24 @@
 			.run(as_dict=True)
 		)
 
-		expected_values = [
+		pl_entries_cr_note1 = (
+			qb.from_(ple)
+			.select(
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.against_voucher_type,
+				ple.against_voucher_no,
+				ple.amount,
+				ple.delinked,
+			)
+			.where(
+				(ple.against_voucher_type == cr_note1.doctype) & (ple.against_voucher_no == cr_note1.name)
+			)
+			.orderby(ple.creation)
+			.run(as_dict=True)
+		)
+
+		expected_values_for_si3 = [
 			{
 				"voucher_type": si3.doctype,
 				"voucher_no": si3.name,
@@ -317,18 +334,21 @@
 				"against_voucher_no": si3.name,
 				"amount": amount,
 				"delinked": 0,
-			},
+			}
+		]
+		# credit/debit notes post ledger entries against itself
+		expected_values_for_cr_note1 = [
 			{
 				"voucher_type": cr_note1.doctype,
 				"voucher_no": cr_note1.name,
-				"against_voucher_type": si3.doctype,
-				"against_voucher_no": si3.name,
+				"against_voucher_type": cr_note1.doctype,
+				"against_voucher_no": cr_note1.name,
 				"amount": -amount,
 				"delinked": 0,
 			},
 		]
-		self.assertEqual(pl_entries[0], expected_values[0])
-		self.assertEqual(pl_entries[1], expected_values[1])
+		self.assertEqual(pl_entries_si3, expected_values_for_si3)
+		self.assertEqual(pl_entries_cr_note1, expected_values_for_cr_note1)
 
 	def test_je_against_inv_and_note(self):
 		ple = self.ple
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
index 2adc123..7b7ce7a 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.js
@@ -163,6 +163,15 @@
 		this.frm.refresh();
 	}
 
+	invoice_name() {
+		this.frm.trigger("get_unreconciled_entries");
+	}
+
+	payment_name() {
+		this.frm.trigger("get_unreconciled_entries");
+	}
+
+
 	clear_child_tables() {
 		this.frm.clear_table("invoices");
 		this.frm.clear_table("payments");
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
index 5f6c703..b88791d 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.json
@@ -27,8 +27,10 @@
   "bank_cash_account",
   "cost_center",
   "sec_break1",
+  "invoice_name",
   "invoices",
   "column_break_15",
+  "payment_name",
   "payments",
   "sec_break2",
   "allocation"
@@ -137,6 +139,7 @@
    "label": "Minimum Invoice Amount"
   },
   {
+   "default": "50",
    "description": "System will fetch all the entries if limit value is zero.",
    "fieldname": "invoice_limit",
    "fieldtype": "Int",
@@ -167,6 +170,7 @@
    "label": "Maximum Payment Amount"
   },
   {
+   "default": "50",
    "description": "System will fetch all the entries if limit value is zero.",
    "fieldname": "payment_limit",
    "fieldtype": "Int",
@@ -194,13 +198,23 @@
    "label": "Default Advance Account",
    "mandatory_depends_on": "doc.party_type",
    "options": "Account"
+  },
+  {
+   "fieldname": "invoice_name",
+   "fieldtype": "Data",
+   "label": "Filter on Invoice"
+  },
+  {
+   "fieldname": "payment_name",
+   "fieldtype": "Data",
+   "label": "Filter on Payment"
   }
  ],
  "hide_toolbar": 1,
  "icon": "icon-resize-horizontal",
  "issingle": 1,
  "links": [],
- "modified": "2023-06-09 13:02:48.718362",
+ "modified": "2023-08-15 05:35:50.109290",
  "modified_by": "Administrator",
  "module": "Accounts",
  "name": "Payment Reconciliation",
diff --git a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 0c62ba9..7ef5278 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -5,6 +5,7 @@
 import frappe
 from frappe import _, msgprint, qb
 from frappe.model.document import Document
+from frappe.query_builder import Criterion
 from frappe.query_builder.custom import ConstantColumn
 from frappe.utils import flt, fmt_money, get_link_to_form, getdate, nowdate, today
 
@@ -74,6 +75,9 @@
 			}
 		)
 
+		if self.payment_name:
+			condition.update({"name": self.payment_name})
+
 		payment_entries = get_advance_payment_entries(
 			self.party_type,
 			self.party,
@@ -89,6 +93,9 @@
 	def get_jv_entries(self):
 		condition = self.get_conditions()
 
+		if self.payment_name:
+			condition += f" and t1.name like '%%{self.payment_name}%%'"
+
 		if self.get("cost_center"):
 			condition += f" and t2.cost_center = '{self.cost_center}' "
 
@@ -146,6 +153,15 @@
 	def get_return_invoices(self):
 		voucher_type = "Sales Invoice" if self.party_type == "Customer" else "Purchase Invoice"
 		doc = qb.DocType(voucher_type)
+
+		conditions = []
+		conditions.append(doc.docstatus == 1)
+		conditions.append(doc[frappe.scrub(self.party_type)] == self.party)
+		conditions.append(doc.is_return == 1)
+
+		if self.payment_name:
+			conditions.append(doc.name.like(f"%{self.payment_name}%"))
+
 		self.return_invoices = (
 			qb.from_(doc)
 			.select(
@@ -153,11 +169,7 @@
 				doc.name.as_("voucher_no"),
 				doc.return_against,
 			)
-			.where(
-				(doc.docstatus == 1)
-				& (doc[frappe.scrub(self.party_type)] == self.party)
-				& (doc.is_return == 1)
-			)
+			.where(Criterion.all(conditions))
 			.run(as_dict=True)
 		)
 
@@ -174,15 +186,12 @@
 		self.common_filter_conditions.append(ple.account == self.receivable_payable_account)
 
 		self.get_return_invoices()
-		return_invoices = [
-			x for x in self.return_invoices if x.return_against == None or x.return_against == ""
-		]
 
 		outstanding_dr_or_cr = []
-		if return_invoices:
+		if self.return_invoices:
 			ple_query = QueryPaymentLedger()
 			return_outstanding = ple_query.get_voucher_outstandings(
-				vouchers=return_invoices,
+				vouchers=self.return_invoices,
 				common_filter=self.common_filter_conditions,
 				posting_date=self.ple_posting_date_filter,
 				min_outstanding=-(self.minimum_payment_amount) if self.minimum_payment_amount else None,
@@ -226,6 +235,8 @@
 			min_outstanding=self.minimum_invoice_amount if self.minimum_invoice_amount else None,
 			max_outstanding=self.maximum_invoice_amount if self.maximum_invoice_amount else None,
 			accounting_dimensions=self.accounting_dimension_filter_conditions,
+			limit=self.invoice_limit,
+			voucher_no=self.invoice_name,
 		)
 
 		cr_dr_notes = (
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
index 66438a7..efe9741 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.js
@@ -86,8 +86,7 @@
 			}
 		}
 
-		if(doc.docstatus == 1 && doc.outstanding_amount != 0
-			&& !(doc.is_return && doc.return_against) && !doc.on_hold) {
+		if(doc.docstatus == 1 && doc.outstanding_amount != 0 && !doc.on_hold) {
 			this.frm.add_custom_button(
 				__('Payment'),
 				() => this.make_payment_entry(),
diff --git a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
index f334399..9f1224d 100644
--- a/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
+++ b/erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py
@@ -628,9 +628,7 @@
 						"credit_in_account_currency": base_grand_total
 						if self.party_account_currency == self.company_currency
 						else grand_total,
-						"against_voucher": self.return_against
-						if cint(self.is_return) and self.return_against
-						else self.name,
+						"against_voucher": self.name,
 						"against_voucher_type": self.doctype,
 						"project": self.project,
 						"cost_center": self.cost_center,
@@ -1644,12 +1642,8 @@
 				elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
 					self.status = "Unpaid"
 				# Check if outstanding amount is 0 due to debit note issued against invoice
-				elif (
-					outstanding_amount <= 0
-					and self.is_return == 0
-					and frappe.db.get_value(
-						"Purchase Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1}
-					)
+				elif self.is_return == 0 and frappe.db.get_value(
+					"Purchase Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1}
 				):
 					self.status = "Debit Note Issued"
 				elif self.is_return == 1:
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
index a4bcdb4..642e99c 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.js
@@ -98,8 +98,7 @@
 			erpnext.accounts.ledger_preview.show_stock_ledger_preview(this.frm);
 		}
 
-		if (doc.docstatus == 1 && doc.outstanding_amount!=0
-			&& !(cint(doc.is_return) && doc.return_against)) {
+		if (doc.docstatus == 1 && doc.outstanding_amount!=0) {
 			this.frm.add_custom_button(
 				__('Payment'),
 				() => this.make_payment_entry(),
diff --git a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
index 0bc5aa2..fba2fa7 100644
--- a/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/sales_invoice.py
@@ -1104,9 +1104,7 @@
 						"debit_in_account_currency": base_grand_total
 						if self.party_account_currency == self.company_currency
 						else grand_total,
-						"against_voucher": self.return_against
-						if cint(self.is_return) and self.return_against
-						else self.name,
+						"against_voucher": self.name,
 						"against_voucher_type": self.doctype,
 						"cost_center": self.cost_center,
 						"project": self.project,
@@ -1732,12 +1730,8 @@
 				elif outstanding_amount > 0 and getdate(self.due_date) >= getdate():
 					self.status = "Unpaid"
 				# Check if outstanding amount is 0 due to credit note issued against invoice
-				elif (
-					outstanding_amount <= 0
-					and self.is_return == 0
-					and frappe.db.get_value(
-						"Sales Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1}
-					)
+				elif self.is_return == 0 and frappe.db.get_value(
+					"Sales Invoice", {"is_return": 1, "return_against": self.name, "docstatus": 1}
 				):
 					self.status = "Credit Note Issued"
 				elif self.is_return == 1:
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index f9cfe5a..21b39d7 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1500,8 +1500,8 @@
 		self.assertEqual(party_credited, 1000)
 
 		# Check outstanding amount
-		self.assertFalse(si1.outstanding_amount)
-		self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 1500)
+		self.assertEqual(frappe.db.get_value("Sales Invoice", si1.name, "outstanding_amount"), -1000)
+		self.assertEqual(frappe.db.get_value("Sales Invoice", si.name, "outstanding_amount"), 2500)
 
 	def test_gle_made_when_asset_is_returned(self):
 		create_asset_data()
diff --git a/erpnext/accounts/general_ledger.py b/erpnext/accounts/general_ledger.py
index 3803836..d496778 100644
--- a/erpnext/accounts/general_ledger.py
+++ b/erpnext/accounts/general_ledger.py
@@ -539,6 +539,10 @@
 		"Company", company, ["round_off_account", "round_off_cost_center"]
 	) or [None, None]
 
+	# Use expense account as fallback
+	if not round_off_account:
+		round_off_account = frappe.get_cached_value("Company", company, "default_expense_account")
+
 	meta = frappe.get_meta(voucher_type)
 
 	# Give first preference to parent cost center for round off GLE
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index a7b35a5..751063a 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -214,8 +214,8 @@
 		for party_type in self.party_type:
 			if self.filters.get(scrub(party_type)):
 				amount = ple.amount_in_account_currency
-		else:
-			amount = ple.amount
+			else:
+				amount = ple.amount
 		amount_in_account_currency = ple.amount_in_account_currency
 
 		# update voucher
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index bccf6f1..1aefeaa 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -908,7 +908,9 @@
 	min_outstanding=None,
 	max_outstanding=None,
 	accounting_dimensions=None,
-	vouchers=None,
+	vouchers=None,  # list of dicts [{'voucher_type': '', 'voucher_no': ''}] for filtering
+	limit=None,  # passed by reconciliation tool
+	voucher_no=None,  # filter passed by reconciliation tool
 ):
 
 	ple = qb.DocType("Payment Ledger Entry")
@@ -941,6 +943,8 @@
 		max_outstanding=max_outstanding,
 		get_invoices=True,
 		accounting_dimensions=accounting_dimensions or [],
+		limit=limit,
+		voucher_no=voucher_no,
 	)
 
 	for d in invoice_list:
@@ -1678,12 +1682,13 @@
 		self.voucher_posting_date = []
 		self.min_outstanding = None
 		self.max_outstanding = None
+		self.limit = self.voucher_no = None
 
 	def reset(self):
 		# clear filters
 		self.vouchers.clear()
 		self.common_filter.clear()
-		self.min_outstanding = self.max_outstanding = None
+		self.min_outstanding = self.max_outstanding = self.limit = None
 
 		# clear result
 		self.voucher_outstandings.clear()
@@ -1697,6 +1702,7 @@
 
 		filter_on_voucher_no = []
 		filter_on_against_voucher_no = []
+
 		if self.vouchers:
 			voucher_types = set([x.voucher_type for x in self.vouchers])
 			voucher_nos = set([x.voucher_no for x in self.vouchers])
@@ -1707,6 +1713,10 @@
 			filter_on_against_voucher_no.append(ple.against_voucher_type.isin(voucher_types))
 			filter_on_against_voucher_no.append(ple.against_voucher_no.isin(voucher_nos))
 
+		if self.voucher_no:
+			filter_on_voucher_no.append(ple.voucher_no.like(f"%{self.voucher_no}%"))
+			filter_on_against_voucher_no.append(ple.against_voucher_no.like(f"%{self.voucher_no}%"))
+
 		# build outstanding amount filter
 		filter_on_outstanding_amount = []
 		if self.min_outstanding:
@@ -1822,6 +1832,11 @@
 				)
 			)
 
+		if self.limit:
+			self.cte_query_voucher_amount_and_outstanding = (
+				self.cte_query_voucher_amount_and_outstanding.limit(self.limit)
+			)
+
 		# execute SQL
 		self.voucher_outstandings = self.cte_query_voucher_amount_and_outstanding.run(as_dict=True)
 
@@ -1835,6 +1850,8 @@
 		get_payments=False,
 		get_invoices=False,
 		accounting_dimensions=None,
+		limit=None,
+		voucher_no=None,
 	):
 		"""
 		Fetch voucher amount and outstanding amount from Payment Ledger using Database CTE
@@ -1856,6 +1873,8 @@
 		self.max_outstanding = max_outstanding
 		self.get_payments = get_payments
 		self.get_invoices = get_invoices
+		self.limit = limit
+		self.voucher_no = voucher_no
 		self.query_for_outstanding()
 
 		return self.voucher_outstandings
diff --git a/erpnext/assets/doctype/asset/asset.js b/erpnext/assets/doctype/asset/asset.js
index 0a2f61d..962292b 100644
--- a/erpnext/assets/doctype/asset/asset.js
+++ b/erpnext/assets/doctype/asset/asset.js
@@ -228,15 +228,19 @@
 				{name: __("Schedule Date"), editable: false, resizable: false, width: 270},
 				{name: __("Depreciation Amount"), editable: false, resizable: false, width: 164},
 				{name: __("Accumulated Depreciation Amount"), editable: false, resizable: false, width: 164},
-				{name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 312}
+				{name: __("Journal Entry"), editable: false, resizable: false, format: value => `<a href="/app/journal-entry/${value}">${value}</a>`, width: 304}
 			],
 			data: data,
+			layout: "fluid",
 			serialNoColumn: false,
 			checkboxColumn: true,
 			cellHeight: 35
 		});
 
-		datatable.style.setStyle(`.dt-scrollable`, {'font-size': '0.75rem', 'margin-bottom': '1rem'});
+		datatable.style.setStyle(`.dt-scrollable`, {'font-size': '0.75rem', 'margin-bottom': '1rem', 'margin-left': '0.35rem', 'margin-right': '0.35rem'});
+		datatable.style.setStyle(`.dt-header`, {'margin-left': '0.35rem', 'margin-right': '0.35rem'});
+		datatable.style.setStyle(`.dt-cell--header`, {'color': 'var(--text-muted)'});
+		datatable.style.setStyle(`.dt-cell`, {'color': 'var(--text-color)'});
 		datatable.style.setStyle(`.dt-cell--col-1`, {'text-align': 'center'});
 		datatable.style.setStyle(`.dt-cell--col-2`, {'font-weight': 600});
 		datatable.style.setStyle(`.dt-cell--col-3`, {'font-weight': 600});
diff --git a/erpnext/assets/doctype/asset/asset.py b/erpnext/assets/doctype/asset/asset.py
index 2060c6c..ddb09c1 100644
--- a/erpnext/assets/doctype/asset/asset.py
+++ b/erpnext/assets/doctype/asset/asset.py
@@ -56,7 +56,6 @@
 
 	def on_submit(self):
 		self.validate_in_use_date()
-		self.set_status()
 		self.make_asset_movement()
 		if not self.booked_fixed_asset and self.validate_make_gl_entry():
 			self.make_gl_entries()
@@ -72,6 +71,7 @@
 						"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
 					).format(asset_depr_schedules_links)
 				)
+		self.set_status()
 		add_asset_activity(self.name, _("Asset submitted"))
 
 	def on_cancel(self):
@@ -96,11 +96,14 @@
 					"Asset Depreciation Schedules created:<br>{0}<br><br>Please check, edit if needed, and submit the Asset."
 				).format(asset_depr_schedules_links)
 			)
-		if not frappe.db.exists(
-			{
-				"doctype": "Asset Activity",
-				"asset": self.name,
-			}
+		if (
+			not frappe.db.exists(
+				{
+					"doctype": "Asset Activity",
+					"asset": self.name,
+				}
+			)
+			and not self.flags.asset_created_via_asset_capitalization
 		):
 			add_asset_activity(self.name, _("Asset created"))
 
diff --git a/erpnext/assets/doctype/asset/test_asset.py b/erpnext/assets/doctype/asset/test_asset.py
index cd66f1d..90eae2d 100644
--- a/erpnext/assets/doctype/asset/test_asset.py
+++ b/erpnext/assets/doctype/asset/test_asset.py
@@ -19,6 +19,7 @@
 from erpnext.accounts.doctype.journal_entry.test_journal_entry import make_journal_entry
 from erpnext.accounts.doctype.purchase_invoice.test_purchase_invoice import make_purchase_invoice
 from erpnext.assets.doctype.asset.asset import (
+	get_asset_value_after_depreciation,
 	make_sales_invoice,
 	split_asset,
 	update_maintenance_status,
diff --git a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
index 324b739..0bf2fbb 100644
--- a/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
+++ b/erpnext/assets/doctype/asset_capitalization/asset_capitalization.py
@@ -509,6 +509,7 @@
 		asset_doc.gross_purchase_amount = total_target_asset_value
 		asset_doc.purchase_receipt_amount = total_target_asset_value
 		asset_doc.flags.ignore_validate = True
+		asset_doc.flags.asset_created_via_asset_capitalization = True
 		asset_doc.insert()
 
 		self.target_asset = asset_doc.name
diff --git a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 39ebd4e..83350aa 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -107,7 +107,7 @@
 			have_asset_details_been_modified, not_manual_depr_or_have_manual_depr_details_been_modified
 		):
 			self.make_depr_schedule(asset_doc, row, date_of_disposal, update_asset_finance_book_row)
-			self.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+			self.set_accumulated_depreciation(asset_doc, row, date_of_disposal, date_of_return)
 
 	def have_asset_details_been_modified(self, asset_doc):
 		return (
@@ -157,7 +157,12 @@
 		self.status = "Draft"
 
 	def make_depr_schedule(
-		self, asset_doc, row, date_of_disposal, update_asset_finance_book_row=True
+		self,
+		asset_doc,
+		row,
+		date_of_disposal,
+		update_asset_finance_book_row=True,
+		value_after_depreciation=None,
 	):
 		if not self.get("depreciation_schedule"):
 			self.depreciation_schedule = []
@@ -167,7 +172,9 @@
 
 		start = self.clear_depr_schedule()
 
-		self._make_depr_schedule(asset_doc, row, start, date_of_disposal, update_asset_finance_book_row)
+		self._make_depr_schedule(
+			asset_doc, row, start, date_of_disposal, update_asset_finance_book_row, value_after_depreciation
+		)
 
 	def clear_depr_schedule(self):
 		start = 0
@@ -187,23 +194,30 @@
 		return start
 
 	def _make_depr_schedule(
-		self, asset_doc, row, start, date_of_disposal, update_asset_finance_book_row
+		self,
+		asset_doc,
+		row,
+		start,
+		date_of_disposal,
+		update_asset_finance_book_row,
+		value_after_depreciation,
 	):
 		asset_doc.validate_asset_finance_books(row)
 
-		value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
+		if not value_after_depreciation:
+			value_after_depreciation = _get_value_after_depreciation_for_making_schedule(asset_doc, row)
 		row.value_after_depreciation = value_after_depreciation
 
 		if update_asset_finance_book_row:
 			row.db_update()
 
-		number_of_pending_depreciations = cint(row.total_number_of_depreciations) - cint(
+		final_number_of_depreciations = cint(row.total_number_of_depreciations) - cint(
 			self.number_of_depreciations_booked
 		)
 
 		has_pro_rata = _check_is_pro_rata(asset_doc, row)
 		if has_pro_rata:
-			number_of_pending_depreciations += 1
+			final_number_of_depreciations += 1
 
 		has_wdv_or_dd_non_yearly_pro_rata = False
 		if (
@@ -219,7 +233,9 @@
 
 		depreciation_amount = 0
 
-		for n in range(start, number_of_pending_depreciations):
+		number_of_pending_depreciations = final_number_of_depreciations - start
+
+		for n in range(start, final_number_of_depreciations):
 			# If depreciation is already completed (for double declining balance)
 			if skip_row:
 				continue
@@ -236,10 +252,11 @@
 				n,
 				prev_depreciation_amount,
 				has_wdv_or_dd_non_yearly_pro_rata,
+				number_of_pending_depreciations,
 			)
 
 			if not has_pro_rata or (
-				n < (cint(number_of_pending_depreciations) - 1) or number_of_pending_depreciations == 2
+				n < (cint(final_number_of_depreciations) - 1) or final_number_of_depreciations == 2
 			):
 				schedule_date = add_months(
 					row.depreciation_start_date, n * cint(row.frequency_of_depreciation)
@@ -310,7 +327,7 @@
 				)
 
 			# For last row
-			elif has_pro_rata and n == cint(number_of_pending_depreciations) - 1:
+			elif has_pro_rata and n == cint(final_number_of_depreciations) - 1:
 				if not asset_doc.flags.increase_in_asset_life:
 					# In case of increase_in_asset_life, the asset.to_date is already set on asset_repair submission
 					asset_doc.to_date = add_months(
@@ -343,7 +360,7 @@
 			# Adjust depreciation amount in the last period based on the expected value after useful life
 			if row.expected_value_after_useful_life and (
 				(
-					n == cint(number_of_pending_depreciations) - 1
+					n == cint(final_number_of_depreciations) - 1
 					and value_after_depreciation != row.expected_value_after_useful_life
 				)
 				or value_after_depreciation < row.expected_value_after_useful_life
@@ -392,6 +409,7 @@
 
 	def set_accumulated_depreciation(
 		self,
+		asset_doc,
 		row,
 		date_of_disposal=None,
 		date_of_return=None,
@@ -403,13 +421,21 @@
 			if self.depreciation_method == "Straight Line" or self.depreciation_method == "Manual"
 		]
 
-		accumulated_depreciation = flt(self.opening_accumulated_depreciation)
+		accumulated_depreciation = None
 		value_after_depreciation = flt(row.value_after_depreciation)
 
 		for i, d in enumerate(self.get("depreciation_schedule")):
 			if ignore_booked_entry and d.journal_entry:
 				continue
 
+			if not accumulated_depreciation:
+				if i > 0 and asset_doc.flags.decrease_in_asset_value_due_to_value_adjustment:
+					accumulated_depreciation = self.get("depreciation_schedule")[
+						i - 1
+					].accumulated_depreciation_amount
+				else:
+					accumulated_depreciation = flt(self.opening_accumulated_depreciation)
+
 			depreciation_amount = flt(d.depreciation_amount, d.precision("depreciation_amount"))
 			value_after_depreciation -= flt(depreciation_amount)
 
@@ -507,9 +533,12 @@
 	schedule_idx=0,
 	prev_depreciation_amount=0,
 	has_wdv_or_dd_non_yearly_pro_rata=False,
+	number_of_pending_depreciations=0,
 ):
 	if fb_row.depreciation_method in ("Straight Line", "Manual"):
-		return get_straight_line_or_manual_depr_amount(asset, fb_row, schedule_idx)
+		return get_straight_line_or_manual_depr_amount(
+			asset, fb_row, schedule_idx, number_of_pending_depreciations
+		)
 	else:
 		rate_of_depreciation = get_updated_rate_of_depreciation_for_wdv_and_dd(
 			asset, depreciable_value, fb_row
@@ -529,7 +558,9 @@
 	return fb_row.rate_of_depreciation
 
 
-def get_straight_line_or_manual_depr_amount(asset, row, schedule_idx):
+def get_straight_line_or_manual_depr_amount(
+	asset, row, schedule_idx, number_of_pending_depreciations
+):
 	# if the Depreciation Schedule is being modified after Asset Repair due to increase in asset life and value
 	if asset.flags.increase_in_asset_life:
 		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / (
@@ -540,6 +571,36 @@
 		return (flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)) / flt(
 			row.total_number_of_depreciations
 		)
+	# if the Depreciation Schedule is being modified after Asset Value Adjustment due to decrease in asset value
+	elif asset.flags.decrease_in_asset_value_due_to_value_adjustment:
+		if row.daily_depreciation:
+			daily_depr_amount = (
+				flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
+			) / date_diff(
+				add_months(
+					row.depreciation_start_date,
+					flt(row.total_number_of_depreciations - asset.number_of_depreciations_booked)
+					* row.frequency_of_depreciation,
+				),
+				add_months(
+					row.depreciation_start_date,
+					flt(
+						row.total_number_of_depreciations
+						- asset.number_of_depreciations_booked
+						- number_of_pending_depreciations
+					)
+					* row.frequency_of_depreciation,
+				),
+			)
+			to_date = add_months(row.depreciation_start_date, schedule_idx * row.frequency_of_depreciation)
+			from_date = add_months(
+				row.depreciation_start_date, (schedule_idx - 1) * row.frequency_of_depreciation
+			)
+			return daily_depr_amount * date_diff(to_date, from_date)
+		else:
+			return (
+				flt(row.value_after_depreciation) - flt(row.expected_value_after_useful_life)
+			) / number_of_pending_depreciations
 	# if the Depreciation Schedule is being prepared for the first time
 	else:
 		if row.daily_depreciation:
@@ -669,7 +730,12 @@
 
 
 def make_new_active_asset_depr_schedules_and_cancel_current_ones(
-	asset_doc, notes, date_of_disposal=None, date_of_return=None
+	asset_doc,
+	notes,
+	date_of_disposal=None,
+	date_of_return=None,
+	value_after_depreciation=None,
+	ignore_booked_entry=False,
 ):
 	for row in asset_doc.get("finance_books"):
 		current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
@@ -695,8 +761,12 @@
 			row.rate_of_depreciation = new_rate_of_depreciation
 			new_asset_depr_schedule_doc.rate_of_depreciation = new_rate_of_depreciation
 
-		new_asset_depr_schedule_doc.make_depr_schedule(asset_doc, row, date_of_disposal)
-		new_asset_depr_schedule_doc.set_accumulated_depreciation(row, date_of_disposal, date_of_return)
+		new_asset_depr_schedule_doc.make_depr_schedule(
+			asset_doc, row, date_of_disposal, value_after_depreciation=value_after_depreciation
+		)
+		new_asset_depr_schedule_doc.set_accumulated_depreciation(
+			asset_doc, row, date_of_disposal, date_of_return, ignore_booked_entry
+		)
 
 		new_asset_depr_schedule_doc.notes = notes
 
@@ -709,9 +779,20 @@
 def get_temp_asset_depr_schedule_doc(
 	asset_doc, row, date_of_disposal=None, date_of_return=None, update_asset_finance_book_row=False
 ):
-	asset_depr_schedule_doc = frappe.new_doc("Asset Depreciation Schedule")
+	current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
+		asset_doc.name, "Active", row.finance_book
+	)
 
-	asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(
+	if not current_asset_depr_schedule_doc:
+		frappe.throw(
+			_("Asset Depreciation Schedule not found for Asset {0} and Finance Book {1}").format(
+				asset_doc.name, row.finance_book
+			)
+		)
+
+	temp_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
+
+	temp_asset_depr_schedule_doc.prepare_draft_asset_depr_schedule_data(
 		asset_doc,
 		row,
 		date_of_disposal,
@@ -719,7 +800,7 @@
 		update_asset_finance_book_row,
 	)
 
-	return asset_depr_schedule_doc
+	return temp_asset_depr_schedule_doc
 
 
 @frappe.whitelist()
diff --git a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
index 823b6e9..9be7243 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/asset_value_adjustment.py
@@ -5,7 +5,7 @@
 import frappe
 from frappe import _
 from frappe.model.document import Document
-from frappe.utils import date_diff, flt, formatdate, get_link_to_form, getdate
+from frappe.utils import flt, formatdate, get_link_to_form, getdate
 
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
 	get_checks_for_pl_and_bs_accounts,
@@ -14,8 +14,7 @@
 from erpnext.assets.doctype.asset.depreciation import get_depreciation_accounts
 from erpnext.assets.doctype.asset_activity.asset_activity import add_asset_activity
 from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
-	get_asset_depr_schedule_doc,
-	get_depreciation_amount,
+	make_new_active_asset_depr_schedules_and_cancel_current_ones,
 )
 
 
@@ -27,7 +26,7 @@
 
 	def on_submit(self):
 		self.make_depreciation_entry()
-		self.reschedule_depreciations(self.new_asset_value)
+		self.update_asset(self.new_asset_value)
 		add_asset_activity(
 			self.asset,
 			_("Asset's value adjusted after submission of Asset Value Adjustment {0}").format(
@@ -36,7 +35,7 @@
 		)
 
 	def on_cancel(self):
-		self.reschedule_depreciations(self.current_asset_value)
+		self.update_asset(self.current_asset_value)
 		add_asset_activity(
 			self.asset,
 			_("Asset's value adjusted after cancellation of Asset Value Adjustment {0}").format(
@@ -124,73 +123,33 @@
 
 		self.db_set("journal_entry", je.name)
 
-	def reschedule_depreciations(self, asset_value):
+	def update_asset(self, asset_value):
 		asset = frappe.get_doc("Asset", self.asset)
-		country = frappe.get_value("Company", self.company, "country")
 
-		for d in asset.finance_books:
-			d.value_after_depreciation = asset_value
+		if not asset.calculate_depreciation:
+			asset.value_after_depreciation = asset_value
+			asset.save()
+			return
 
-			current_asset_depr_schedule_doc = get_asset_depr_schedule_doc(
-				asset.name, "Active", d.finance_book
+		asset.flags.decrease_in_asset_value_due_to_value_adjustment = True
+
+		if self.docstatus == 1:
+			notes = _(
+				"This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
+			).format(
+				get_link_to_form("Asset", asset.name),
+				get_link_to_form(self.get("doctype"), self.get("name")),
+			)
+		elif self.docstatus == 2:
+			notes = _(
+				"This schedule was created when Asset {0}'s Asset Value Adjustment {1} was cancelled."
+			).format(
+				get_link_to_form("Asset", asset.name),
+				get_link_to_form(self.get("doctype"), self.get("name")),
 			)
 
-			new_asset_depr_schedule_doc = frappe.copy_doc(current_asset_depr_schedule_doc)
-			new_asset_depr_schedule_doc.status = "Draft"
-			new_asset_depr_schedule_doc.docstatus = 0
-
-			current_asset_depr_schedule_doc.flags.should_not_cancel_depreciation_entries = True
-			current_asset_depr_schedule_doc.cancel()
-
-			if self.docstatus == 1:
-				notes = _(
-					"This schedule was created when Asset {0} was adjusted through Asset Value Adjustment {1}."
-				).format(
-					get_link_to_form(asset.doctype, asset.name),
-					get_link_to_form(self.get("doctype"), self.get("name")),
-				)
-			elif self.docstatus == 2:
-				notes = _(
-					"This schedule was created when Asset {0}'s Asset Value Adjustment {1} was cancelled."
-				).format(
-					get_link_to_form(asset.doctype, asset.name),
-					get_link_to_form(self.get("doctype"), self.get("name")),
-				)
-			new_asset_depr_schedule_doc.notes = notes
-
-			new_asset_depr_schedule_doc.insert()
-
-			depr_schedule = new_asset_depr_schedule_doc.get("depreciation_schedule")
-
-			if d.depreciation_method in ("Straight Line", "Manual"):
-				end_date = max(s.schedule_date for s in depr_schedule)
-				total_days = date_diff(end_date, self.date)
-				rate_per_day = flt(d.value_after_depreciation - d.expected_value_after_useful_life) / flt(
-					total_days
-				)
-				from_date = self.date
-			else:
-				no_of_depreciations = len([s.name for s in depr_schedule if not s.journal_entry])
-
-			value_after_depreciation = d.value_after_depreciation
-			for data in depr_schedule:
-				if not data.journal_entry:
-					if d.depreciation_method in ("Straight Line", "Manual"):
-						days = date_diff(data.schedule_date, from_date)
-						depreciation_amount = days * rate_per_day
-						from_date = data.schedule_date
-					else:
-						depreciation_amount = get_depreciation_amount(asset, value_after_depreciation, d)
-
-					if depreciation_amount:
-						value_after_depreciation -= flt(depreciation_amount)
-						data.depreciation_amount = depreciation_amount
-
-			d.db_update()
-
-			new_asset_depr_schedule_doc.set_accumulated_depreciation(d, ignore_booked_entry=True)
-			for asset_data in depr_schedule:
-				if not asset_data.journal_entry:
-					asset_data.db_update()
-
-			new_asset_depr_schedule_doc.submit()
+		make_new_active_asset_depr_schedules_and_cancel_current_ones(
+			asset, notes, value_after_depreciation=asset_value, ignore_booked_entry=True
+		)
+		asset.flags.ignore_validate_update_after_submit = True
+		asset.save()
diff --git a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
index 0b3dcba..5d49759 100644
--- a/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
+++ b/erpnext/assets/doctype/asset_value_adjustment/test_asset_value_adjustment.py
@@ -4,9 +4,10 @@
 import unittest
 
 import frappe
-from frappe.utils import add_days, get_last_day, nowdate
+from frappe.utils import add_days, cstr, get_last_day, getdate, nowdate
 
 from erpnext.assets.doctype.asset.asset import get_asset_value_after_depreciation
+from erpnext.assets.doctype.asset.depreciation import post_depreciation_entries
 from erpnext.assets.doctype.asset.test_asset import create_asset_data
 from erpnext.assets.doctype.asset_depreciation_schedule.asset_depreciation_schedule import (
 	get_asset_depr_schedule_doc,
@@ -49,27 +50,23 @@
 
 	def test_asset_depreciation_value_adjustment(self):
 		pr = make_purchase_receipt(
-			item_code="Macbook Pro", qty=1, rate=100000.0, location="Test Location"
+			item_code="Macbook Pro", qty=1, rate=120000.0, location="Test Location"
 		)
 
 		asset_name = frappe.db.get_value("Asset", {"purchase_receipt": pr.name}, "name")
 		asset_doc = frappe.get_doc("Asset", asset_name)
 		asset_doc.calculate_depreciation = 1
+		asset_doc.available_for_use_date = "2023-01-15"
+		asset_doc.purchase_date = "2023-01-15"
 
-		month_end_date = get_last_day(nowdate())
-		purchase_date = nowdate() if nowdate() != month_end_date else add_days(nowdate(), -15)
-
-		asset_doc.available_for_use_date = purchase_date
-		asset_doc.purchase_date = purchase_date
-		asset_doc.calculate_depreciation = 1
 		asset_doc.append(
 			"finance_books",
 			{
 				"expected_value_after_useful_life": 200,
 				"depreciation_method": "Straight Line",
-				"total_number_of_depreciations": 3,
-				"frequency_of_depreciation": 10,
-				"depreciation_start_date": month_end_date,
+				"total_number_of_depreciations": 12,
+				"frequency_of_depreciation": 1,
+				"depreciation_start_date": "2023-01-31",
 			},
 		)
 		asset_doc.submit()
@@ -77,9 +74,15 @@
 		first_asset_depr_schedule = get_asset_depr_schedule_doc(asset_doc.name, "Active")
 		self.assertEquals(first_asset_depr_schedule.status, "Active")
 
+		post_depreciation_entries(getdate("2023-08-21"))
+
 		current_value = get_asset_value_after_depreciation(asset_doc.name)
+
 		adj_doc = make_asset_value_adjustment(
-			asset=asset_doc.name, current_asset_value=current_value, new_asset_value=50000.0
+			asset=asset_doc.name,
+			current_asset_value=current_value,
+			new_asset_value=50000.0,
+			date="2023-08-21",
 		)
 		adj_doc.submit()
 
@@ -90,8 +93,8 @@
 		self.assertEquals(first_asset_depr_schedule.status, "Cancelled")
 
 		expected_gle = (
-			("_Test Accumulated Depreciations - _TC", 0.0, 50000.0),
-			("_Test Depreciations - _TC", 50000.0, 0.0),
+			("_Test Accumulated Depreciations - _TC", 0.0, 4625.29),
+			("_Test Depreciations - _TC", 4625.29, 0.0),
 		)
 
 		gle = frappe.db.sql(
@@ -103,6 +106,29 @@
 
 		self.assertSequenceEqual(gle, expected_gle)
 
+		expected_schedules = [
+			["2023-01-31", 5474.73, 5474.73],
+			["2023-02-28", 9983.33, 15458.06],
+			["2023-03-31", 9983.33, 25441.39],
+			["2023-04-30", 9983.33, 35424.72],
+			["2023-05-31", 9983.33, 45408.05],
+			["2023-06-30", 9983.33, 55391.38],
+			["2023-07-31", 9983.33, 65374.71],
+			["2023-08-31", 8300.0, 73674.71],
+			["2023-09-30", 8300.0, 81974.71],
+			["2023-10-31", 8300.0, 90274.71],
+			["2023-11-30", 8300.0, 98574.71],
+			["2023-12-31", 8300.0, 106874.71],
+			["2024-01-15", 8300.0, 115174.71],
+		]
+
+		schedules = [
+			[cstr(d.schedule_date), d.depreciation_amount, d.accumulated_depreciation_amount]
+			for d in second_asset_depr_schedule.get("depreciation_schedule")
+		]
+
+		self.assertEqual(schedules, expected_schedules)
+
 
 def make_asset_value_adjustment(**args):
 	args = frappe._dict(args)
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.js b/erpnext/buying/doctype/purchase_order/purchase_order.js
index 7c33056..f6a1951 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.js
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.js
@@ -185,8 +185,7 @@
 
 			if(!in_list(["Closed", "Delivered"], doc.status)) {
 				if(this.frm.doc.status !== 'Closed' && flt(this.frm.doc.per_received, 2) < 100 && flt(this.frm.doc.per_billed, 2) < 100) {
-					// Don't add Update Items button if the PO is following the new subcontracting flow.
-					if (!(this.frm.doc.is_subcontracted && !this.frm.doc.is_old_subcontracting_flow)) {
+					if (!this.frm.doc.__onload || this.frm.doc.__onload.can_update_items) {
 						this.frm.add_custom_button(__('Update Items'), () => {
 							erpnext.utils.update_child_items({
 								frm: this.frm,
diff --git a/erpnext/buying/doctype/purchase_order/purchase_order.py b/erpnext/buying/doctype/purchase_order/purchase_order.py
index 06b9d29..3576cd4 100644
--- a/erpnext/buying/doctype/purchase_order/purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/purchase_order.py
@@ -52,6 +52,7 @@
 	def onload(self):
 		supplier_tds = frappe.db.get_value("Supplier", self.supplier, "tax_withholding_category")
 		self.set_onload("supplier_tds", supplier_tds)
+		self.set_onload("can_update_items", self.can_update_items())
 
 	def validate(self):
 		super(PurchaseOrder, self).validate()
@@ -450,6 +451,17 @@
 		else:
 			self.db_set("per_received", 0, update_modified=False)
 
+	def can_update_items(self) -> bool:
+		result = True
+
+		if self.is_subcontracted and not self.is_old_subcontracting_flow:
+			if frappe.db.exists(
+				"Subcontracting Order", {"purchase_order": self.name, "docstatus": ["!=", 2]}
+			):
+				result = False
+
+		return result
+
 
 def item_last_purchase_rate(name, conversion_rate, item_code, conversion_factor=1.0):
 	"""get last purchase rate for an item"""
diff --git a/erpnext/buying/doctype/purchase_order/test_purchase_order.py b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
index 3edaffa..55c01e8 100644
--- a/erpnext/buying/doctype/purchase_order/test_purchase_order.py
+++ b/erpnext/buying/doctype/purchase_order/test_purchase_order.py
@@ -901,6 +901,71 @@
 
 		self.assertRaises(frappe.ValidationError, po.save)
 
+	def test_update_items_for_subcontracting_purchase_order(self):
+		from erpnext.controllers.tests.test_subcontracting_controller import (
+			get_subcontracting_order,
+			make_bom_for_subcontracted_items,
+			make_raw_materials,
+			make_service_items,
+			make_subcontracted_items,
+		)
+
+		def update_items(po, qty):
+			trans_items = [po.items[0].as_dict()]
+			trans_items[0]["qty"] = qty
+			trans_items[0]["fg_item_qty"] = qty
+			trans_items = json.dumps(trans_items, default=str)
+
+			return update_child_qty_rate(
+				po.doctype,
+				trans_items,
+				po.name,
+			)
+
+		make_subcontracted_items()
+		make_raw_materials()
+		make_service_items()
+		make_bom_for_subcontracted_items()
+
+		service_items = [
+			{
+				"warehouse": "_Test Warehouse - _TC",
+				"item_code": "Subcontracted Service Item 7",
+				"qty": 10,
+				"rate": 100,
+				"fg_item": "Subcontracted Item SA7",
+				"fg_item_qty": 10,
+			},
+		]
+		po = create_purchase_order(
+			rm_items=service_items,
+			is_subcontracted=1,
+			supplier_warehouse="_Test Warehouse 1 - _TC",
+		)
+
+		update_items(po, qty=20)
+		po.reload()
+
+		# Test - 1: Items should be updated as there is no Subcontracting Order against PO
+		self.assertEqual(po.items[0].qty, 20)
+		self.assertEqual(po.items[0].fg_item_qty, 20)
+
+		sco = get_subcontracting_order(po_name=po.name, warehouse="_Test Warehouse - _TC")
+
+		# Test - 2: ValidationError should be raised as there is Subcontracting Order against PO
+		self.assertRaises(frappe.ValidationError, update_items, po=po, qty=30)
+
+		sco.reload()
+		sco.cancel()
+		po.reload()
+
+		update_items(po, qty=30)
+		po.reload()
+
+		# Test - 3: Items should be updated as the Subcontracting Order is cancelled
+		self.assertEqual(po.items[0].qty, 30)
+		self.assertEqual(po.items[0].fg_item_qty, 30)
+
 
 def prepare_data_for_internal_transfer():
 	from erpnext.accounts.doctype.sales_invoice.test_sales_invoice import create_internal_supplier
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
index fbfc1ac..06dbd86 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.json
@@ -25,6 +25,7 @@
   "col_break_email_1",
   "html_llwp",
   "send_attached_files",
+  "send_document_print",
   "sec_break_email_2",
   "message_for_supplier",
   "terms_section_break",
@@ -283,13 +284,21 @@
    "fieldname": "send_attached_files",
    "fieldtype": "Check",
    "label": "Send Attached Files"
+  },
+  {
+   "default": "0",
+   "description": "If enabled, a print of this document will be attached to each email",
+   "fieldname": "send_document_print",
+   "fieldtype": "Check",
+   "label": "Send Document Print",
+   "print_hide": 1
   }
  ],
  "icon": "fa fa-shopping-cart",
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2023-08-08 16:30:10.870429",
+ "modified": "2023-08-09 12:20:26.850623",
  "modified_by": "Administrator",
  "module": "Buying",
  "name": "Request for Quotation",
diff --git a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
index 56840c1..6b39982 100644
--- a/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
+++ b/erpnext/buying/doctype/request_for_quotation/request_for_quotation.py
@@ -205,10 +205,24 @@
 		if preview:
 			return {"message": message, "subject": subject}
 
-		attachments = None
+		attachments = []
 		if self.send_attached_files:
 			attachments = self.get_attachments()
 
+		if self.send_document_print:
+			supplier_language = frappe.db.get_value("Supplier", data.supplier, "language")
+			system_language = frappe.db.get_single_value("System Settings", "language")
+			attachments.append(
+				frappe.attach_print(
+					self.doctype,
+					self.name,
+					doc=self,
+					print_format=self.meta.default_print_format or "Standard",
+					lang=supplier_language or system_language,
+					letterhead=self.letter_head,
+				)
+			)
+
 		self.send_email(data, sender, subject, message, attachments)
 
 	def send_email(self, data, sender, subject, message, attachments):
@@ -218,7 +232,6 @@
 			recipients=data.email_id,
 			sender=sender,
 			attachments=attachments,
-			print_format=self.meta.default_print_format or "Standard",
 			send_email=True,
 			doctype=self.doctype,
 			name=self.name,
diff --git a/erpnext/buying/report/procurement_tracker/procurement_tracker.py b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
index 71019e8..a7e03c0 100644
--- a/erpnext/buying/report/procurement_tracker/procurement_tracker.py
+++ b/erpnext/buying/report/procurement_tracker/procurement_tracker.py
@@ -154,31 +154,35 @@
 	procurement_record = []
 	if procurement_record_against_mr:
 		procurement_record += procurement_record_against_mr
+
 	for po in purchase_order_entry:
 		# fetch material records linked to the purchase order item
-		mr_record = mr_records.get(po.material_request_item, [{}])[0]
-		procurement_detail = {
-			"material_request_date": mr_record.get("transaction_date"),
-			"cost_center": po.cost_center,
-			"project": po.project,
-			"requesting_site": po.warehouse,
-			"requestor": po.owner,
-			"material_request_no": po.material_request,
-			"item_code": po.item_code,
-			"quantity": flt(po.qty),
-			"unit_of_measurement": po.stock_uom,
-			"status": po.status,
-			"purchase_order_date": po.transaction_date,
-			"purchase_order": po.parent,
-			"supplier": po.supplier,
-			"estimated_cost": flt(mr_record.get("amount")),
-			"actual_cost": flt(pi_records.get(po.name)),
-			"purchase_order_amt": flt(po.amount),
-			"purchase_order_amt_in_company_currency": flt(po.base_amount),
-			"expected_delivery_date": po.schedule_date,
-			"actual_delivery_date": pr_records.get(po.name),
-		}
-		procurement_record.append(procurement_detail)
+		material_requests = mr_records.get(po.material_request_item, [{}])
+
+		for mr_record in material_requests:
+			procurement_detail = {
+				"material_request_date": mr_record.get("transaction_date"),
+				"cost_center": po.cost_center,
+				"project": po.project,
+				"requesting_site": po.warehouse,
+				"requestor": po.owner,
+				"material_request_no": po.material_request,
+				"item_code": po.item_code,
+				"quantity": flt(po.qty),
+				"unit_of_measurement": po.stock_uom,
+				"status": po.status,
+				"purchase_order_date": po.transaction_date,
+				"purchase_order": po.parent,
+				"supplier": po.supplier,
+				"estimated_cost": flt(mr_record.get("amount")),
+				"actual_cost": flt(pi_records.get(po.name)),
+				"purchase_order_amt": flt(po.amount),
+				"purchase_order_amt_in_company_currency": flt(po.base_amount),
+				"expected_delivery_date": po.schedule_date,
+				"actual_delivery_date": pr_records.get(po.name),
+			}
+			procurement_record.append(procurement_detail)
+
 	return procurement_record
 
 
@@ -301,7 +305,7 @@
 			& (parent.name == child.parent)
 			& (parent.status.notin(("Closed", "Completed", "Cancelled")))
 		)
-		.groupby(parent.name, child.item_code)
+		.groupby(parent.name, child.material_request_item)
 	)
 	query = apply_filters_on_query(filters, parent, child, query)
 
diff --git a/erpnext/controllers/accounts_controller.py b/erpnext/controllers/accounts_controller.py
index 340ec01..955ebef 100644
--- a/erpnext/controllers/accounts_controller.py
+++ b/erpnext/controllers/accounts_controller.py
@@ -2418,6 +2418,9 @@
 		q = q.select((payment_entry.target_exchange_rate).as_("exchange_rate"))
 
 	if condition:
+		if condition.get("name", None):
+			q = q.where(payment_entry.name.like(f"%{condition.get('name')}%"))
+
 		q = q.where(payment_entry.company == condition["company"])
 		q = (
 			q.where(payment_entry.posting_date >= condition["from_payment_date"])
@@ -2855,6 +2858,27 @@
 
 		return update_supplied_items
 
+	def validate_fg_item_for_subcontracting(new_data, is_new):
+		if is_new:
+			if not new_data.get("fg_item"):
+				frappe.throw(
+					_("Finished Good Item is not specified for service item {0}").format(new_data["item_code"])
+				)
+			else:
+				is_sub_contracted_item, default_bom = frappe.db.get_value(
+					"Item", new_data["fg_item"], ["is_sub_contracted_item", "default_bom"]
+				)
+
+				if not is_sub_contracted_item:
+					frappe.throw(
+						_("Finished Good Item {0} must be a sub-contracted item").format(new_data["fg_item"])
+					)
+				elif not default_bom:
+					frappe.throw(_("Default BOM not found for FG Item {0}").format(new_data["fg_item"]))
+
+		if not new_data.get("fg_item_qty"):
+			frappe.throw(_("Finished Good Item {0} Qty can not be zero").format(new_data["fg_item"]))
+
 	data = json.loads(trans_items)
 
 	any_qty_changed = False  # updated to true if any item's qty changes
@@ -2886,6 +2910,7 @@
 
 			prev_rate, new_rate = flt(child_item.get("rate")), flt(d.get("rate"))
 			prev_qty, new_qty = flt(child_item.get("qty")), flt(d.get("qty"))
+			prev_fg_qty, new_fg_qty = flt(child_item.get("fg_item_qty")), flt(d.get("fg_item_qty"))
 			prev_con_fac, new_con_fac = flt(child_item.get("conversion_factor")), flt(
 				d.get("conversion_factor")
 			)
@@ -2898,6 +2923,7 @@
 
 			rate_unchanged = prev_rate == new_rate
 			qty_unchanged = prev_qty == new_qty
+			fg_qty_unchanged = prev_fg_qty == new_fg_qty
 			uom_unchanged = prev_uom == new_uom
 			conversion_factor_unchanged = prev_con_fac == new_con_fac
 			any_conversion_factor_changed |= not conversion_factor_unchanged
@@ -2907,6 +2933,7 @@
 			if (
 				rate_unchanged
 				and qty_unchanged
+				and fg_qty_unchanged
 				and conversion_factor_unchanged
 				and uom_unchanged
 				and date_unchanged
@@ -2917,6 +2944,17 @@
 		if flt(child_item.get("qty")) != flt(d.get("qty")):
 			any_qty_changed = True
 
+		if (
+			parent.doctype == "Purchase Order"
+			and parent.is_subcontracted
+			and not parent.is_old_subcontracting_flow
+		):
+			validate_fg_item_for_subcontracting(d, new_child_flag)
+			child_item.fg_item_qty = flt(d["fg_item_qty"])
+
+			if new_child_flag:
+				child_item.fg_item = d["fg_item"]
+
 		child_item.qty = flt(d.get("qty"))
 		rate_precision = child_item.precision("rate") or 2
 		conv_fac_precision = child_item.precision("conversion_factor") or 2
@@ -3020,11 +3058,20 @@
 		parent.update_ordered_qty()
 		parent.update_ordered_and_reserved_qty()
 		parent.update_receiving_percentage()
-		if parent.is_old_subcontracting_flow:
-			if should_update_supplied_items(parent):
-				parent.update_reserved_qty_for_subcontract()
-				parent.create_raw_materials_supplied()
-			parent.save()
+
+		if parent.is_subcontracted:
+			if parent.is_old_subcontracting_flow:
+				if should_update_supplied_items(parent):
+					parent.update_reserved_qty_for_subcontract()
+					parent.create_raw_materials_supplied()
+				parent.save()
+			else:
+				if not parent.can_update_items():
+					frappe.throw(
+						_(
+							"Items cannot be updated as Subcontracting Order is created against the Purchase Order {0}."
+						).format(frappe.bold(parent.name))
+					)
 	else:  # Sales Order
 		parent.validate_warehouse()
 		parent.update_reserved_qty()
diff --git a/erpnext/controllers/buying_controller.py b/erpnext/controllers/buying_controller.py
index 7b7c53e..b396b27 100644
--- a/erpnext/controllers/buying_controller.py
+++ b/erpnext/controllers/buying_controller.py
@@ -759,7 +759,7 @@
 				"company": self.company,
 				"supplier": self.supplier,
 				"purchase_date": self.posting_date,
-				"calculate_depreciation": 1,
+				"calculate_depreciation": 0,
 				"purchase_receipt_amount": purchase_amount,
 				"gross_purchase_amount": purchase_amount,
 				"asset_quantity": row.qty if is_grouped_asset else 0,
diff --git a/erpnext/controllers/stock_controller.py b/erpnext/controllers/stock_controller.py
index d669abe..ae54b80 100644
--- a/erpnext/controllers/stock_controller.py
+++ b/erpnext/controllers/stock_controller.py
@@ -599,6 +599,7 @@
 		inspection_fieldname_map = {
 			"Purchase Receipt": "inspection_required_before_purchase",
 			"Purchase Invoice": "inspection_required_before_purchase",
+			"Subcontracting Receipt": "inspection_required_before_purchase",
 			"Sales Invoice": "inspection_required_before_delivery",
 			"Delivery Note": "inspection_required_before_delivery",
 		}
diff --git a/erpnext/controllers/tests/test_subcontracting_controller.py b/erpnext/controllers/tests/test_subcontracting_controller.py
index eeb35c4..6b61ae9 100644
--- a/erpnext/controllers/tests/test_subcontracting_controller.py
+++ b/erpnext/controllers/tests/test_subcontracting_controller.py
@@ -1090,7 +1090,7 @@
 		po = frappe.get_doc("Purchase Order", args.get("po_name"))
 
 		if po.is_subcontracted:
-			return create_subcontracting_order(po_name=po.name, **args)
+			return create_subcontracting_order(**args)
 
 	if not args.service_items:
 		service_items = [
diff --git a/erpnext/crm/doctype/opportunity/opportunity.js b/erpnext/crm/doctype/opportunity/opportunity.js
index 6ef8297..0b485bb 100644
--- a/erpnext/crm/doctype/opportunity/opportunity.js
+++ b/erpnext/crm/doctype/opportunity/opportunity.js
@@ -1,7 +1,7 @@
 // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
 // License: GNU General Public License v3. See license.txt
 frappe.provide("erpnext.crm");
-erpnext.pre_sales.set_as_lost("Quotation");
+erpnext.pre_sales.set_as_lost("Opportunity");
 erpnext.sales_common.setup_selling_controller();
 
 
diff --git a/erpnext/manufacturing/doctype/bom/bom.json b/erpnext/manufacturing/doctype/bom/bom.json
index d024022..e8d3542 100644
--- a/erpnext/manufacturing/doctype/bom/bom.json
+++ b/erpnext/manufacturing/doctype/bom/bom.json
@@ -78,6 +78,10 @@
   "show_items",
   "show_operations",
   "web_long_description",
+  "reference_section",
+  "bom_creator",
+  "bom_creator_item",
+  "column_break_oxbz",
   "amended_from",
   "connections_tab"
  ],
@@ -233,7 +237,7 @@
    "fieldname": "rm_cost_as_per",
    "fieldtype": "Select",
    "label": "Rate Of Materials Based On",
-   "options": "Valuation Rate\nLast Purchase Rate\nPrice List"
+   "options": "Valuation Rate\nLast Purchase Rate\nPrice List\nManual"
   },
   {
    "allow_on_submit": 1,
@@ -599,6 +603,32 @@
    "fieldname": "operating_cost_per_bom_quantity",
    "fieldtype": "Currency",
    "label": "Operating Cost Per BOM Quantity"
+  },
+  {
+   "fieldname": "reference_section",
+   "fieldtype": "Section Break",
+   "label": "Reference"
+  },
+  {
+   "fieldname": "bom_creator",
+   "fieldtype": "Link",
+   "label": "BOM Creator",
+   "no_copy": 1,
+   "options": "BOM Creator",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "bom_creator_item",
+   "fieldtype": "Data",
+   "label": "BOM Creator Item",
+   "no_copy": 1,
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_oxbz",
+   "fieldtype": "Column Break"
   }
  ],
  "icon": "fa fa-sitemap",
@@ -606,7 +636,7 @@
  "image_field": "image",
  "is_submittable": 1,
  "links": [],
- "modified": "2023-04-06 12:47:58.514795",
+ "modified": "2023-08-07 11:38:08.152294",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "BOM",
diff --git a/erpnext/manufacturing/doctype/bom/bom.py b/erpnext/manufacturing/doctype/bom/bom.py
index 8058a5f..0231668 100644
--- a/erpnext/manufacturing/doctype/bom/bom.py
+++ b/erpnext/manufacturing/doctype/bom/bom.py
@@ -206,6 +206,7 @@
 
 	def on_submit(self):
 		self.manage_default_bom()
+		self.update_bom_creator_status()
 
 	def on_cancel(self):
 		self.db_set("is_active", 0)
@@ -214,6 +215,23 @@
 		# check if used in any other bom
 		self.validate_bom_links()
 		self.manage_default_bom()
+		self.update_bom_creator_status()
+
+	def update_bom_creator_status(self):
+		if not self.bom_creator:
+			return
+
+		if self.bom_creator_item:
+			frappe.db.set_value(
+				"BOM Creator Item",
+				self.bom_creator_item,
+				"bom_created",
+				1 if self.docstatus == 1 else 0,
+				update_modified=False,
+			)
+
+		doc = frappe.get_doc("BOM Creator", self.bom_creator)
+		doc.set_status(save=True)
 
 	def on_update_after_submit(self):
 		self.validate_bom_links()
@@ -662,18 +680,19 @@
 
 		for d in self.get("items"):
 			old_rate = d.rate
-			d.rate = self.get_rm_rate(
-				{
-					"company": self.company,
-					"item_code": d.item_code,
-					"bom_no": d.bom_no,
-					"qty": d.qty,
-					"uom": d.uom,
-					"stock_uom": d.stock_uom,
-					"conversion_factor": d.conversion_factor,
-					"sourced_by_supplier": d.sourced_by_supplier,
-				}
-			)
+			if self.rm_cost_as_per != "Manual":
+				d.rate = self.get_rm_rate(
+					{
+						"company": self.company,
+						"item_code": d.item_code,
+						"bom_no": d.bom_no,
+						"qty": d.qty,
+						"uom": d.uom,
+						"stock_uom": d.stock_uom,
+						"conversion_factor": d.conversion_factor,
+						"sourced_by_supplier": d.sourced_by_supplier,
+					}
+				)
 
 			d.base_rate = flt(d.rate) * flt(self.conversion_rate)
 			d.amount = flt(d.rate, d.precision("rate")) * flt(d.qty, d.precision("qty"))
@@ -964,7 +983,12 @@
 			.as_("valuation_rate")
 		)
 		.where((bin_table.item_code == item_code) & (wh_table.company == company))
-	).run(as_dict=True)[0]
+	)
+
+	if data.get("set_rate_based_on_warehouse") and data.get("warehouse"):
+		item_valuation = item_valuation.where(bin_table.warehouse == data.get("warehouse"))
+
+	item_valuation = item_valuation.run(as_dict=True)[0]
 
 	valuation_rate = item_valuation.get("valuation_rate")
 
diff --git a/erpnext/manufacturing/doctype/bom_creator/__init__.py b/erpnext/manufacturing/doctype/bom_creator/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator/__init__.py
diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.js b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js
new file mode 100644
index 0000000..01dc89b
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.js
@@ -0,0 +1,201 @@
+// Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+// For license information, please see license.txt
+frappe.provide("erpnext.bom");
+
+frappe.ui.form.on("BOM Creator", {
+	setup(frm) {
+		frm.trigger("set_queries");
+	},
+
+	setup_bom_creator(frm) {
+		frm.dashboard.clear_comment();
+
+		if (!frm.is_new()) {
+			if ((!frappe.bom_configurator
+				|| frappe.bom_configurator.bom_configurator !== frm.doc.name)) {
+				frm.trigger("build_tree");
+			}
+		} else {
+			let $parent = $(frm.fields_dict["bom_creator"].wrapper);
+			$parent.empty();
+			frm.trigger("make_new_entry");
+		}
+	},
+
+	build_tree(frm) {
+		let $parent = $(frm.fields_dict["bom_creator"].wrapper);
+		$parent.empty();
+		frm.toggle_enable("item_code", false);
+
+		frappe.require('bom_configurator.bundle.js').then(() => {
+			frappe.bom_configurator = new frappe.ui.BOMConfigurator({
+				wrapper: $parent,
+				page: $parent,
+				frm: frm,
+				bom_configurator: frm.doc.name,
+			});
+		});
+	},
+
+	make_new_entry(frm) {
+		let dialog = new frappe.ui.Dialog({
+			title: __("Multi-level BOM Creator"),
+			fields: [
+				{
+					label: __("Name"),
+					fieldtype: "Data",
+					fieldname: "name",
+					reqd: 1
+				},
+				{ fieldtype: "Column Break" },
+				{
+					label: __("Company"),
+					fieldtype: "Link",
+					fieldname: "company",
+					options: "Company",
+					reqd: 1,
+					default: frappe.defaults.get_user_default("Company"),
+				},
+				{ fieldtype: "Section Break" },
+				{
+					label: __("Item Code (Final Product)"),
+					fieldtype: "Link",
+					fieldname: "item_code",
+					options: "Item",
+					reqd: 1
+				},
+				{ fieldtype: "Column Break" },
+				{
+					label: __("Quantity"),
+					fieldtype: "Float",
+					fieldname: "qty",
+					reqd: 1,
+					default: 1.0
+				},
+				{ fieldtype: "Section Break" },
+				{
+					label: __("Currency"),
+					fieldtype: "Link",
+					fieldname: "currency",
+					options: "Currency",
+					reqd: 1,
+					default: frappe.defaults.get_global_default("currency")
+				},
+				{ fieldtype: "Column Break" },
+				{
+					label: __("Conversion Rate"),
+					fieldtype: "Float",
+					fieldname: "conversion_rate",
+					reqd: 1,
+					default: 1.0
+				},
+			],
+			primary_action_label: __("Create"),
+			primary_action: (values) => {
+				values.doctype = frm.doc.doctype;
+				frappe.db
+					.insert(values)
+					.then((doc) => {
+						frappe.set_route("Form", doc.doctype, doc.name);
+					});
+			}
+		})
+
+		dialog.show();
+	},
+
+	set_queries(frm) {
+		frm.set_query("bom_no", "items", function(doc, cdt, cdn) {
+			let item = frappe.get_doc(cdt, cdn);
+			return {
+				filters: {
+					item: item.item_code,
+				}
+			}
+		});
+	},
+
+	refresh(frm) {
+		frm.trigger("setup_bom_creator");
+		frm.trigger("set_root_item");
+		frm.trigger("add_custom_buttons");
+	},
+
+	set_root_item(frm) {
+		if (frm.is_new() && frm.doc.items?.length) {
+			frappe.model.set_value(frm.doc.items[0].doctype,
+				frm.doc.items[0].name, "is_root", 1);
+		}
+	},
+
+	add_custom_buttons(frm) {
+		if (!frm.is_new()) {
+			frm.add_custom_button(__("Rebuild Tree"), () => {
+				frm.trigger("build_tree");
+			});
+		}
+	}
+});
+
+frappe.ui.form.on("BOM Creator Item", {
+	item_code(frm, cdt, cdn) {
+		let item = frappe.get_doc(cdt, cdn);
+		if (item.item_code && item.is_root) {
+			frappe.model.set_value(cdt, cdn, "fg_item", item.item_code);
+		}
+	},
+
+	do_not_explode(frm, cdt, cdn) {
+		let item = frappe.get_doc(cdt, cdn);
+		if (!item.do_not_explode) {
+			frm.call({
+				method: "get_default_bom",
+				doc: frm.doc,
+				args: {
+					item_code: item.item_code
+				},
+				callback(r) {
+					if (r.message) {
+						frappe.model.set_value(cdt, cdn, "bom_no", r.message);
+					}
+				}
+			})
+		} else {
+			frappe.model.set_value(cdt, cdn, "bom_no", "");
+		}
+	}
+});
+
+
+erpnext.bom.BomConfigurator = class BomConfigurator extends erpnext.TransactionController {
+	conversion_rate(doc) {
+		if(this.frm.doc.currency === this.get_company_currency()) {
+			this.frm.set_value("conversion_rate", 1.0);
+		} else {
+			erpnext.bom.update_cost(doc);
+		}
+	}
+
+	buying_price_list(doc) {
+		this.apply_price_list();
+	}
+
+	plc_conversion_rate(doc) {
+		if (!this.in_apply_price_list) {
+			this.apply_price_list(null, true);
+		}
+	}
+
+	conversion_factor(doc, cdt, cdn) {
+		if (frappe.meta.get_docfield(cdt, "stock_qty", cdn)) {
+			var item = frappe.get_doc(cdt, cdn);
+			frappe.model.round_floats_in(item, ["qty", "conversion_factor"]);
+			item.stock_qty = flt(item.qty * item.conversion_factor, precision("stock_qty", item));
+			refresh_field("stock_qty", item.name, item.parentfield);
+			this.toggle_conversion_factor(item);
+			this.frm.events.update_cost(this.frm);
+		}
+	}
+};
+
+extend_cscript(cur_frm.cscript, new erpnext.bom.BomConfigurator({frm: cur_frm}));
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.json b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json
new file mode 100644
index 0000000..fb4c6c5
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.json
@@ -0,0 +1,330 @@
+{
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "prompt",
+ "creation": "2023-07-18 14:56:34.477800",
+ "default_view": "List",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+  "tab_2_tab",
+  "bom_creator",
+  "details_tab",
+  "section_break_ylsl",
+  "item_code",
+  "item_name",
+  "item_group",
+  "column_break_ikj7",
+  "qty",
+  "project",
+  "uom",
+  "raw_materials_tab",
+  "currency_detail",
+  "rm_cost_as_per",
+  "set_rate_based_on_warehouse",
+  "buying_price_list",
+  "price_list_currency",
+  "plc_conversion_rate",
+  "column_break_ivyw",
+  "currency",
+  "conversion_rate",
+  "section_break_zcfg",
+  "default_warehouse",
+  "column_break_tzot",
+  "company",
+  "materials_section",
+  "items",
+  "costing_detail",
+  "raw_material_cost",
+  "remarks_tab",
+  "remarks",
+  "section_break_yixm",
+  "status",
+  "column_break_irab",
+  "error_log",
+  "connections_tab",
+  "amended_from"
+ ],
+ "fields": [
+  {
+   "fieldname": "company",
+   "fieldtype": "Link",
+   "label": "Company",
+   "options": "Company",
+   "remember_last_selected_value": 1,
+   "reqd": 1
+  },
+  {
+   "fieldname": "currency_detail",
+   "fieldtype": "Section Break",
+   "label": "Costing"
+  },
+  {
+   "allow_on_submit": 1,
+   "default": "Valuation Rate",
+   "fieldname": "rm_cost_as_per",
+   "fieldtype": "Select",
+   "label": "Rate Of Materials Based On",
+   "options": "Valuation Rate\nLast Purchase Rate\nPrice List\nManual",
+   "reqd": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "depends_on": "eval:doc.rm_cost_as_per===\"Price List\"",
+   "fieldname": "buying_price_list",
+   "fieldtype": "Link",
+   "label": "Price List",
+   "options": "Price List"
+  },
+  {
+   "allow_on_submit": 1,
+   "depends_on": "eval:doc.rm_cost_as_per=='Price List'",
+   "fieldname": "price_list_currency",
+   "fieldtype": "Link",
+   "label": "Price List Currency",
+   "options": "Currency",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "allow_on_submit": 1,
+   "depends_on": "eval:doc.rm_cost_as_per=='Price List'",
+   "fieldname": "plc_conversion_rate",
+   "fieldtype": "Float",
+   "label": "Price List Exchange Rate"
+  },
+  {
+   "fieldname": "column_break_ivyw",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "currency",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Currency",
+   "options": "Currency",
+   "reqd": 1
+  },
+  {
+   "default": "1",
+   "fieldname": "conversion_rate",
+   "fieldtype": "Float",
+   "label": "Conversion Rate",
+   "precision": "9"
+  },
+  {
+   "fieldname": "materials_section",
+   "fieldtype": "Section Break",
+   "oldfieldtype": "Section Break"
+  },
+  {
+   "allow_bulk_edit": 1,
+   "fieldname": "items",
+   "fieldtype": "Table",
+   "label": "Items",
+   "oldfieldname": "bom_materials",
+   "oldfieldtype": "Table",
+   "options": "BOM Creator Item"
+  },
+  {
+   "fieldname": "costing_detail",
+   "fieldtype": "Section Break",
+   "label": "Costing Details"
+  },
+  {
+   "fieldname": "raw_material_cost",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Total Cost",
+   "no_copy": 1,
+   "options": "currency",
+   "read_only": 1
+  },
+  {
+   "fieldname": "remarks",
+   "fieldtype": "Text Editor",
+   "label": "Remarks"
+  },
+  {
+   "fieldname": "column_break_ikj7",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "project",
+   "fieldtype": "Link",
+   "label": "Project",
+   "options": "Project"
+  },
+  {
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "in_standard_filter": 1,
+   "label": "Finished Good",
+   "options": "Item",
+   "reqd": 1
+  },
+  {
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "label": "Quantity",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "item_code.item_name",
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name"
+  },
+  {
+   "fetch_from": "item_code.stock_uom",
+   "fieldname": "uom",
+   "fieldtype": "Link",
+   "label": "UOM",
+   "options": "UOM"
+  },
+  {
+   "fieldname": "tab_2_tab",
+   "fieldtype": "Tab Break",
+   "label": "BOM Tree"
+  },
+  {
+   "fieldname": "details_tab",
+   "fieldtype": "Tab Break",
+   "label": "Final Product"
+  },
+  {
+   "fieldname": "raw_materials_tab",
+   "fieldtype": "Tab Break",
+   "label": "Sub Assemblies & Raw Materials"
+  },
+  {
+   "fieldname": "remarks_tab",
+   "fieldtype": "Tab Break",
+   "label": "Remarks"
+  },
+  {
+   "fieldname": "connections_tab",
+   "fieldtype": "Tab Break",
+   "label": "Connections",
+   "show_dashboard": 1
+  },
+  {
+   "fetch_from": "item_code.item_group",
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "label": "Item Group",
+   "options": "Item Group"
+  },
+  {
+   "fieldname": "amended_from",
+   "fieldtype": "Link",
+   "label": "Amended From",
+   "no_copy": 1,
+   "options": "BOM Creator",
+   "print_hide": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "section_break_zcfg",
+   "fieldtype": "Section Break",
+   "label": "Warehouse"
+  },
+  {
+   "fieldname": "column_break_tzot",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "default_warehouse",
+   "fieldtype": "Link",
+   "label": "Default Source Warehouse",
+   "options": "Warehouse"
+  },
+  {
+   "fieldname": "bom_creator",
+   "fieldtype": "HTML"
+  },
+  {
+   "fieldname": "section_break_ylsl",
+   "fieldtype": "Section Break"
+  },
+  {
+   "default": "0",
+   "depends_on": "eval:doc.rm_cost_as_per === \"Valuation Rate\"",
+   "fieldname": "set_rate_based_on_warehouse",
+   "fieldtype": "Check",
+   "label": "Set Valuation Rate Based on Source Warehouse"
+  },
+  {
+   "fieldname": "section_break_yixm",
+   "fieldtype": "Section Break"
+  },
+  {
+   "default": "Draft",
+   "fieldname": "status",
+   "fieldtype": "Select",
+   "label": "Status",
+   "no_copy": 1,
+   "options": "Draft\nSubmitted\nIn Progress\nCompleted\nFailed\nCancelled",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_irab",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "error_log",
+   "fieldtype": "Text",
+   "label": "Error Log",
+   "read_only": 1
+  }
+ ],
+ "icon": "fa fa-sitemap",
+ "is_submittable": 1,
+ "links": [
+  {
+   "link_doctype": "BOM",
+   "link_fieldname": "bom_creator"
+  }
+ ],
+ "modified": "2023-08-07 15:45:06.176313",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Creator",
+ "naming_rule": "Set by user",
+ "owner": "Administrator",
+ "permissions": [
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Manufacturing Manager",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  },
+  {
+   "cancel": 1,
+   "create": 1,
+   "delete": 1,
+   "email": 1,
+   "print": 1,
+   "read": 1,
+   "report": 1,
+   "role": "Manufacturing User",
+   "share": 1,
+   "submit": 1,
+   "write": 1
+  }
+ ],
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py
new file mode 100644
index 0000000..999d610
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator.py
@@ -0,0 +1,424 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+from collections import OrderedDict
+
+import frappe
+from frappe import _
+from frappe.model.document import Document
+from frappe.utils import flt
+
+from erpnext.manufacturing.doctype.bom.bom import get_bom_item_rate
+
+BOM_FIELDS = [
+	"company",
+	"rm_cost_as_per",
+	"project",
+	"currency",
+	"conversion_rate",
+	"buying_price_list",
+]
+
+BOM_ITEM_FIELDS = [
+	"item_code",
+	"qty",
+	"uom",
+	"rate",
+	"stock_qty",
+	"stock_uom",
+	"conversion_factor",
+	"do_not_explode",
+]
+
+
+class BOMCreator(Document):
+	def before_save(self):
+		self.set_status()
+		self.set_is_expandable()
+		self.set_conversion_factor()
+		self.set_reference_id()
+		self.set_rate_for_items()
+
+	def validate(self):
+		self.validate_items()
+
+	def validate_items(self):
+		for row in self.items:
+			if row.is_expandable and row.item_code == self.item_code:
+				frappe.throw(_("Item {0} cannot be added as a sub-assembly of itself").format(row.item_code))
+
+	def set_status(self, save=False):
+		self.status = {
+			0: "Draft",
+			1: "Submitted",
+			2: "Cancelled",
+		}[self.docstatus]
+
+		self.set_status_completed()
+		if save:
+			self.db_set("status", self.status)
+
+	def set_status_completed(self):
+		if self.docstatus != 1:
+			return
+
+		has_completed = True
+		for row in self.items:
+			if row.is_expandable and not row.bom_created:
+				has_completed = False
+				break
+
+		if not frappe.get_cached_value(
+			"BOM", {"bom_creator": self.name, "item": self.item_code}, "name"
+		):
+			has_completed = False
+
+		if has_completed:
+			self.status = "Completed"
+
+	def on_cancel(self):
+		self.set_status(True)
+
+	def set_conversion_factor(self):
+		for row in self.items:
+			row.conversion_factor = 1.0
+
+	def before_submit(self):
+		self.validate_fields()
+		self.set_status()
+
+	def set_reference_id(self):
+		parent_reference = {row.idx: row.name for row in self.items}
+
+		for row in self.items:
+			if row.fg_reference_id:
+				continue
+
+			if row.parent_row_no:
+				row.fg_reference_id = parent_reference.get(row.parent_row_no)
+
+	@frappe.whitelist()
+	def add_boms(self):
+		self.submit()
+
+	def set_rate_for_items(self):
+		if self.rm_cost_as_per == "Manual":
+			return
+
+		amount = self.get_raw_material_cost()
+		self.raw_material_cost = amount
+
+	def get_raw_material_cost(self, fg_reference_id=None, amount=0):
+		if not fg_reference_id:
+			fg_reference_id = self.name
+
+		for row in self.items:
+			if row.fg_reference_id != fg_reference_id:
+				continue
+
+			if not row.is_expandable:
+				row.rate = get_bom_item_rate(
+					{
+						"company": self.company,
+						"item_code": row.item_code,
+						"bom_no": "",
+						"qty": row.qty,
+						"uom": row.uom,
+						"stock_uom": row.stock_uom,
+						"conversion_factor": row.conversion_factor,
+						"sourced_by_supplier": row.sourced_by_supplier,
+					},
+					self,
+				)
+
+				row.amount = flt(row.rate) * flt(row.qty)
+
+			else:
+				row.amount = 0.0
+				row.amount = self.get_raw_material_cost(row.name, row.amount)
+				row.rate = flt(row.amount) / (flt(row.qty) * flt(row.conversion_factor))
+
+			amount += flt(row.amount)
+
+		return amount
+
+	def set_is_expandable(self):
+		fg_items = [row.fg_item for row in self.items if row.fg_item != self.item_code]
+		for row in self.items:
+			row.is_expandable = 0
+			if row.item_code in fg_items:
+				row.is_expandable = 1
+
+	def validate_fields(self):
+		fields = {
+			"items": "Items",
+		}
+
+		for field, label in fields.items():
+			if not self.get(field):
+				frappe.throw(_("Please set {0} in BOM Creator {1}").format(label, self.name))
+
+	def on_submit(self):
+		self.enqueue_create_boms()
+
+	def enqueue_create_boms(self):
+		frappe.enqueue(
+			self.create_boms,
+			queue="short",
+			timeout=600,
+			is_async=True,
+		)
+
+		frappe.msgprint(
+			_("BOMs creation has been enqueued, kindly check the status after some time"), alert=True
+		)
+
+	def create_boms(self):
+		"""
+		Sample data structure of production_item_wise_rm
+		production_item_wise_rm = {
+		        (fg_item_code, name): {
+		                "items": [],
+		                "bom_no": "",
+		                "fg_item_data": {}
+		        }
+		}
+		"""
+
+		self.db_set("status", "In Progress")
+		production_item_wise_rm = OrderedDict({})
+		production_item_wise_rm.setdefault(
+			(self.item_code, self.name), frappe._dict({"items": [], "bom_no": "", "fg_item_data": self})
+		)
+
+		for row in self.items:
+			if row.is_expandable:
+				if (row.item_code, row.name) not in production_item_wise_rm:
+					production_item_wise_rm.setdefault(
+						(row.item_code, row.name), frappe._dict({"items": [], "bom_no": "", "fg_item_data": row})
+					)
+
+			production_item_wise_rm[(row.fg_item, row.fg_reference_id)]["items"].append(row)
+
+		reverse_tree = OrderedDict(reversed(list(production_item_wise_rm.items())))
+
+		try:
+			for d in reverse_tree:
+				fg_item_data = production_item_wise_rm.get(d).fg_item_data
+				self.create_bom(fg_item_data, production_item_wise_rm)
+
+			frappe.msgprint(_("BOMs created successfully"))
+		except Exception:
+			traceback = frappe.get_traceback()
+			self.db_set(
+				{
+					"status": "Failed",
+					"error_log": traceback,
+				}
+			)
+
+			frappe.msgprint(_("BOMs creation failed"))
+
+	def create_bom(self, row, production_item_wise_rm):
+		bom = frappe.new_doc("BOM")
+		bom.update(
+			{
+				"item": row.item_code,
+				"bom_type": "Production",
+				"quantity": row.qty,
+				"allow_alternative_item": 1,
+				"bom_creator": self.name,
+				"bom_creator_item": row.name if row.name != self.name else "",
+				"rm_cost_as_per": "Manual",
+			}
+		)
+
+		for field in BOM_FIELDS:
+			if self.get(field):
+				bom.set(field, self.get(field))
+
+		for item in production_item_wise_rm[(row.item_code, row.name)]["items"]:
+			bom_no = ""
+			item.do_not_explode = 1
+			if (item.item_code, item.name) in production_item_wise_rm:
+				bom_no = production_item_wise_rm.get((item.item_code, item.name)).bom_no
+				item.do_not_explode = 0
+
+			item_args = {}
+			for field in BOM_ITEM_FIELDS:
+				item_args[field] = item.get(field)
+
+			item_args.update(
+				{
+					"bom_no": bom_no,
+					"allow_alternative_item": 1,
+					"allow_scrap_items": 1,
+					"include_item_in_manufacturing": 1,
+				}
+			)
+
+			bom.append("items", item_args)
+
+		bom.save(ignore_permissions=True)
+		bom.submit()
+
+		production_item_wise_rm[(row.item_code, row.name)].bom_no = bom.name
+
+	@frappe.whitelist()
+	def get_default_bom(self, item_code) -> str:
+		return frappe.get_cached_value("Item", item_code, "default_bom")
+
+
+@frappe.whitelist()
+def get_children(doctype=None, parent=None, **kwargs):
+	if isinstance(kwargs, str):
+		kwargs = frappe.parse_json(kwargs)
+
+	if isinstance(kwargs, dict):
+		kwargs = frappe._dict(kwargs)
+
+	fields = [
+		"item_code as value",
+		"is_expandable as expandable",
+		"parent as parent_id",
+		"qty",
+		"idx",
+		"'BOM Creator Item' as doctype",
+		"name",
+		"uom",
+		"rate",
+		"amount",
+	]
+
+	query_filters = {
+		"fg_item": parent,
+		"parent": kwargs.parent_id,
+	}
+
+	if kwargs.name:
+		query_filters["name"] = kwargs.name
+
+	return frappe.get_all("BOM Creator Item", fields=fields, filters=query_filters, order_by="idx")
+
+
+@frappe.whitelist()
+def add_item(**kwargs):
+	if isinstance(kwargs, str):
+		kwargs = frappe.parse_json(kwargs)
+
+	if isinstance(kwargs, dict):
+		kwargs = frappe._dict(kwargs)
+
+	doc = frappe.get_doc("BOM Creator", kwargs.parent)
+	item_info = get_item_details(kwargs.item_code)
+	kwargs.update(
+		{
+			"uom": item_info.stock_uom,
+			"stock_uom": item_info.stock_uom,
+			"conversion_factor": 1,
+		}
+	)
+
+	doc.append("items", kwargs)
+	doc.save()
+
+	return doc
+
+
+@frappe.whitelist()
+def add_sub_assembly(**kwargs):
+	if isinstance(kwargs, str):
+		kwargs = frappe.parse_json(kwargs)
+
+	if isinstance(kwargs, dict):
+		kwargs = frappe._dict(kwargs)
+
+	doc = frappe.get_doc("BOM Creator", kwargs.parent)
+	bom_item = frappe.parse_json(kwargs.bom_item)
+
+	name = kwargs.fg_reference_id
+	parent_row_no = ""
+	if not kwargs.convert_to_sub_assembly:
+		item_info = get_item_details(bom_item.item_code)
+		item_row = doc.append(
+			"items",
+			{
+				"item_code": bom_item.item_code,
+				"qty": bom_item.qty,
+				"uom": item_info.stock_uom,
+				"fg_item": kwargs.fg_item,
+				"conversion_factor": 1,
+				"fg_reference_id": name,
+				"stock_qty": bom_item.qty,
+				"fg_reference_id": name,
+				"do_not_explode": 1,
+				"is_expandable": 1,
+				"stock_uom": item_info.stock_uom,
+			},
+		)
+
+		parent_row_no = item_row.idx
+		name = ""
+
+	for row in bom_item.get("items"):
+		row = frappe._dict(row)
+		item_info = get_item_details(row.item_code)
+		doc.append(
+			"items",
+			{
+				"item_code": row.item_code,
+				"qty": row.qty,
+				"fg_item": bom_item.item_code,
+				"uom": item_info.stock_uom,
+				"fg_reference_id": name,
+				"parent_row_no": parent_row_no,
+				"conversion_factor": 1,
+				"do_not_explode": 1,
+				"stock_qty": row.qty,
+				"stock_uom": item_info.stock_uom,
+			},
+		)
+
+	doc.save()
+
+	return doc
+
+
+def get_item_details(item_code):
+	return frappe.get_cached_value(
+		"Item", item_code, ["item_name", "description", "image", "stock_uom", "default_bom"], as_dict=1
+	)
+
+
+@frappe.whitelist()
+def delete_node(**kwargs):
+	if isinstance(kwargs, str):
+		kwargs = frappe.parse_json(kwargs)
+
+	if isinstance(kwargs, dict):
+		kwargs = frappe._dict(kwargs)
+
+	items = get_children(parent=kwargs.fg_item, parent_id=kwargs.parent)
+	if kwargs.docname:
+		frappe.delete_doc("BOM Creator Item", kwargs.docname)
+
+	for item in items:
+		frappe.delete_doc("BOM Creator Item", item.name)
+		if item.expandable:
+			delete_node(fg_item=item.value, parent=item.parent_id)
+
+	doc = frappe.get_doc("BOM Creator", kwargs.parent)
+	doc.set_rate_for_items()
+	doc.save()
+
+	return doc
+
+
+@frappe.whitelist()
+def edit_qty(doctype, docname, qty, parent):
+	frappe.db.set_value(doctype, docname, "qty", qty)
+	doc = frappe.get_doc("BOM Creator", parent)
+	doc.set_rate_for_items()
+	doc.save()
+
+	return doc
diff --git a/erpnext/manufacturing/doctype/bom_creator/bom_creator_list.js b/erpnext/manufacturing/doctype/bom_creator/bom_creator_list.js
new file mode 100644
index 0000000..423b721
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator/bom_creator_list.js
@@ -0,0 +1,18 @@
+frappe.listview_settings['BOM Creator'] = {
+	add_fields: ["status"],
+	get_indicator: function (doc) {
+		if (doc.status === "Draft") {
+			return [__("Draft"), "red", "status,=,Draft"];
+		} else if (doc.status === "In Progress") {
+			return [__("In Progress"), "orange", "status,=,In Progress"];
+		} else if (doc.status === "Completed") {
+			return [__("Completed"), "green", "status,=,Completed"];
+		} else if (doc.status === "Cancelled") {
+			return [__("Cancelled"), "red", "status,=,Cancelled"];
+		} else if (doc.status === "Failed") {
+			return [__("Failed"), "red", "status,=,Failed"];
+		} else if (doc.status === "Submitted") {
+			return [__("Submitted"), "blue", "status,=,Submitted"];
+		}
+	},
+};
diff --git a/erpnext/manufacturing/doctype/bom_creator/test_bom_creator.py b/erpnext/manufacturing/doctype/bom_creator/test_bom_creator.py
new file mode 100644
index 0000000..d239d58
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator/test_bom_creator.py
@@ -0,0 +1,240 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# See license.txt
+
+import random
+
+import frappe
+from frappe.tests.utils import FrappeTestCase
+
+from erpnext.manufacturing.doctype.bom_creator.bom_creator import (
+	add_item,
+	add_sub_assembly,
+	delete_node,
+	edit_qty,
+)
+from erpnext.stock.doctype.item.test_item import make_item
+
+
+class TestBOMCreator(FrappeTestCase):
+	def setUp(self) -> None:
+		create_items()
+
+	def test_bom_sub_assembly(self):
+		final_product = "Bicycle"
+		make_item(
+			final_product,
+			{
+				"item_group": "Raw Material",
+				"stock_uom": "Nos",
+			},
+		)
+
+		doc = make_bom_creator(
+			name="Bicycle BOM with Sub Assembly",
+			company="_Test Company",
+			item_code=final_product,
+			qty=1,
+			rm_cosy_as_per="Valuation Rate",
+			currency="INR",
+			plc_conversion_rate=1,
+			conversion_rate=1,
+		)
+
+		add_sub_assembly(
+			parent=doc.name,
+			fg_item=final_product,
+			fg_reference_id=doc.name,
+			bom_item={
+				"item_code": "Frame Assembly",
+				"qty": 1,
+				"items": [
+					{
+						"item_code": "Frame",
+						"qty": 1,
+					},
+					{
+						"item_code": "Fork",
+						"qty": 1,
+					},
+				],
+			},
+		)
+
+		doc.reload()
+		self.assertEqual(doc.items[0].item_code, "Frame Assembly")
+
+		fg_valuation_rate = 0
+		for row in doc.items:
+			if not row.is_expandable:
+				fg_valuation_rate += row.amount
+				self.assertEqual(row.fg_item, "Frame Assembly")
+				self.assertEqual(row.fg_reference_id, doc.items[0].name)
+
+		self.assertEqual(doc.items[0].amount, fg_valuation_rate)
+
+	def test_bom_raw_material(self):
+		final_product = "Bicycle"
+		make_item(
+			final_product,
+			{
+				"item_group": "Raw Material",
+				"stock_uom": "Nos",
+			},
+		)
+
+		doc = make_bom_creator(
+			name="Bicycle BOM with Raw Material",
+			company="_Test Company",
+			item_code=final_product,
+			qty=1,
+			rm_cosy_as_per="Valuation Rate",
+			currency="INR",
+			plc_conversion_rate=1,
+			conversion_rate=1,
+		)
+
+		add_item(
+			parent=doc.name,
+			fg_item=final_product,
+			fg_reference_id=doc.name,
+			item_code="Pedal Assembly",
+			qty=2,
+		)
+
+		doc.reload()
+		self.assertEqual(doc.items[0].item_code, "Pedal Assembly")
+		self.assertEqual(doc.items[0].qty, 2)
+
+		fg_valuation_rate = 0
+		for row in doc.items:
+			if not row.is_expandable:
+				fg_valuation_rate += row.amount
+				self.assertEqual(row.fg_item, "Bicycle")
+				self.assertEqual(row.fg_reference_id, doc.name)
+
+		self.assertEqual(doc.raw_material_cost, fg_valuation_rate)
+
+	def test_convert_to_sub_assembly(self):
+		final_product = "Bicycle"
+		make_item(
+			final_product,
+			{
+				"item_group": "Raw Material",
+				"stock_uom": "Nos",
+			},
+		)
+
+		doc = make_bom_creator(
+			name="Bicycle BOM",
+			company="_Test Company",
+			item_code=final_product,
+			qty=1,
+			rm_cosy_as_per="Valuation Rate",
+			currency="INR",
+			plc_conversion_rate=1,
+			conversion_rate=1,
+		)
+
+		add_item(
+			parent=doc.name,
+			fg_item=final_product,
+			fg_reference_id=doc.name,
+			item_code="Pedal Assembly",
+			qty=2,
+		)
+
+		doc.reload()
+		self.assertEqual(doc.items[0].is_expandable, 0)
+
+		add_sub_assembly(
+			convert_to_sub_assembly=1,
+			parent=doc.name,
+			fg_item=final_product,
+			fg_reference_id=doc.items[0].name,
+			bom_item={
+				"item_code": "Pedal Assembly",
+				"qty": 2,
+				"items": [
+					{
+						"item_code": "Pedal Body",
+						"qty": 2,
+					},
+					{
+						"item_code": "Pedal Axle",
+						"qty": 2,
+					},
+				],
+			},
+		)
+
+		doc.reload()
+		self.assertEqual(doc.items[0].is_expandable, 1)
+
+		fg_valuation_rate = 0
+		for row in doc.items:
+			if not row.is_expandable:
+				fg_valuation_rate += row.amount
+				self.assertEqual(row.fg_item, "Pedal Assembly")
+				self.assertEqual(row.qty, 2.0)
+				self.assertEqual(row.fg_reference_id, doc.items[0].name)
+
+		self.assertEqual(doc.raw_material_cost, fg_valuation_rate)
+
+
+def create_items():
+	raw_materials = [
+		"Frame",
+		"Fork",
+		"Rim",
+		"Spokes",
+		"Hub",
+		"Tube",
+		"Tire",
+		"Pedal Body",
+		"Pedal Axle",
+		"Ball Bearings",
+		"Chain Links",
+		"Chain Pins",
+		"Seat",
+		"Seat Post",
+		"Seat Clamp",
+	]
+
+	for item in raw_materials:
+		valuation_rate = random.choice([100, 200, 300, 500, 333, 222, 44, 20, 10])
+		make_item(
+			item,
+			{
+				"item_group": "Raw Material",
+				"stock_uom": "Nos",
+				"valuation_rate": valuation_rate,
+			},
+		)
+
+	sub_assemblies = [
+		"Frame Assembly",
+		"Wheel Assembly",
+		"Pedal Assembly",
+		"Chain Assembly",
+		"Seat Assembly",
+	]
+
+	for item in sub_assemblies:
+		make_item(
+			item,
+			{
+				"item_group": "Raw Material",
+				"stock_uom": "Nos",
+			},
+		)
+
+
+def make_bom_creator(**kwargs):
+	if isinstance(kwargs, str) or isinstance(kwargs, dict):
+		kwargs = frappe.parse_json(kwargs)
+
+	doc = frappe.new_doc("BOM Creator")
+	doc.update(kwargs)
+	doc.save()
+
+	return doc
diff --git a/erpnext/manufacturing/doctype/bom_creator_item/__init__.py b/erpnext/manufacturing/doctype/bom_creator_item/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator_item/__init__.py
diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json
new file mode 100644
index 0000000..fdb5d3a
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.json
@@ -0,0 +1,243 @@
+{
+ "actions": [],
+ "allow_rename": 1,
+ "creation": "2023-07-18 14:35:50.307386",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+  "item_code",
+  "item_name",
+  "item_group",
+  "column_break_f63f",
+  "fg_item",
+  "source_warehouse",
+  "is_expandable",
+  "sourced_by_supplier",
+  "bom_created",
+  "description_section",
+  "description",
+  "quantity_and_rate_section",
+  "qty",
+  "rate",
+  "uom",
+  "column_break_bgnb",
+  "stock_qty",
+  "conversion_factor",
+  "stock_uom",
+  "amount_section",
+  "amount",
+  "column_break_yuca",
+  "base_rate",
+  "base_amount",
+  "section_break_wtld",
+  "do_not_explode",
+  "parent_row_no",
+  "fg_reference_id",
+  "column_break_sulm",
+  "instruction"
+ ],
+ "fields": [
+  {
+   "columns": 2,
+   "fieldname": "item_code",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Item Code",
+   "options": "Item",
+   "reqd": 1
+  },
+  {
+   "fetch_from": "item_code.item_name",
+   "fetch_if_empty": 1,
+   "fieldname": "item_name",
+   "fieldtype": "Data",
+   "label": "Item Name"
+  },
+  {
+   "fetch_from": "item_code.item_group",
+   "fieldname": "item_group",
+   "fieldtype": "Link",
+   "label": "Item Group",
+   "options": "Item Group"
+  },
+  {
+   "fieldname": "column_break_f63f",
+   "fieldtype": "Column Break"
+  },
+  {
+   "columns": 2,
+   "fieldname": "fg_item",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "FG Item",
+   "options": "Item",
+   "reqd": 1
+  },
+  {
+   "fieldname": "source_warehouse",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "Source Warehouse",
+   "options": "Warehouse"
+  },
+  {
+   "default": "0",
+   "fieldname": "is_expandable",
+   "fieldtype": "Check",
+   "label": "Is Expandable",
+   "read_only": 1
+  },
+  {
+   "collapsible": 1,
+   "fieldname": "description_section",
+   "fieldtype": "Section Break",
+   "label": "Description"
+  },
+  {
+   "fetch_from": "item_code.description",
+   "fetch_if_empty": 1,
+   "fieldname": "description",
+   "fieldtype": "Small Text"
+  },
+  {
+   "fieldname": "quantity_and_rate_section",
+   "fieldtype": "Section Break",
+   "label": "Quantity and Rate"
+  },
+  {
+   "columns": 1,
+   "fieldname": "qty",
+   "fieldtype": "Float",
+   "in_list_view": 1,
+   "label": "Qty"
+  },
+  {
+   "columns": 2,
+   "fieldname": "rate",
+   "fieldtype": "Currency",
+   "in_list_view": 1,
+   "label": "Rate"
+  },
+  {
+   "columns": 1,
+   "fieldname": "uom",
+   "fieldtype": "Link",
+   "in_list_view": 1,
+   "label": "UOM",
+   "options": "UOM"
+  },
+  {
+   "fieldname": "column_break_bgnb",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "stock_qty",
+   "fieldtype": "Float",
+   "label": "Stock Qty",
+   "read_only": 1
+  },
+  {
+   "fieldname": "conversion_factor",
+   "fieldtype": "Float",
+   "label": "Conversion Factor"
+  },
+  {
+   "fetch_from": "item_code.stock_uom",
+   "fieldname": "stock_uom",
+   "fieldtype": "Link",
+   "label": "Stock UOM",
+   "no_copy": 1,
+   "options": "UOM",
+   "read_only": 1
+  },
+  {
+   "fieldname": "amount_section",
+   "fieldtype": "Section Break",
+   "label": "Amount"
+  },
+  {
+   "fieldname": "amount",
+   "fieldtype": "Currency",
+   "label": "Amount",
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_yuca",
+   "fieldtype": "Column Break"
+  },
+  {
+   "default": "1",
+   "fieldname": "do_not_explode",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "Do Not Explode"
+  },
+  {
+   "fieldname": "instruction",
+   "fieldtype": "Small Text",
+   "label": "Instruction"
+  },
+  {
+   "fieldname": "base_amount",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Base Amount"
+  },
+  {
+   "fieldname": "base_rate",
+   "fieldtype": "Currency",
+   "hidden": 1,
+   "label": "Base Rate"
+  },
+  {
+   "default": "0",
+   "fieldname": "sourced_by_supplier",
+   "fieldtype": "Check",
+   "label": "Sourced by Supplier"
+  },
+  {
+   "fieldname": "section_break_wtld",
+   "fieldtype": "Section Break"
+  },
+  {
+   "fieldname": "fg_reference_id",
+   "fieldtype": "Data",
+   "label": "FG Reference",
+   "no_copy": 1,
+   "read_only": 1
+  },
+  {
+   "fieldname": "column_break_sulm",
+   "fieldtype": "Column Break"
+  },
+  {
+   "fieldname": "parent_row_no",
+   "fieldtype": "Data",
+   "label": "Parent Row No",
+   "no_copy": 1,
+   "print_hide": 1
+  },
+  {
+   "default": "0",
+   "fieldname": "bom_created",
+   "fieldtype": "Check",
+   "hidden": 1,
+   "label": "BOM Created",
+   "no_copy": 1,
+   "print_hide": 1
+  }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2023-08-07 11:52:30.492233",
+ "modified_by": "Administrator",
+ "module": "Manufacturing",
+ "name": "BOM Creator Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py
new file mode 100644
index 0000000..350c918
--- /dev/null
+++ b/erpnext/manufacturing/doctype/bom_creator_item/bom_creator_item.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class BOMCreatorItem(Document):
+	pass
diff --git a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
index 518ae14..8e07850 100644
--- a/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
+++ b/erpnext/manufacturing/workspace/manufacturing/manufacturing.json
@@ -1,6 +1,6 @@
 {
  "charts": [],
- "content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"PIQJYZOMnD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Manufacturing\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"id\":\"csBCiDglCE\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Your Shortcuts</b></span>\",\"col\":12}},{\"id\":\"YHCQG3wAGv\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Creator\",\"col\":3}},{\"id\":\"xit0dg7KvY\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM\",\"col\":3}},{\"id\":\"LRhGV9GAov\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Plan\",\"col\":3}},{\"id\":\"69KKosI6Hg\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Work Order\",\"col\":3}},{\"id\":\"PwndxuIpB3\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Job Card\",\"col\":3}},{\"id\":\"OaiDqTT03Y\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Forecasting\",\"col\":3}},{\"id\":\"OtMcArFRa5\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"BOM Stock Report\",\"col\":3}},{\"id\":\"76yYsI5imF\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Production Planning Report\",\"col\":3}},{\"id\":\"PIQJYZOMnD\",\"type\":\"shortcut\",\"data\":{\"shortcut_name\":\"Learn Manufacturing\",\"col\":3}},{\"id\":\"bN_6tHS-Ct\",\"type\":\"spacer\",\"data\":{\"col\":12}},{\"id\":\"yVEFZMqVwd\",\"type\":\"header\",\"data\":{\"text\":\"<span class=\\\"h4\\\"><b>Reports &amp; Masters</b></span>\",\"col\":12}},{\"id\":\"rwrmsTI58-\",\"type\":\"card\",\"data\":{\"card_name\":\"Production\",\"col\":4}},{\"id\":\"6dnsyX-siZ\",\"type\":\"card\",\"data\":{\"card_name\":\"Bill of Materials\",\"col\":4}},{\"id\":\"CIq-v5f5KC\",\"type\":\"card\",\"data\":{\"card_name\":\"Reports\",\"col\":4}},{\"id\":\"8RRiQeYr0G\",\"type\":\"card\",\"data\":{\"card_name\":\"Tools\",\"col\":4}},{\"id\":\"Pu8z7-82rT\",\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
  "creation": "2020-03-02 17:11:37.032604",
  "custom_blocks": [],
  "docstatus": 0,
@@ -316,7 +316,7 @@
    "type": "Link"
   }
  ],
- "modified": "2023-07-04 14:40:47.281125",
+ "modified": "2023-08-08 22:28:39.633891",
  "modified_by": "Administrator",
  "module": "Manufacturing",
  "name": "Manufacturing",
@@ -339,6 +339,13 @@
   {
    "color": "Grey",
    "doc_view": "List",
+   "label": "BOM Creator",
+   "link_to": "BOM Creator",
+   "type": "DocType"
+  },
+  {
+   "color": "Grey",
+   "doc_view": "List",
    "label": "BOM",
    "link_to": "BOM",
    "stats_filter": "{\"is_active\":[\"=\",1]}",
diff --git a/erpnext/public/js/bom_configurator/bom_configurator.bundle.js b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js
new file mode 100644
index 0000000..b3b2e9f
--- /dev/null
+++ b/erpnext/public/js/bom_configurator/bom_configurator.bundle.js
@@ -0,0 +1,416 @@
+class BOMConfigurator {
+	constructor({ wrapper, page, frm, bom_configurator }) {
+		this.$wrapper = $(wrapper);
+		this.page = page;
+		this.bom_configurator = bom_configurator;
+		this.frm = frm;
+
+		this.make();
+		this.prepare_layout();
+		this.bind_events();
+	}
+
+	add_boms() {
+		this.frm.call({
+			method: "add_boms",
+			freeze: true,
+			doc: this.frm.doc,
+		});
+	}
+
+	make() {
+		let options = {
+			...this.tree_options(),
+			...this.tree_methods(),
+		};
+
+		frappe.views.trees["BOM Configurator"] = new frappe.views.TreeView(options);
+		this.tree_view = frappe.views.trees["BOM Configurator"];
+	}
+
+	bind_events() {
+		frappe.views.trees["BOM Configurator"].events = {
+			frm: this.frm,
+			add_item: this.add_item,
+			add_sub_assembly: this.add_sub_assembly,
+			get_sub_assembly_modal_fields: this.get_sub_assembly_modal_fields,
+			convert_to_sub_assembly: this.convert_to_sub_assembly,
+			delete_node: this.delete_node,
+			edit_qty: this.edit_qty,
+			load_tree: this.load_tree,
+			set_default_qty: this.set_default_qty,
+		}
+	}
+
+	tree_options() {
+		return {
+			parent: this.$wrapper.get(0),
+			body: this.$wrapper.get(0),
+			doctype: 'BOM Configurator',
+			page: this.page,
+			expandable: true,
+			title: __("Configure Product Assembly"),
+			breadcrumb: "Manufacturing",
+			get_tree_nodes: "erpnext.manufacturing.doctype.bom_creator.bom_creator.get_children",
+			root_label: this.frm.doc.item_code,
+			disable_add_node: true,
+			get_tree_root: false,
+			show_expand_all: false,
+			extend_toolbar: false,
+			do_not_make_page: true,
+			do_not_setup_menu: true,
+		}
+	}
+
+	tree_methods() {
+		let frm_obj = this;
+		let view = frappe.views.trees["BOM Configurator"];
+
+		return {
+			onload: function(me) {
+				me.args["parent_id"] = frm_obj.frm.doc.name;
+				me.args["parent"] = frm_obj.frm.doc.item_code;
+				me.parent = frm_obj.$wrapper.get(0);
+				me.body = frm_obj.$wrapper.get(0);
+				me.make_tree();
+			},
+			onrender(node) {
+				const qty = node.data.qty || frm_obj.frm.doc.qty;
+				const uom = node.data.uom || frm_obj.frm.doc.uom;
+				const docname = node.data.name || frm_obj.frm.doc.name;
+				let amount = node.data.amount;
+				if (node.data.value === frm_obj.frm.doc.item_code) {
+					amount = frm_obj.frm.doc.raw_material_cost;
+				}
+
+				amount = frappe.format(amount, { fieldtype: "Currency", currency: frm_obj.frm.doc.currency });
+
+				$(`
+					<div class="pill small pull-right bom-qty-pill"
+						style="background-color: var(--bg-white);
+							color: var(--text-on-gray);
+							font-weight:450;
+							margin-right: 40px;
+							display: inline-flex;
+							min-width: 128px;
+							border: 1px solid var(--bg-gray);
+						">
+							<div style="padding-right:5px" data-bom-qty-docname="${docname}">${qty} ${uom}</div>
+							<div class="fg-item-amt" style="padding-left:12px; border-left:1px solid var(--bg-gray)">
+								${amount}
+							</div>
+					</div>
+
+				`).insertBefore(node.$ul);
+			},
+			toolbar: this.frm?.doc.docstatus === 0 ? [
+				{
+					label:__(frappe.utils.icon('edit', 'sm') + " Qty"),
+					click: function(node) {
+						let view = frappe.views.trees["BOM Configurator"];
+						view.events.edit_qty(node, view);
+					},
+					btnClass: "hidden-xs"
+				},
+				{
+					label:__(frappe.utils.icon('add', 'sm') + " Raw Material"),
+					click: function(node) {
+						let view = frappe.views.trees["BOM Configurator"];
+						view.events.add_item(node, view);
+					},
+					condition: function(node) {
+						return node.expandable;
+					},
+					btnClass: "hidden-xs"
+				},
+				{
+					label:__(frappe.utils.icon('add', 'sm') + " Sub Assembly"),
+					click: function(node) {
+						let view = frappe.views.trees["BOM Configurator"];
+						view.events.add_sub_assembly(node, view);
+					},
+					condition: function(node) {
+						return node.expandable;
+					},
+					btnClass: "hidden-xs"
+				},
+				{
+					label:__("Expand All"),
+					click: function(node) {
+						let view = frappe.views.trees["BOM Configurator"];
+
+						if (!node.expanded) {
+							view.tree.load_children(node, true);
+							$(node.parent[0]).find(".tree-children").show();
+							node.$toolbar.find(".expand-all-btn").html("Collapse All");
+						} else {
+							node.$tree_link.trigger("click");
+							node.$toolbar.find(".expand-all-btn").html("Expand All");
+						}
+					},
+					condition: function(node) {
+						return node.expandable && node.is_root;
+					},
+					btnClass: "hidden-xs expand-all-btn"
+				},
+				{
+					label:__(frappe.utils.icon('move', 'sm') + " Sub Assembly"),
+					click: function(node) {
+						let view = frappe.views.trees["BOM Configurator"];
+						view.events.convert_to_sub_assembly(node, view);
+					},
+					condition: function(node) {
+						return !node.expandable;
+					},
+					btnClass: "hidden-xs"
+				},
+				{
+					label:__(frappe.utils.icon('delete', 'sm') + __(" Item")),
+					click: function(node) {
+						let view = frappe.views.trees["BOM Configurator"];
+						view.events.delete_node(node, view);
+					},
+					condition: function(node) {
+						return !node.is_root;
+					},
+					btnClass: "hidden-xs"
+				},
+			] : [{
+				label:__("Expand All"),
+				click: function(node) {
+					let view = frappe.views.trees["BOM Configurator"];
+
+					if (!node.expanded) {
+						view.tree.load_children(node, true);
+						$(node.parent[0]).find(".tree-children").show();
+						node.$toolbar.find(".expand-all-btn").html("Collapse All");
+					} else {
+						node.$tree_link.trigger("click");
+						node.$toolbar.find(".expand-all-btn").html("Expand All");
+					}
+				},
+				condition: function(node) {
+					return node.expandable && node.is_root;
+				},
+				btnClass: "hidden-xs expand-all-btn"
+			}],
+		}
+	}
+
+	add_item(node, view) {
+		frappe.prompt([
+			{ label: __("Item"), fieldname: "item_code", fieldtype: "Link", options: "Item", reqd: 1 },
+			{ label: __("Qty"), fieldname: "qty", default: 1.0, fieldtype: "Float", reqd: 1 },
+		],
+		(data) => {
+			if (!node.data.parent_id) {
+				node.data.parent_id = this.frm.doc.name;
+			}
+
+			frappe.call({
+				method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.add_item",
+				args: {
+					parent: node.data.parent_id,
+					fg_item: node.data.value,
+					item_code: data.item_code,
+					fg_reference_id: node.data.name || this.frm.doc.name,
+					qty: data.qty,
+				},
+				callback: (r) => {
+					view.events.load_tree(r, node);
+				}
+			});
+		},
+		__("Add Item"),
+		__("Add"));
+	}
+
+	add_sub_assembly(node, view) {
+		let dialog = new frappe.ui.Dialog({
+			fields: view.events.get_sub_assembly_modal_fields(),
+			title: __("Add Sub Assembly"),
+		});
+
+		dialog.show();
+		view.events.set_default_qty(dialog);
+
+		dialog.set_primary_action(__("Add"), () => {
+			let bom_item = dialog.get_values();
+
+			if (!node.data?.parent_id) {
+				node.data.parent_id = this.frm.doc.name;
+			}
+
+			frappe.call({
+				method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.add_sub_assembly",
+				args: {
+					parent: node.data.parent_id,
+					fg_item: node.data.value,
+					fg_reference_id: node.data.name || this.frm.doc.name,
+					bom_item: bom_item,
+				},
+				callback: (r) => {
+					view.events.load_tree(r, node);
+				}
+			});
+
+			dialog.hide();
+		});
+
+	}
+
+	get_sub_assembly_modal_fields(read_only=false) {
+		return [
+			{ label: __("Sub Assembly Item"), fieldname: "item_code", fieldtype: "Link", options: "Item", reqd: 1, read_only: read_only },
+			{ fieldtype: "Column Break" },
+			{ label: __("Qty"), fieldname: "qty", default: 1.0, fieldtype: "Float", reqd: 1, read_only: read_only },
+			{ fieldtype: "Section Break" },
+			{ label: __("Raw Materials"), fieldname: "items", fieldtype: "Table", reqd: 1,
+				fields: [
+					{ label: __("Item"), fieldname: "item_code", fieldtype: "Link", options: "Item", reqd: 1, in_list_view: 1 },
+					{ label: __("Qty"), fieldname: "qty", default: 1.0, fieldtype: "Float", reqd: 1, in_list_view: 1 },
+				]
+			},
+		]
+	}
+
+	convert_to_sub_assembly(node, view) {
+		let dialog = new frappe.ui.Dialog({
+			fields: view.events.get_sub_assembly_modal_fields(true),
+			title: __("Add Sub Assembly"),
+		});
+
+		dialog.set_values({
+			item_code: node.data.value,
+			qty: node.data.qty,
+		});
+
+		dialog.show();
+		view.events.set_default_qty(dialog);
+
+		dialog.set_primary_action(__("Add"), () => {
+			let bom_item = dialog.get_values();
+
+			frappe.call({
+				method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.add_sub_assembly",
+				args: {
+					parent: node.data.parent_id,
+					fg_item: node.data.value,
+					bom_item: bom_item,
+					fg_reference_id: node.data.name || this.frm.doc.name,
+					convert_to_sub_assembly: true,
+				},
+				callback: (r) => {
+					node.expandable = true;
+					view.events.load_tree(r, node);
+				}
+			});
+
+			dialog.hide();
+		});
+	}
+
+	set_default_qty(dialog) {
+		dialog.fields_dict.items.grid.fields_map.item_code.onchange = function (event) {
+			if (event) {
+				let name = $(event.currentTarget).closest('.grid-row').attr("data-name")
+				let item_row = dialog.fields_dict.items.grid.grid_rows_by_docname[name].doc;
+				item_row.qty = 1;
+				dialog.fields_dict.items.grid.refresh()
+			}
+		}
+	}
+
+	delete_node(node, view) {
+		frappe.confirm(__("Are you sure you want to delete this Item?"), () => {
+			frappe.call({
+				method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.delete_node",
+				args: {
+					parent: node.data.parent_id,
+					fg_item: node.data.value,
+					doctype: node.data.doctype,
+					docname: node.data.name,
+				},
+				callback: (r) => {
+					view.events.load_tree(r, node.parent_node);
+				}
+			});
+		});
+	}
+
+	edit_qty(node, view) {
+		let qty = node.data.qty || this.frm.doc.qty;
+		frappe.prompt([
+			{ label: __("Qty"), fieldname: "qty", default: qty, fieldtype: "Float", reqd: 1 },
+		],
+		(data) => {
+			let doctype = node.data.doctype || this.frm.doc.doctype;
+			let docname = node.data.name || this.frm.doc.name;
+
+			frappe.call({
+				method: "erpnext.manufacturing.doctype.bom_creator.bom_creator.edit_qty",
+				args: {
+					doctype: doctype,
+					docname: docname,
+					qty: data.qty,
+					parent: node.data.parent_id,
+				},
+				callback: (r) => {
+					node.data.qty = data.qty;
+					let uom = node.data.uom || this.frm.doc.uom;
+					$(node.parent.get(0)).find(`[data-bom-qty-docname='${docname}']`).html(data.qty + " " + uom);
+					view.events.load_tree(r, node);
+				}
+			});
+		},
+		__("Edit Qty"),
+		__("Update"));
+	}
+
+	prepare_layout() {
+		let main_div = $(this.page)[0];
+
+		main_div.style.marginBottom = "15px";
+		$(main_div).find(".tree-children")[0].style.minHeight = "370px";
+		$(main_div).find(".tree-children")[0].style.maxHeight = "370px";
+		$(main_div).find(".tree-children")[0].style.overflowY = "auto";
+	}
+
+	load_tree(response, node) {
+		let item_row = "";
+		let parent_dom = ""
+		let total_amount = response.message.raw_material_cost;
+
+		frappe.views.trees["BOM Configurator"].tree.load_children(node);
+
+		while (true) {
+			item_row = response.message.items.filter(item => item.name === node.data.name);
+
+			if (item_row?.length) {
+				node.data.amount = item_row[0].amount;
+				total_amount = node.data.amount
+			} else {
+				total_amount = response.message.raw_material_cost;
+			}
+
+			parent_dom = $(node.parent.get(0));
+			total_amount = frappe.format(
+				total_amount, {
+					fieldtype: "Currency",
+					currency: this.frm.doc.currency
+				}
+			);
+
+			$($(parent_dom).find(".fg-item-amt")[0]).html(total_amount);
+
+			if (node.is_root) {
+				break;
+			}
+
+			node = node.parent_node;
+		}
+
+	}
+}
+
+frappe.ui.BOMConfigurator = BOMConfigurator;
\ No newline at end of file
diff --git a/erpnext/public/js/controllers/transaction.js b/erpnext/public/js/controllers/transaction.js
index 59d2b15..ac5735b 100644
--- a/erpnext/public/js/controllers/transaction.js
+++ b/erpnext/public/js/controllers/transaction.js
@@ -277,7 +277,7 @@
 	}
 
 	setup_quality_inspection() {
-		if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)) {
+		if(!in_list(["Delivery Note", "Sales Invoice", "Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)) {
 			return;
 		}
 
@@ -289,7 +289,7 @@
 			this.frm.page.set_inner_btn_group_as_primary(__('Create'));
 		}
 
-		const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice"], this.frm.doc.doctype)
+		const inspection_type = in_list(["Purchase Receipt", "Purchase Invoice", "Subcontracting Receipt"], this.frm.doc.doctype)
 			? "Incoming" : "Outgoing";
 
 		let quality_inspection_field = this.frm.get_docfield("items", "quality_inspection");
@@ -2067,6 +2067,7 @@
 		const me = this;
 		const dialog = new frappe.ui.Dialog({
 			title: __("Select Items for Quality Inspection"),
+			size: "extra-large",
 			fields: fields,
 			primary_action: function () {
 				const data = dialog.get_values();
diff --git a/erpnext/public/js/setup_wizard.js b/erpnext/public/js/setup_wizard.js
index ba200ef..3cbec3e 100644
--- a/erpnext/public/js/setup_wizard.js
+++ b/erpnext/public/js/setup_wizard.js
@@ -45,7 +45,8 @@
 				fieldname: 'setup_demo',
 				label: __('Generate Demo Data for Exploration'),
 				fieldtype: 'Check',
-				description: 'If checked, we will create demo data for you to explore the system. This demo data can be erased later.'},
+				description: __('If checked, we will create demo data for you to explore the system. This demo data can be erased later.')
+			},
 		],
 
 		onload: function (slide) {
diff --git a/erpnext/public/js/utils.js b/erpnext/public/js/utils.js
index f456e5e..a3c10c6 100755
--- a/erpnext/public/js/utils.js
+++ b/erpnext/public/js/utils.js
@@ -114,6 +114,10 @@
 	},
 
 	view_serial_batch_nos: function(frm) {
+		if (!frm.doc?.items) {
+			return;
+		}
+
 		let bundle_ids = frm.doc.items.filter(d => d.serial_and_batch_bundle);
 
 		if (bundle_ids?.length) {
@@ -579,7 +583,9 @@
 			"conversion_factor": d.conversion_factor,
 			"qty": d.qty,
 			"rate": d.rate,
-			"uom": d.uom
+			"uom": d.uom,
+			"fg_item": d.fg_item,
+			"fg_item_qty": d.fg_item_qty,
 		}
 	});
 
@@ -678,6 +684,37 @@
 		})
 	}
 
+	if (frm.doc.doctype == 'Purchase Order' && frm.doc.is_subcontracted && !frm.doc.is_old_subcontracting_flow) {
+		fields.push({
+			fieldtype:'Link',
+			fieldname:'fg_item',
+			options: 'Item',
+			reqd: 1,
+			in_list_view: 0,
+			read_only: 0,
+			disabled: 0,
+			label: __('Finished Good Item'),
+			get_query: () => {
+				return {
+					filters: {
+						'is_stock_item': 1,
+						'is_sub_contracted_item': 1,
+						'default_bom': ['!=', '']
+					}
+				}
+			},
+		}, {
+			fieldtype:'Float',
+			fieldname:'fg_item_qty',
+			reqd: 1,
+			default: 0,
+			read_only: 0,
+			in_list_view: 0,
+			label: __('Finished Good Item Qty'),
+			precision: get_precision('fg_item_qty')
+		})
+	}
+
 	let dialog = new frappe.ui.Dialog({
 		title: __("Update Items"),
 		size: "extra-large",
diff --git a/erpnext/selling/doctype/customer/customer.py b/erpnext/selling/doctype/customer/customer.py
index 555db59..d351c3c 100644
--- a/erpnext/selling/doctype/customer/customer.py
+++ b/erpnext/selling/doctype/customer/customer.py
@@ -15,6 +15,7 @@
 from frappe.model.naming import set_name_by_naming_series, set_name_from_naming_options
 from frappe.model.utils.rename_doc import update_linked_doctypes
 from frappe.utils import cint, cstr, flt, get_formatted_email, today
+from frappe.utils.nestedset import get_root_of
 from frappe.utils.user import get_users_with_role
 
 from erpnext.accounts.party import (  # noqa
@@ -80,6 +81,7 @@
 		validate_party_accounts(self)
 		self.validate_credit_limit_on_change()
 		self.set_loyalty_program()
+		self.set_territory_and_group()
 		self.check_customer_group_change()
 		self.validate_default_bank_account()
 		self.validate_internal_customer()
@@ -138,6 +140,12 @@
 					_("{0} is not a company bank account").format(frappe.bold(self.default_bank_account))
 				)
 
+	def set_territory_and_group(self):
+		if not self.territory:
+			self.territory = get_root_of("Territory")
+		if not self.customer_group:
+			self.customer_group = get_root_of("Customer Group")
+
 	def validate_internal_customer(self):
 		if not self.is_internal_customer:
 			self.represents_company = ""
diff --git a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
index be75bd6..d341d23 100644
--- a/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
+++ b/erpnext/selling/page/point_of_sale/pos_past_order_summary.js
@@ -48,8 +48,8 @@
 		const email_dialog = new frappe.ui.Dialog({
 			title: 'Email Receipt',
 			fields: [
-				{fieldname: 'email_id', fieldtype: 'Data', options: 'Email', label: 'Email ID'},
-				// {fieldname:'remarks', fieldtype:'Text', label:'Remarks (if any)'}
+				{fieldname: 'email_id', fieldtype: 'Data', options: 'Email', label: 'Email ID', reqd: 1},
+				{fieldname:'content', fieldtype:'Small Text', label:'Message (if any)'}
 			],
 			primary_action: () => {
 				this.send_email();
@@ -243,6 +243,7 @@
 	send_email() {
 		const frm = this.events.get_frm();
 		const recipients = this.email_dialog.get_values().email_id;
+		const content = this.email_dialog.get_values().content;
 		const doc = this.doc || frm.doc;
 		const print_format = frm.pos_print_format;
 
@@ -251,6 +252,7 @@
 			args: {
 				recipients: recipients,
 				subject: __(frm.meta.name) + ': ' + doc.name,
+				content: content ? content : __(frm.meta.name) + ': ' + doc.name,
 				doctype: doc.doctype,
 				name: doc.name,
 				send_email: 1,
diff --git a/erpnext/setup/demo_data/customer.json b/erpnext/setup/demo_data/customer.json
index 1b47906..5e77e78 100644
--- a/erpnext/setup/demo_data/customer.json
+++ b/erpnext/setup/demo_data/customer.json
@@ -2,19 +2,16 @@
     {
         "doctype": "Customer",
         "customer_group": "Demo Customer Group",
-        "territory": "All Territories",
         "customer_name": "Grant Plastics Ltd."
     },
     {
         "doctype": "Customer",
         "customer_group": "Demo Customer Group",
-        "territory": "All Territories",
         "customer_name": "West View Software Ltd."
     },
     {
         "doctype": "Customer",
         "customer_group": "Demo Customer Group",
-        "territory": "All Territories",
         "customer_name": "Palmer Productions Ltd."
     }
-]
\ No newline at end of file
+]
diff --git a/erpnext/setup/doctype/company/company.py b/erpnext/setup/doctype/company/company.py
index fcdf245..b05696a 100644
--- a/erpnext/setup/doctype/company/company.py
+++ b/erpnext/setup/doctype/company/company.py
@@ -403,14 +403,20 @@
 				self._set_default_account(default_account, default_accounts.get(default_account))
 
 		if not self.default_income_account:
-			income_account = frappe.db.get_value(
-				"Account", {"account_name": _("Sales"), "company": self.name, "is_group": 0}
+			income_account = frappe.db.get_all(
+				"Account",
+				filters={"company": self.name, "is_group": 0},
+				or_filters={
+					"account_name": ("in", [_("Sales"), _("Sales Account")]),
+					"account_type": "Income Account",
+				},
+				pluck="name",
 			)
 
-			if not income_account:
-				income_account = frappe.db.get_value(
-					"Account", {"account_name": _("Sales Account"), "company": self.name}
-				)
+			if income_account:
+				income_account = income_account[0]
+			else:
+				income_account = None
 
 			self.db_set("default_income_account", income_account)
 
diff --git a/erpnext/stock/doctype/material_request/material_request.py b/erpnext/stock/doctype/material_request/material_request.py
index 1139c4b..9efae6a 100644
--- a/erpnext/stock/doctype/material_request/material_request.py
+++ b/erpnext/stock/doctype/material_request/material_request.py
@@ -656,7 +656,10 @@
 					"job_card_item": "job_card_item",
 				},
 				"postprocess": update_item,
-				"condition": lambda doc: doc.ordered_qty < doc.stock_qty,
+				"condition": lambda doc: (
+					flt(doc.ordered_qty, doc.precision("ordered_qty"))
+					< flt(doc.stock_qty, doc.precision("ordered_qty"))
+				),
 			},
 		},
 		target_doc,
diff --git a/erpnext/stock/doctype/material_request/material_request_dashboard.py b/erpnext/stock/doctype/material_request/material_request_dashboard.py
index 2bba52a..f91ea6a 100644
--- a/erpnext/stock/doctype/material_request/material_request_dashboard.py
+++ b/erpnext/stock/doctype/material_request/material_request_dashboard.py
@@ -6,6 +6,8 @@
 		"fieldname": "material_request",
 		"internal_links": {
 			"Sales Order": ["items", "sales_order"],
+			"Project": ["items", "project"],
+			"Cost Center": ["items", "cost_center"],
 		},
 		"transactions": [
 			{
@@ -15,5 +17,6 @@
 			{"label": _("Stock"), "items": ["Stock Entry", "Purchase Receipt", "Pick List"]},
 			{"label": _("Manufacturing"), "items": ["Work Order"]},
 			{"label": _("Internal Transfer"), "items": ["Sales Order"]},
+			{"label": _("Accounting Dimensions"), "items": ["Project", "Cost Center"]},
 		],
 	}
diff --git a/erpnext/stock/doctype/quality_inspection/quality_inspection.json b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
index db9322f..914a9f3 100644
--- a/erpnext/stock/doctype/quality_inspection/quality_inspection.json
+++ b/erpnext/stock/doctype/quality_inspection/quality_inspection.json
@@ -74,7 +74,7 @@
    "fieldname": "reference_type",
    "fieldtype": "Select",
    "label": "Reference Type",
-   "options": "\nPurchase Receipt\nPurchase Invoice\nDelivery Note\nSales Invoice\nStock Entry\nJob Card",
+   "options": "\nPurchase Receipt\nPurchase Invoice\nSubcontracting Receipt\nDelivery Note\nSales Invoice\nStock Entry\nJob Card",
    "reqd": 1
   },
   {
@@ -245,7 +245,7 @@
  "index_web_pages_for_search": 1,
  "is_submittable": 1,
  "links": [],
- "modified": "2022-10-04 22:00:13.995221",
+ "modified": "2023-08-23 11:56:50.282878",
  "modified_by": "Administrator",
  "module": "Stock",
  "name": "Quality Inspection",
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
index 94a2589..e374077 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.js
@@ -3,14 +3,91 @@
 
 frappe.provide('erpnext.buying');
 
-erpnext.landed_cost_taxes_and_charges.setup_triggers("Subcontracting Receipt");
+erpnext.landed_cost_taxes_and_charges.setup_triggers('Subcontracting Receipt');
 
 frappe.ui.form.on('Subcontracting Receipt', {
 	setup: (frm) => {
 		frm.ignore_doctypes_on_cancel_all = ['Serial and Batch Bundle'];
 		frm.get_field('supplied_items').grid.cannot_add_rows = true;
 		frm.get_field('supplied_items').grid.only_sortable();
+		frm.trigger('set_queries');
+	},
 
+	refresh: (frm) => {
+		if (frm.doc.docstatus > 0) {
+			frm.add_custom_button(__('Stock Ledger'), () => {
+					frappe.route_options = {
+						voucher_no: frm.doc.name,
+						from_date: frm.doc.posting_date,
+						to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
+						company: frm.doc.company,
+						show_cancelled_entries: frm.doc.docstatus === 2
+					};
+					frappe.set_route('query-report', 'Stock Ledger');
+				}, __('View'));
+
+			frm.add_custom_button(__('Accounting Ledger'), () => {
+					frappe.route_options = {
+						voucher_no: frm.doc.name,
+						from_date: frm.doc.posting_date,
+						to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
+						company: frm.doc.company,
+						group_by: 'Group by Voucher (Consolidated)',
+						show_cancelled_entries: frm.doc.docstatus === 2
+					};
+					frappe.set_route('query-report', 'General Ledger');
+				}, __('View'));
+		}
+
+		if (!frm.doc.is_return && frm.doc.docstatus === 1 && frm.doc.per_returned < 100) {
+			frm.add_custom_button(__('Subcontract Return'), () => {
+					frappe.model.open_mapped_doc({
+						method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return',
+						frm: frm
+					});
+				}, __('Create'));
+			frm.page.set_inner_btn_group_as_primary(__('Create'));
+		}
+
+		if (frm.doc.docstatus === 0) {
+			frm.add_custom_button(__('Subcontracting Order'), () => {
+					if (!frm.doc.supplier) {
+						frappe.throw({
+							title: __('Mandatory'),
+							message: __('Please Select a Supplier')
+						});
+					}
+
+					erpnext.utils.map_current_doc({
+						method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt',
+						source_doctype: 'Subcontracting Order',
+						target: frm,
+						setters: {
+							supplier: frm.doc.supplier,
+						},
+						get_query_filters: {
+							docstatus: 1,
+							per_received: ['<', 100],
+							company: frm.doc.company
+						}
+					});
+				}, __('Get Items From'));
+
+			frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM');
+		}
+
+		frm.trigger('setup_quality_inspection');
+	},
+
+	set_warehouse: (frm) => {
+		set_warehouse_in_children(frm.doc.items, 'warehouse', frm.doc.set_warehouse);
+	},
+
+	rejected_warehouse: (frm) => {
+		set_warehouse_in_children(frm.doc.items, 'rejected_warehouse', frm.doc.rejected_warehouse);
+	},
+
+	set_queries: (frm) => {
 		frm.set_query('set_warehouse', () => {
 			return {
 				filters: {
@@ -52,38 +129,36 @@
 			}
 		}));
 
-		frm.set_query('expense_account', 'items', function () {
-			return {
+		frm.set_query('expense_account', 'items', () => ({
 				query: 'erpnext.controllers.queries.get_expense_account',
 				filters: { 'company': frm.doc.company }
-			};
-		});
+			}));
 
-		frm.set_query('batch_no', 'items', function(doc, cdt, cdn) {
+		frm.set_query('batch_no', 'items', (doc, cdt, cdn) => {
 			var row = locals[cdt][cdn];
 			return {
 				filters: {
 					item: row.item_code
 				}
-			}
+			};
 		});
 
-		frm.set_query('batch_no', 'supplied_items', function(doc, cdt, cdn) {
+		frm.set_query('batch_no', 'supplied_items', (doc, cdt, cdn) => {
 			var row = locals[cdt][cdn];
 			return {
 				filters: {
 					item: row.rm_item_code
 				}
-			}
+			};
 		});
 
-		frm.set_query("serial_and_batch_bundle", "supplied_items", (doc, cdt, cdn) => {
+		frm.set_query('serial_and_batch_bundle', 'supplied_items', (doc, cdt, cdn) => {
 			let row = locals[cdt][cdn];
 			return {
 				filters: {
 					'item_code': row.rm_item_code,
 					'voucher_type': doc.doctype,
-					'voucher_no': ["in", [doc.name, ""]],
+					'voucher_no': ['in', [doc.name, '']],
 					'is_cancelled': 0,
 				}
 			}
@@ -101,7 +176,7 @@
 
 		let batch_no_field = frm.get_docfield('items', 'batch_no');
 		if (batch_no_field) {
-			batch_no_field.get_route_options_for_new_doc = function(row) {
+			batch_no_field.get_route_options_for_new_doc = (row) => {
 				return {
 					'item': row.doc.item_code
 				}
@@ -109,85 +184,20 @@
 		}
 	},
 
-	refresh: (frm) => {
-		if (frm.doc.docstatus > 0) {
-			frm.add_custom_button(__('Stock Ledger'), function () {
-				frappe.route_options = {
-					voucher_no: frm.doc.name,
-					from_date: frm.doc.posting_date,
-					to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
-					company: frm.doc.company,
-					show_cancelled_entries: frm.doc.docstatus === 2
-				};
-				frappe.set_route('query-report', 'Stock Ledger');
-			}, __('View'));
-
-			frm.add_custom_button(__('Accounting Ledger'), function () {
-				frappe.route_options = {
-					voucher_no: frm.doc.name,
-					from_date: frm.doc.posting_date,
-					to_date: moment(frm.doc.modified).format('YYYY-MM-DD'),
-					company: frm.doc.company,
-					group_by: 'Group by Voucher (Consolidated)',
-					show_cancelled_entries: frm.doc.docstatus === 2
-				};
-				frappe.set_route('query-report', 'General Ledger');
-			}, __('View'));
+	setup_quality_inspection: (frm) => {
+		if (!frm.is_new() && frm.doc.docstatus === 0 && !frm.doc.is_return) {
+			let transaction_controller = new erpnext.TransactionController({ frm: frm });
+			transaction_controller.setup_quality_inspection();
 		}
-
-		if (!frm.doc.is_return && frm.doc.docstatus == 1 && frm.doc.per_returned < 100) {
-			frm.add_custom_button(__('Subcontract Return'), function () {
-				frappe.model.open_mapped_doc({
-					method: 'erpnext.subcontracting.doctype.subcontracting_receipt.subcontracting_receipt.make_subcontract_return',
-					frm: frm
-				});
-			}, __('Create'));
-			frm.page.set_inner_btn_group_as_primary(__('Create'));
-		}
-
-		if (frm.doc.docstatus == 0) {
-			frm.add_custom_button(__('Subcontracting Order'), function () {
-				if (!frm.doc.supplier) {
-					frappe.throw({
-						title: __('Mandatory'),
-						message: __('Please Select a Supplier')
-					});
-				}
-
-				erpnext.utils.map_current_doc({
-					method: 'erpnext.subcontracting.doctype.subcontracting_order.subcontracting_order.make_subcontracting_receipt',
-					source_doctype: 'Subcontracting Order',
-					target: frm,
-					setters: {
-						supplier: frm.doc.supplier,
-					},
-					get_query_filters: {
-						docstatus: 1,
-						per_received: ['<', 100],
-						company: frm.doc.company
-					}
-				});
-			}, __('Get Items From'));
-
-			frm.fields_dict.supplied_items.grid.update_docfield_property('consumed_qty', 'read_only', frm.doc.__onload && frm.doc.__onload.backflush_based_on === 'BOM');
-		}
-	},
-
-	set_warehouse: (frm) => {
-		set_warehouse_in_children(frm.doc.items, 'warehouse', frm.doc.set_warehouse);
-	},
-
-	rejected_warehouse: (frm) => {
-		set_warehouse_in_children(frm.doc.items, 'rejected_warehouse', frm.doc.rejected_warehouse);
 	},
 });
 
 frappe.ui.form.on('Landed Cost Taxes and Charges', {
-	amount: function (frm, cdt, cdn) {
+	amount: (frm, cdt, cdn) => {
 		frm.events.set_base_amount(frm, cdt, cdn);
 	},
 
-	expense_account: function (frm, cdt, cdn) {
+	expense_account: (frm, cdt, cdn) => {
 		frm.events.set_account_currency(frm, cdt, cdn);
 	}
 });
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 60746d9..afe1b60 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -81,6 +81,9 @@
 		self.validate_posting_time()
 		self.validate_rejected_warehouse()
 
+		if not self.get("is_return"):
+			self.validate_inspection()
+
 		if getdate(self.posting_date) > getdate(nowdate()):
 			frappe.throw(_("Posting Date cannot be future date"))
 
@@ -270,17 +273,24 @@
 				status = "Draft"
 			elif self.docstatus == 1:
 				status = "Completed"
+
 				if self.is_return:
 					status = "Return"
-					return_against = frappe.get_doc("Subcontracting Receipt", self.return_against)
-					return_against.run_method("update_status")
 				elif self.per_returned == 100:
 					status = "Return Issued"
+
 			elif self.docstatus == 2:
 				status = "Cancelled"
 
+			if self.is_return:
+				frappe.get_doc("Subcontracting Receipt", self.return_against).update_status(
+					update_modified=update_modified
+				)
+
 		if status:
-			frappe.db.set_value("Subcontracting Receipt", self.name, "status", status, update_modified)
+			frappe.db.set_value(
+				"Subcontracting Receipt", self.name, "status", status, update_modified=update_modified
+			)
 
 	def get_gl_entries(self, warehouse_account=None):
 		from erpnext.accounts.general_ledger import process_gl_map
diff --git a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
index 887cba5..a170527 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/test_subcontracting_receipt.py
@@ -567,6 +567,64 @@
 			self.assertEqual(rm_item.rate, 100)
 			self.assertEqual(rm_item.amount, rm_item.consumed_qty * rm_item.rate)
 
+	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()
+		qa.cancel()
+		scr1.reload()
+		scr1.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()
+
 
 def make_return_subcontracting_receipt(**args):
 	args = frappe._dict(args)