Merge branch 'develop' of https://github.com/frappe/erpnext into dev_fr_translation
diff --git a/erpnext/accounts/doctype/payment_entry/payment_entry.js b/erpnext/accounts/doctype/payment_entry/payment_entry.js
index 794a4ef..0203c45 100644
--- a/erpnext/accounts/doctype/payment_entry/payment_entry.js
+++ b/erpnext/accounts/doctype/payment_entry/payment_entry.js
@@ -154,6 +154,12 @@
 		frm.events.set_dynamic_labels(frm);
 		frm.events.show_general_ledger(frm);
 		erpnext.accounts.ledger_preview.show_accounting_ledger_preview(frm);
+		if(frm.doc.references.find((elem) => {return elem.exchange_gain_loss != 0})) {
+			frm.add_custom_button(__("View Exchange Gain/Loss Journals"), function() {
+				frappe.set_route("List", "Journal Entry", {"voucher_type": "Exchange Gain Or Loss", "reference_name": frm.doc.name});
+			}, __('Actions'));
+
+		}
 		erpnext.accounts.unreconcile_payments.add_unreconcile_btn(frm);
 	},
 
diff --git a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
index af1c066..d984d86 100644
--- a/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
+++ b/erpnext/accounts/doctype/period_closing_voucher/period_closing_voucher.py
@@ -33,7 +33,7 @@
 	def on_cancel(self):
 		self.validate_future_closing_vouchers()
 		self.db_set("gle_processing_status", "In Progress")
-		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry")
+		self.ignore_linked_doctypes = ("GL Entry", "Stock Ledger Entry", "Payment Ledger Entry")
 		gle_count = frappe.db.count(
 			"GL Entry",
 			{"voucher_type": "Period Closing Voucher", "voucher_no": self.name, "is_cancelled": 0},
diff --git a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
index 9ffdaf6..84b0149 100644
--- a/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
+++ b/erpnext/accounts/doctype/sales_invoice/test_sales_invoice.py
@@ -1801,6 +1801,10 @@
 		)
 
 	def test_outstanding_amount_after_advance_payment_entry_cancellation(self):
+		"""Test impact of advance PE submission/cancellation on SI and SO."""
+		from erpnext.selling.doctype.sales_order.test_sales_order import make_sales_order
+
+		sales_order = make_sales_order(item_code="138-CMS Shoe", qty=1, price_list_rate=500)
 		pe = frappe.get_doc(
 			{
 				"doctype": "Payment Entry",
@@ -1820,10 +1824,25 @@
 				"paid_to": "_Test Cash - _TC",
 			}
 		)
+		pe.append(
+			"references",
+			{
+				"reference_doctype": "Sales Order",
+				"reference_name": sales_order.name,
+				"total_amount": sales_order.grand_total,
+				"outstanding_amount": sales_order.grand_total,
+				"allocated_amount": 300,
+			},
+		)
 		pe.insert()
 		pe.submit()
 
+		sales_order.reload()
+		self.assertEqual(sales_order.advance_paid, 300)
+
 		si = frappe.copy_doc(test_records[0])
