Merge pull request #36786 from s-aga-r/SCR-QI

feat: `Quality Inspection` for Subcontracting Receipt
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.py b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
index 07b46a4..7ef5278 100644
--- a/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
+++ b/erpnext/accounts/doctype/payment_reconciliation/payment_reconciliation.py
@@ -186,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,
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/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_depreciation_schedule/asset_depreciation_schedule.py b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
index 2b4b248..83350aa 100644
--- a/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
+++ b/erpnext/assets/doctype/asset_depreciation_schedule/asset_depreciation_schedule.py
@@ -779,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,
@@ -789,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/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/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/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
index 58afc2f..afe1b60 100644
--- a/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
+++ b/erpnext/subcontracting/doctype/subcontracting_receipt/subcontracting_receipt.py
@@ -273,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