Merge pull request #39377 from s-aga-r/BUMP-NODE

ci: bump node in release workflow
diff --git a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py
index 5a6bb69..adf5925 100644
--- a/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py
+++ b/erpnext/accounts/doctype/bank_reconciliation_tool/test_bank_reconciliation_tool.py
@@ -76,6 +76,7 @@
 					"deposit": 100,
 					"bank_account": self.bank_account,
 					"reference_number": "123",
+					"currency": "INR",
 				}
 			)
 			.save()
diff --git a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
index c38d273..fef3b56 100644
--- a/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
+++ b/erpnext/accounts/doctype/bank_transaction/bank_transaction.py
@@ -49,6 +49,24 @@
 
 	def validate(self):
 		self.validate_duplicate_references()
+		self.validate_currency()
+
+	def validate_currency(self):
+		"""
+		Bank Transaction should be on the same currency as the Bank Account.
+		"""
+		if self.currency and self.bank_account:
+			account = frappe.get_cached_value("Bank Account", self.bank_account, "account")
+			account_currency = frappe.get_cached_value("Account", account, "account_currency")
+
+			if self.currency != account_currency:
+				frappe.throw(
+					_(
+						"Transaction currency: {0} cannot be different from Bank Account({1}) currency: {2}"
+					).format(
+						frappe.bold(self.currency), frappe.bold(self.bank_account), frappe.bold(account_currency)
+					)
+				)
 
 	def set_status(self):
 		if self.docstatus == 2:
diff --git a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
index a2c0f86..f4a0175 100644
--- a/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
+++ b/erpnext/accounts/report/customer_ledger_summary/customer_ledger_summary.py
@@ -376,6 +376,10 @@
 		if not income_or_expense_accounts:
 			# prevent empty 'in' condition
 			income_or_expense_accounts.append("")
+		else:
+			# escape '%' in account name
+			# ignoring frappe.db.escape as it replaces single quotes with double quotes
+			income_or_expense_accounts = [x.replace("%", "%%") for x in income_or_expense_accounts]
 
 		accounts_query = (
 			qb.from_(gl)
diff --git a/erpnext/controllers/queries.py b/erpnext/controllers/queries.py
index 2650753..8ebdcc5 100644
--- a/erpnext/controllers/queries.py
+++ b/erpnext/controllers/queries.py
@@ -421,23 +421,14 @@
 	meta = frappe.get_meta(doctype, cached=True)
 	searchfields = meta.get_search_fields()
 
-	query = get_batches_from_stock_ledger_entries(searchfields, txt, filters)
-	bundle_query = get_batches_from_serial_and_batch_bundle(searchfields, txt, filters)
-
-	data = (
-		frappe.qb.from_((query) + (bundle_query))
-		.select("batch_no", "qty", "manufacturing_date", "expiry_date")
-		.offset(start)
-		.limit(page_len)
+	batches = get_batches_from_stock_ledger_entries(searchfields, txt, filters, start, page_len)
+	batches.extend(
+		get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start, page_len)
 	)
 
-	for field in searchfields:
-		data = data.select(field)
+	filtered_batches = get_filterd_batches(batches)
 
-	data = data.run()
-	data = get_filterd_batches(data)
-
-	return data
+	return filtered_batches
 
 
 def get_filterd_batches(data):
@@ -457,7 +448,7 @@
 	return filterd_batch
 
 
-def get_batches_from_stock_ledger_entries(searchfields, txt, filters):
+def get_batches_from_stock_ledger_entries(searchfields, txt, filters, start=0, page_len=100):
 	stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
 	batch_table = frappe.qb.DocType("Batch")
 
@@ -479,6 +470,8 @@
 			& (stock_ledger_entry.batch_no.isnotnull())
 		)
 		.groupby(stock_ledger_entry.batch_no, stock_ledger_entry.warehouse)
+		.offset(start)
+		.limit(page_len)
 	)
 
 	query = query.select(
@@ -493,16 +486,16 @@
 		query = query.select(batch_table[field])
 
 	if txt:
-		txt_condition = batch_table.name.like(txt)
+		txt_condition = batch_table.name.like("%{0}%".format(txt))
 		for field in searchfields + ["name"]:
-			txt_condition |= batch_table[field].like(txt)
+			txt_condition |= batch_table[field].like("%{0}%".format(txt))
 
 		query = query.where(txt_condition)
 
-	return query
+	return query.run(as_list=1) or []
 
 
-def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters):
+def get_batches_from_serial_and_batch_bundle(searchfields, txt, filters, start=0, page_len=100):
 	bundle = frappe.qb.DocType("Serial and Batch Entry")
 	stock_ledger_entry = frappe.qb.DocType("Stock Ledger Entry")
 	batch_table = frappe.qb.DocType("Batch")
@@ -527,6 +520,8 @@
 			& (stock_ledger_entry.serial_and_batch_bundle.isnotnull())
 		)
 		.groupby(bundle.batch_no, bundle.warehouse)
+		.offset(start)
+		.limit(page_len)
 	)
 
 	bundle_query = bundle_query.select(
@@ -541,13 +536,13 @@
 		bundle_query = bundle_query.select(batch_table[field])
 
 	if txt:
-		txt_condition = batch_table.name.like(txt)
+		txt_condition = batch_table.name.like("%{0}%".format(txt))
 		for field in searchfields + ["name"]:
-			txt_condition |= batch_table[field].like(txt)
+			txt_condition |= batch_table[field].like("%{0}%".format(txt))
 
 		bundle_query = bundle_query.where(txt_condition)
 
-	return bundle_query
+	return bundle_query.run(as_list=1)
 
 
 @frappe.whitelist()