+		si.items[0].sales_order = sales_order.name
+		si.items[0].so_detail = sales_order.get("items")[0].name
 		si.is_pos = 0
 		si.append(
 			"advances",
@@ -1831,6 +1850,7 @@
 				"doctype": "Sales Invoice Advance",
 				"reference_type": "Payment Entry",
 				"reference_name": pe.name,
+				"reference_row": pe.references[0].name,
 				"advance_amount": 300,
 				"allocated_amount": 300,
 				"remarks": pe.remarks,
@@ -1839,7 +1859,13 @@
 		si.insert()
 		si.submit()
 
-		si.load_from_db()
+		si.reload()
+		pe.reload()
+		sales_order.reload()
+
+		# Check if SO is unlinked/replaced by SI in PE & if SO advance paid is 0
+		self.assertEqual(pe.references[0].reference_name, si.name)
+		self.assertEqual(sales_order.advance_paid, 0.0)
 
 		# check outstanding after advance allocation
 		self.assertEqual(
@@ -1847,11 +1873,9 @@
 			flt(si.rounded_total - si.total_advance, si.precision("outstanding_amount")),
 		)
 
-		# added to avoid Document has been modified exception
-		pe = frappe.get_doc("Payment Entry", pe.name)
 		pe.cancel()
+		si.reload()
 
-		si.load_from_db()
 		# check outstanding after advance cancellation
 		self.assertEqual(
 			flt(si.outstanding_amount),
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 1360f73..555ed4f 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -581,6 +581,10 @@
 	"""
 	jv_detail = journal_entry.get("accounts", {"name": d["voucher_detail_no"]})[0]
 
+	# Update Advance Paid in SO/PO since they might be getting unlinked
+	if jv_detail.get("reference_type") in ("Sales Order", "Purchase Order"):
+		frappe.get_doc(jv_detail.reference_type, jv_detail.reference_name).set_total_advance_paid()
+
 	if flt(d["unadjusted_amount"]) - flt(d["allocated_amount"]) != 0:
 		# adjust the unreconciled balance
 		amount_in_account_currency = flt(d["unadjusted_amount"]) - flt(d["allocated_amount"])
@@ -647,6 +651,13 @@
 
 	if d.voucher_detail_no:
 		existing_row = payment_entry.get("references", {"name": d["voucher_detail_no"]})[0]
+
+		# Update Advance Paid in SO/PO since they are getting unlinked
+		if existing_row.get("reference_doctype") in ("Sales Order", "Purchase Order"):
+			frappe.get_doc(
+				existing_row.reference_doctype, existing_row.reference_name
+			).set_total_advance_paid()
+
 		original_row = existing_row.as_dict().copy()
 		existing_row.update(reference_details)
 
diff --git a/erpnext/public/js/utils/unreconcile.js b/erpnext/public/js/utils/unreconcile.js
index bbdd51d..fa00ed2 100644
--- a/erpnext/public/js/utils/unreconcile.js
+++ b/erpnext/public/js/utils/unreconcile.js
@@ -17,7 +17,7 @@
 				},
 				callback: function(r) {
 					if (r.message) {
-						frm.add_custom_button(__("Un-Reconcile"), function() {
+						frm.add_custom_button(__("UnReconcile"), function() {
 							erpnext.accounts.unreconcile_payments.build_unreconcile_dialog(frm);
 						}, __('Actions'));
 					}
@@ -87,11 +87,11 @@
 						unreconcile_dialog_fields[0].get_data = function(){ return r.message};
 
 						let d = new frappe.ui.Dialog({
-							title: 'Un-Reconcile Allocations',
+							title: 'UnReconcile Allocations',
 							fields: unreconcile_dialog_fields,
 							size: 'large',
 							cannot_add_rows: true,
-							primary_action_label: 'Un-Reconcile',
+							primary_action_label: 'UnReconcile',
 							primary_action(values) {
 
 								let selected_allocations = values.allocations.filter(x=>x.__checked);
diff --git a/erpnext/stock/dashboard/item_dashboard.py b/erpnext/stock/dashboard/item_dashboard.py
index 5a8d84a..e638268 100644
--- a/erpnext/stock/dashboard/item_dashboard.py
+++ b/erpnext/stock/dashboard/item_dashboard.py
@@ -3,7 +3,7 @@
 from frappe.utils import cint, flt
 
 from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-	get_sre_reserved_qty_for_item_and_warehouse as get_reserved_stock,
+	get_sre_reserved_qty_for_items_and_warehouses as get_reserved_stock_details,
 )
 
 
@@ -61,7 +61,10 @@
 		limit_page_length=21,
 	)
 
-	sre_reserved_stock_details = get_reserved_stock(item_code, warehouse)
+	item_code_list = [item_code] if item_code else [i.item_code for i in items]
+	warehouse_list = [warehouse] if warehouse else [i.warehouse for i in items]
+
+	sre_reserved_stock_details = get_reserved_stock_details(item_code_list, warehouse_list)
 	precision = cint(frappe.db.get_single_value("System Settings", "float_precision"))
 
 	for item in items:
@@ -75,7 +78,8 @@
 				"reserved_qty_for_production": flt(item.reserved_qty_for_production, precision),
 				"reserved_qty_for_sub_contract": flt(item.reserved_qty_for_sub_contract, precision),
 				"actual_qty": flt(item.actual_qty, precision),
-				"reserved_stock": sre_reserved_stock_details,
+				"reserved_stock": flt(sre_reserved_stock_details.get((item.item_code, item.warehouse))),
 			}
 		)
+
 	return items
diff --git a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
index 26ca012..e36d576 100644
--- a/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
+++ b/erpnext/stock/doctype/stock_reconciliation/stock_reconciliation.py
@@ -346,7 +346,7 @@
 		"""Raises an exception if there is any reserved stock for the items in the Stock Reconciliation."""
 
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-			get_sre_reserved_qty_for_item_and_warehouse as get_sre_reserved_qty_details,
+			get_sre_reserved_qty_for_items_and_warehouses as get_sre_reserved_qty_details,
 		)
 
 		item_code_list, warehouse_list = [], []
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js
index 4d96636..c5df319 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.js
@@ -1,42 +1,42 @@
 // Copyright (c) 2023, Frappe Technologies Pvt. Ltd. and contributors
 // For license information, please see license.txt
 
-frappe.ui.form.on("Stock Reservation Entry", {
+frappe.ui.form.on('Stock Reservation Entry', {
 	refresh(frm) {
-		frm.trigger("set_queries");
-		frm.trigger("toggle_read_only_fields");
-		frm.trigger("hide_rate_related_fields");
-		frm.trigger("hide_primary_action_button");
-		frm.trigger("make_sb_entries_warehouse_read_only");
+		frm.trigger('set_queries');
+		frm.trigger('toggle_read_only_fields');
+		frm.trigger('hide_rate_related_fields');
+		frm.trigger('hide_primary_action_button');
+		frm.trigger('make_sb_entries_warehouse_read_only');
 	},
 
 	has_serial_no(frm) {
-		frm.trigger("toggle_read_only_fields");
+		frm.trigger('toggle_read_only_fields');
 	},
 
 	has_batch_no(frm) {
-		frm.trigger("toggle_read_only_fields");
+		frm.trigger('toggle_read_only_fields');
 	},
 
 	warehouse(frm) {
 		if (frm.doc.warehouse) {
 			frm.doc.sb_entries.forEach((row) => {
-				frappe.model.set_value(row.doctype, row.name, "warehouse", frm.doc.warehouse);
+				frappe.model.set_value(row.doctype, row.name, 'warehouse', frm.doc.warehouse);
 			});
 		}
 	},
 
 	set_queries(frm) {
-		frm.set_query("warehouse", () => {
+		frm.set_query('warehouse', () => {
 			return {
 				filters: {
-					"is_group": 0,
-					"company": frm.doc.company,
+					'is_group': 0,
+					'company': frm.doc.company,
 				}
 			};
 		});
 
-		frm.set_query("serial_no", "sb_entries", function(doc, cdt, cdn) {
+		frm.set_query('serial_no', 'sb_entries', function(doc, cdt, cdn) {
 			var selected_serial_nos = doc.sb_entries.map(row => {
 				return row.serial_no;
 			});
@@ -45,16 +45,16 @@
 				filters: {
 					item_code: doc.item_code,
 					warehouse: row.warehouse,
-					status: "Active",
-					name: ["not in", selected_serial_nos],
+					status: 'Active',
+					name: ['not in', selected_serial_nos],
 				}
 			}
 		});
 
-		frm.set_query("batch_no", "sb_entries", function(doc, cdt, cdn) {
+		frm.set_query('batch_no', 'sb_entries', function(doc, cdt, cdn) {
 			let filters = {
 				item: doc.item_code,
-				batch_qty: [">", 0],
+				batch_qty: ['>', 0],
 				disabled: 0,
 			}
 
@@ -63,7 +63,7 @@
 					return row.batch_no;
 				});
 
-				filters.name = ["not in", selected_batch_nos];
+				filters.name = ['not in', selected_batch_nos];
 			}
 
 			return { filters: filters }
@@ -74,37 +74,37 @@
 		if (frm.doc.has_serial_no) {
 			frm.doc.sb_entries.forEach(row => {
 				if (row.qty !== 1) {
-					frappe.model.set_value(row.doctype, row.name, "qty", 1);
+					frappe.model.set_value(row.doctype, row.name, 'qty', 1);
 				}
 			})
 		}
 
 		frm.fields_dict.sb_entries.grid.update_docfield_property(
-			"serial_no", "read_only", !frm.doc.has_serial_no
+			'serial_no', 'read_only', !frm.doc.has_serial_no
 		);
 
 		frm.fields_dict.sb_entries.grid.update_docfield_property(
-			"batch_no", "read_only", !frm.doc.has_batch_no
+			'batch_no', 'read_only', !frm.doc.has_batch_no
 		);
 
 		// Qty will always be 1 for Serial No.
 		frm.fields_dict.sb_entries.grid.update_docfield_property(
-			"qty", "read_only", frm.doc.has_serial_no
+			'qty', 'read_only', frm.doc.has_serial_no
 		);
 
-		frm.set_df_property("sb_entries", "allow_on_submit", frm.doc.against_pick_list ? 0 : 1);
+		frm.set_df_property('sb_entries', 'allow_on_submit', frm.doc.against_pick_list ? 0 : 1);
 	},
 
 	hide_rate_related_fields(frm) {
-		["incoming_rate", "outgoing_rate", "stock_value_difference", "is_outward", "stock_queue"].forEach(field => {
+		['incoming_rate', 'outgoing_rate', 'stock_value_difference', 'is_outward', 'stock_queue'].forEach(field => {
 			frm.fields_dict.sb_entries.grid.update_docfield_property(
-				field, "hidden", 1
+				field, 'hidden', 1
 			);
 		});
 	},
 
 	hide_primary_action_button(frm) {
-		// Hide "Amend" button on cancelled document
+		// Hide 'Amend' button on cancelled document
 		if (frm.doc.docstatus == 2) {
 			frm.page.btn_primary.hide()
 		}
@@ -112,15 +112,15 @@
 
 	make_sb_entries_warehouse_read_only(frm) {
 		frm.fields_dict.sb_entries.grid.update_docfield_property(
-			"warehouse", "read_only", 1
+			'warehouse', 'read_only', 1
 		);
 	},
 });
 
-frappe.ui.form.on("Serial and Batch Entry", {
+frappe.ui.form.on('Serial and Batch Entry', {
 	sb_entries_add(frm, cdt, cdn) {
 		if (frm.doc.warehouse) {
-			frappe.model.set_value(cdt, cdn, "warehouse", frm.doc.warehouse);
+			frappe.model.set_value(cdt, cdn, 'warehouse', frm.doc.warehouse);
 		}
 	},
 });
\ No newline at end of file
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
index bd7bb66..936be3f 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry.py
@@ -14,7 +14,7 @@
 
 		self.validate_amended_doc()
 		self.validate_mandatory()
-		self.validate_for_group_warehouse()
+		self.validate_group_warehouse()
 		validate_disabled_warehouse(self.warehouse)
 		validate_warehouse_company(self.warehouse, self.company)
 		self.validate_uom_is_integer()
@@ -74,7 +74,7 @@
 				msg = _("{0} is required").format(self.meta.get_label(d))
 				frappe.throw(msg)
 
-	def validate_for_group_warehouse(self) -> None:
+	def validate_group_warehouse(self) -> None:
 		"""Raises an exception if `Warehouse` is a Group Warehouse."""
 
 		if frappe.get_cached_value("Warehouse", self.warehouse, "is_group"):
@@ -544,10 +544,36 @@
 	return available_serial_nos_list
 
 
-def get_sre_reserved_qty_for_item_and_warehouse(
-	item_code: str | list, warehouse: str | list = None
-) -> float | dict:
-	"""Returns `Reserved Qty` for Item and Warehouse combination OR a dict like {("item_code", "warehouse"): "reserved_qty", ... }."""
+def get_sre_reserved_qty_for_item_and_warehouse(item_code: str, warehouse: str = None) -> float:
+	"""Returns current `Reserved Qty` for Item and Warehouse combination."""
+
+	sre = frappe.qb.DocType("Stock Reservation Entry")
+	query = (
+		frappe.qb.from_(sre)
+		.select(Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"))
+		.where(
+			(sre.docstatus == 1)
+			& (sre.item_code == item_code)
+			& (sre.status.notin(["Delivered", "Cancelled"]))
+		)
+		.groupby(sre.item_code, sre.warehouse)
+	)
+
+	if warehouse:
+		query = query.where(sre.warehouse == warehouse)
+
+	reserved_qty = query.run(as_list=True)
+
+	return flt(reserved_qty[0][0]) if reserved_qty else 0.0
+
+
+def get_sre_reserved_qty_for_items_and_warehouses(
+	item_code_list: list, warehouse_list: list = None
+) -> dict:
+	"""Returns a dict like {("item_code", "warehouse"): "reserved_qty", ... }."""
+
+	if not item_code_list:
+		return {}
 
 	sre = frappe.qb.DocType("Stock Reservation Entry")
 	query = (
@@ -557,29 +583,20 @@
 			sre.warehouse,
 			Sum(sre.reserved_qty - sre.delivered_qty).as_("reserved_qty"),
 		)
-		.where((sre.docstatus == 1) & (sre.status.notin(["Delivered", "Cancelled"])))
+		.where(
+			(sre.docstatus == 1)
+			& sre.item_code.isin(item_code_list)
+			& (sre.status.notin(["Delivered", "Cancelled"]))
+		)
 		.groupby(sre.item_code, sre.warehouse)
 	)
 
-	query = (
-		query.where(sre.item_code.isin(item_code))
-		if isinstance(item_code, list)
-		else query.where(sre.item_code == item_code)
-	)
-
-	if warehouse:
-		query = (
-			query.where(sre.warehouse.isin(warehouse))
-			if isinstance(warehouse, list)
-			else query.where(sre.warehouse == warehouse)
-		)
+	if warehouse_list:
+		query = query.where(sre.warehouse.isin(warehouse_list))
 
 	data = query.run(as_dict=True)
 
-	if isinstance(item_code, str) and isinstance(warehouse, str):
-		return data[0]["reserved_qty"] if data else 0.0
-	else:
-		return {(d["item_code"], d["warehouse"]): d["reserved_qty"] for d in data} if data else {}
+	return {(d["item_code"], d["warehouse"]): d["reserved_qty"] for d in data} if data else {}
 
 
 def get_sre_reserved_qty_details_for_voucher(voucher_type: str, voucher_no: str) -> dict:
@@ -711,7 +728,7 @@
 	).run(as_dict=True)
 
 
-def get_ssb_bundle_for_voucher(sre: dict) -> object | None:
+def get_ssb_bundle_for_voucher(sre: dict) -> object:
 	"""Returns a new `Serial and Batch Bundle` against the provided SRE."""
 
 	sb_entries = get_serial_batch_entries_for_voucher(sre["name"])
diff --git a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry_list.js b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry_list.js
index 442ac39..5b390f7 100644
--- a/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry_list.js
+++ b/erpnext/stock/doctype/stock_reservation_entry/stock_reservation_entry_list.js
@@ -4,13 +4,14 @@
 frappe.listview_settings['Stock Reservation Entry'] = {
 	get_indicator: function (doc) {
 		const status_colors = {
-            'Draft': 'red',
-            'Partially Reserved': 'orange',
-            'Reserved': 'blue',
-            'Partially Delivered': 'purple',
-            'Delivered': 'green',
-            'Cancelled': 'red',
+                  'Draft': 'red',
+                  'Partially Reserved': 'orange',
+                  'Reserved': 'blue',
+                  'Partially Delivered': 'purple',
+                  'Delivered': 'green',
+                  'Cancelled': 'red',
 		};
-		return [__(doc.status), status_colors[doc.status], 'status,=,' + doc.status];
+
+            return [__(doc.status), status_colors[doc.status], 'status,=,' + doc.status];
 	},
 };
\ No newline at end of file
diff --git a/erpnext/stock/report/stock_balance/stock_balance.py b/erpnext/stock/report/stock_balance/stock_balance.py
index 337b0ea..a59f9de 100644
--- a/erpnext/stock/report/stock_balance/stock_balance.py
+++ b/erpnext/stock/report/stock_balance/stock_balance.py
@@ -165,7 +165,7 @@
 
 	def get_sre_reserved_qty_details(self) -> dict:
 		from erpnext.stock.doctype.stock_reservation_entry.stock_reservation_entry import (
-			get_sre_reserved_qty_for_item_and_warehouse as get_reserved_qty_details,
+			get_sre_reserved_qty_for_items_and_warehouses as get_reserved_qty_details,
 		)
 
 		item_code_list, warehouse_list = [], []