Merge branch 'develop' into FIX-ISS-22-23-05936
diff --git a/erpnext/accounts/doctype/account/account.py b/erpnext/accounts/doctype/account/account.py
index ec0ba08..0404d1c 100644
--- a/erpnext/accounts/doctype/account/account.py
+++ b/erpnext/accounts/doctype/account/account.py
@@ -394,7 +394,13 @@
 
 	if ancestors and not allow_independent_account_creation:
 		for ancestor in ancestors:
-			if frappe.db.get_value("Account", {"account_name": old_acc_name, "company": ancestor}, "name"):
+			old_name = frappe.db.get_value(
+				"Account",
+				{"account_number": old_acc_number, "account_name": old_acc_name, "company": ancestor},
+				"name",
+			)
+
+			if old_name:
 				# same account in parent company exists
 				allow_child_account_creation = _("Allow Account Creation Against Child Company")
 
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index 94a1510..11de9a0 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -859,7 +859,7 @@
 						)
 					else:
 						self.qb_selection_filter.append(
-							self.ple[dimension.fieldname] == self.filters[dimension.fieldname]
+							self.ple[dimension.fieldname].isin(self.filters[dimension.fieldname])
 						)
 
 	def is_invoice(self, ple):
diff --git a/erpnext/accounts/report/gross_profit/gross_profit.py b/erpnext/accounts/report/gross_profit/gross_profit.py
index fde4de8..01fee28 100644
--- a/erpnext/accounts/report/gross_profit/gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/gross_profit.py
@@ -501,7 +501,14 @@
 						):
 							returned_item_rows = self.returned_invoices[row.parent][row.item_code]
 							for returned_item_row in returned_item_rows:
-								row.qty += flt(returned_item_row.qty)
+								# returned_items 'qty' should be stateful
+								if returned_item_row.qty != 0:
+									if row.qty >= abs(returned_item_row.qty):
+										row.qty += returned_item_row.qty
+										returned_item_row.qty = 0
+									else:
+										row.qty = 0
+										returned_item_row.qty += row.qty
 								row.base_amount += flt(returned_item_row.base_amount, self.currency_precision)
 							row.buying_amount = flt(flt(row.qty) * flt(row.buying_rate), self.currency_precision)
 						if flt(row.qty) or row.base_amount:
@@ -734,6 +741,8 @@
 		if self.filters.to_date:
 			conditions += " and posting_date <= %(to_date)s"
 
+		conditions += " and (is_return = 0 or (is_return=1 and return_against is null))"
+
 		if self.filters.item_group:
 			conditions += " and {0}".format(get_item_group_condition(self.filters.item_group))
 
diff --git a/erpnext/accounts/report/gross_profit/test_gross_profit.py b/erpnext/accounts/report/gross_profit/test_gross_profit.py
index 21681be..82fe1a0 100644
--- a/erpnext/accounts/report/gross_profit/test_gross_profit.py
+++ b/erpnext/accounts/report/gross_profit/test_gross_profit.py
@@ -381,3 +381,82 @@
 		}
 		gp_entry = [x for x in data if x.parent_invoice == sinv.name]
 		self.assertDictContainsSubset(expected_entry, gp_entry[0])
+
+	def test_crnote_against_invoice_with_multiple_instances_of_same_item(self):
+		"""
+		Item Qty for Sales Invoices with multiple instances of same item go in the -ve. Ideally, the credit noteshould cancel out the invoice items.
+		"""
+		from erpnext.accounts.doctype.sales_invoice.sales_invoice import make_sales_return
+
+		# Invoice with an item added twice
+		sinv = self.create_sales_invoice(qty=1, rate=100, posting_date=nowdate(), do_not_submit=True)
+		sinv.append("items", frappe.copy_doc(sinv.items[0], ignore_no_copy=False))
+		sinv = sinv.save().submit()
+
+		# Create Credit Note for Invoice
+		cr_note = make_sales_return(sinv.name)
+		cr_note = cr_note.save().submit()
+
+		filters = frappe._dict(
+			company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
+		)
+
+		columns, data = execute(filters=filters)
+		expected_entry = {
+			"parent_invoice": sinv.name,
+			"currency": "INR",
+			"sales_invoice": self.item,
+			"customer": self.customer,
+			"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
+			"item_code": self.item,
+			"item_name": self.item,
+			"warehouse": "Stores - _GP",
+			"qty": 0.0,
+			"avg._selling_rate": 0.0,
+			"valuation_rate": 0.0,
+			"selling_amount": -100.0,
+			"buying_amount": 0.0,
+			"gross_profit": -100.0,
+			"gross_profit_%": 100.0,
+		}
+		gp_entry = [x for x in data if x.parent_invoice == sinv.name]
+		# Both items of Invoice should have '0' qty
+		self.assertEqual(len(gp_entry), 2)
+		self.assertDictContainsSubset(expected_entry, gp_entry[0])
+		self.assertDictContainsSubset(expected_entry, gp_entry[1])
+
+	def test_standalone_cr_notes(self):
+		"""
+		Standalone cr notes will be reported as usual
+		"""
+		# Make Cr Note
+		sinv = self.create_sales_invoice(
+			qty=-1, rate=100, posting_date=nowdate(), do_not_save=True, do_not_submit=True
+		)
+		sinv.is_return = 1
+		sinv = sinv.save().submit()
+
+		filters = frappe._dict(
+			company=self.company, from_date=nowdate(), to_date=nowdate(), group_by="Invoice"
+		)
+
+		columns, data = execute(filters=filters)
+		expected_entry = {
+			"parent_invoice": sinv.name,
+			"currency": "INR",
+			"sales_invoice": self.item,
+			"customer": self.customer,
+			"posting_date": frappe.utils.datetime.date.fromisoformat(nowdate()),
+			"item_code": self.item,
+			"item_name": self.item,
+			"warehouse": "Stores - _GP",
+			"qty": -1.0,
+			"avg._selling_rate": 100.0,
+			"valuation_rate": 0.0,
+			"selling_amount": -100.0,
+			"buying_amount": 0.0,
+			"gross_profit": -100.0,
+			"gross_profit_%": 100.0,
+		}
+		gp_entry = [x for x in data if x.parent_invoice == sinv.name]
+		self.assertDictContainsSubset(expected_entry, gp_entry[0])
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 2608c03..005a2f1 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -455,7 +455,9 @@
 			try:
 				doc.validate_total_debit_and_credit()
 			except Exception as validation_exception:
-				raise frappe.ValidationError(_(f"Validation Error for {doc.name}")) from validation_exception
+				raise frappe.ValidationError(
+					_("Validation Error for {0}").format(doc.name)
+				) from validation_exception
 
 		doc.save(ignore_permissions=True)
 		# re-submit advance entry
diff --git a/erpnext/patches.txt b/erpnext/patches.txt
index c1d4b82..d42b012 100644
--- a/erpnext/patches.txt
+++ b/erpnext/patches.txt
@@ -322,6 +322,7 @@
 erpnext.patches.v14_0.update_entry_type_for_journal_entry
 erpnext.patches.v14_0.change_autoname_for_tax_withheld_vouchers
 erpnext.patches.v14_0.set_pick_list_status
+erpnext.patches.v13_0.update_docs_link
 erpnext.patches.v15_0.update_asset_value_for_manual_depr_entries
 erpnext.patches.v15_0.update_gpa_and_ndb_for_assdeprsch
 # below migration patches should always run last
diff --git a/erpnext/patches/v13_0/update_docs_link.py b/erpnext/patches/v13_0/update_docs_link.py
new file mode 100644
index 0000000..4bc5c05
--- /dev/null
+++ b/erpnext/patches/v13_0/update_docs_link.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and Contributors
+# License: MIT. See LICENSE
+
+
+import frappe
+
+
+def execute():
+	navbar_settings = frappe.get_single("Navbar Settings")
+	for item in navbar_settings.help_dropdown:
+		if item.is_standard and item.route == "https://erpnext.com/docs/user/manual":
+			item.route = "https://docs.erpnext.com/docs/v14/user/manual/en/introduction"
+
+	navbar_settings.save()
diff --git a/erpnext/setup/install.py b/erpnext/setup/install.py
index 1f7dddf..088958d 100644
--- a/erpnext/setup/install.py
+++ b/erpnext/setup/install.py
@@ -155,7 +155,7 @@
 		{
 			"item_label": "Documentation",
 			"item_type": "Route",
-			"route": "https://erpnext.com/docs/user/manual",
+			"route": "https://docs.erpnext.com/docs/v14/user/manual/en/introduction",
 			"is_standard": 1,
 		},
 		{
diff --git a/erpnext/stock/doctype/pick_list/pick_list.py b/erpnext/stock/doctype/pick_list/pick_list.py
index bf3b5dd..46d6e9e 100644
--- a/erpnext/stock/doctype/pick_list/pick_list.py
+++ b/erpnext/stock/doctype/pick_list/pick_list.py
@@ -172,8 +172,8 @@
 			if (row.picked_qty / row.stock_qty) * 100 > over_delivery_receipt_allowance:
 				frappe.throw(
 					_(
-						f"You are picking more than required quantity for the item {row.item_code}. Check if there is any other pick list created for the sales order {row.sales_order}."
-					)
+						"You are picking more than required quantity for the item {0}. Check if there is any other pick list created for the sales order {1}."
+					).format(row.item_code, row.sales_order)
 				)
 
 	@frappe.whitelist()
diff --git a/erpnext/stock/doctype/stock_entry/stock_entry.py b/erpnext/stock/doctype/stock_entry/stock_entry.py
index 7f69397..36c875f 100644
--- a/erpnext/stock/doctype/stock_entry/stock_entry.py
+++ b/erpnext/stock/doctype/stock_entry/stock_entry.py
@@ -658,6 +658,7 @@
 		)
 		finished_item_qty = sum(d.transfer_qty for d in self.items if d.is_finished_item)
 
+		items = []
 		# Set basic rate for incoming items
 		for d in self.get("items"):
 			if d.s_warehouse or d.set_basic_rate_manually:
@@ -665,12 +666,7 @@
 
 			if d.allow_zero_valuation_rate:
 				d.basic_rate = 0.0
-				frappe.msgprint(
-					_(
-						"Row {0}: Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {1}"
-					).format(d.idx, d.item_code),
-					alert=1,
-				)
+				items.append(d.item_code)
 
 			elif d.is_finished_item:
 				if self.purpose == "Manufacture":
@@ -697,6 +693,20 @@
 			d.basic_rate = flt(d.basic_rate)
 			d.basic_amount = flt(flt(d.transfer_qty) * flt(d.basic_rate), d.precision("basic_amount"))
 
+		if items:
+			message = ""
+
+			if len(items) > 1:
+				message = _(
+					"Items rate has been updated to zero as Allow Zero Valuation Rate is checked for the following items: {0}"
+				).format(", ".join(frappe.bold(item) for item in items))
+			else:
+				message = _(
+					"Item rate has been updated to zero as Allow Zero Valuation Rate is checked for item {0}"
+				).format(frappe.bold(items[0]))
+
+			frappe.msgprint(message, alert=True)
+
 	def set_rate_for_outgoing_items(self, reset_outgoing_rate=True, raise_error_if_no_rate=True):
 		outgoing_items_cost = 0.0
 		for d in self.get("items"):