Merge branch 'develop' into subcontract-supplied-items
diff --git a/.github/workflows/patch.yml b/.github/workflows/patch.yml
index afabe43..2cf4444 100644
--- a/.github/workflows/patch.yml
+++ b/.github/workflows/patch.yml
@@ -115,4 +115,5 @@
           echo "Updating to latest version"
           git -C "apps/frappe" checkout -q -f "${GITHUB_BASE_REF:-${GITHUB_REF##*/}}"
           git -C "apps/erpnext" checkout -q -f "$GITHUB_SHA"
+          bench setup requirements --python
           bench --site test_site migrate
diff --git a/.mergify.yml b/.mergify.yml
index cc8c080..d7f82e6 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -9,6 +9,8 @@
           - author!=nabinhait
           - author!=ankush
           - author!=deepeshgarg007
+          - author!=mergify[bot]
+
         - or:
           - base=version-13
           - base=version-12
@@ -19,6 +21,16 @@
             @{{author}}, thanks for the contribution, but we do not accept pull requests on a stable branch. Please raise PR on an appropriate hotfix branch.
             https://github.com/frappe/erpnext/wiki/Pull-Request-Checklist#which-branch
 
+  - name: Auto-close PRs on pre-release branch
+    conditions:
+      - base=version-13-pre-release
+    actions:
+      close:
+      comment:
+          message: |
+            @{{author}}, pre-release branch is not maintained anymore. Releases are directly done by merging hotfix branch to stable branches.
+
+
   - name: backport to develop
     conditions:
       - label="backport develop"
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
index 748bcde..0238711 100644
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.js
@@ -173,11 +173,6 @@
 			"fieldtype": "Check",
 		},
 		{
-			"fieldname": "show_remarks",
-			"label": __("Show Remarks"),
-			"fieldtype": "Check",
-		},
-		{
 			"fieldname": "tax_id",
 			"label": __("Tax Id"),
 			"fieldtype": "Data",
diff --git a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
index de9d63d..1911152 100755
--- a/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/accounts_receivable.py
@@ -5,7 +5,9 @@
 from collections import OrderedDict
 
 import frappe
-from frappe import _, scrub
+from frappe import _, qb, scrub
+from frappe.query_builder import Criterion
+from frappe.query_builder.functions import Date
 from frappe.utils import cint, cstr, flt, getdate, nowdate
 
 from erpnext.accounts.doctype.accounting_dimension.accounting_dimension import (
@@ -41,6 +43,8 @@
 class ReceivablePayableReport(object):
 	def __init__(self, filters=None):
 		self.filters = frappe._dict(filters or {})
+		self.qb_selection_filter = []
+		self.ple = qb.DocType("Payment Ledger Entry")
 		self.filters.report_date = getdate(self.filters.report_date or nowdate())
 		self.age_as_on = (
 			getdate(nowdate())
@@ -78,7 +82,7 @@
 			self.skip_total_row = 1
 
 	def get_data(self):
-		self.get_gl_entries()
+		self.get_ple_entries()
 		self.get_sales_invoices_or_customers_based_on_sales_person()
 		self.voucher_balance = OrderedDict()
 		self.init_voucher_balance()  # invoiced, paid, credit_note, outstanding
@@ -96,25 +100,25 @@
 		self.get_return_entries()
 
 		self.data = []
-		for gle in self.gl_entries:
-			self.update_voucher_balance(gle)
+
+		for ple in self.ple_entries:
+			self.update_voucher_balance(ple)
 
 		self.build_data()
 
 	def init_voucher_balance(self):
 		# build all keys, since we want to exclude vouchers beyond the report date
-		for gle in self.gl_entries:
+		for ple in self.ple_entries:
 			# get the balance object for voucher_type
-			key = (gle.voucher_type, gle.voucher_no, gle.party)
+			key = (ple.voucher_type, ple.voucher_no, ple.party)
 			if not key in self.voucher_balance:
 				self.voucher_balance[key] = frappe._dict(
-					voucher_type=gle.voucher_type,
-					voucher_no=gle.voucher_no,
-					party=gle.party,
-					party_account=gle.account,
-					posting_date=gle.posting_date,
-					account_currency=gle.account_currency,
-					remarks=gle.remarks if self.filters.get("show_remarks") else None,
+					voucher_type=ple.voucher_type,
+					voucher_no=ple.voucher_no,
+					party=ple.party,
+					party_account=ple.account,
+					posting_date=ple.posting_date,
+					account_currency=ple.account_currency,
 					invoiced=0.0,
 					paid=0.0,
 					credit_note=0.0,
@@ -124,23 +128,22 @@
 					credit_note_in_account_currency=0.0,
 					outstanding_in_account_currency=0.0,
 				)
-			self.get_invoices(gle)
 
 			if self.filters.get("group_by_party"):
-				self.init_subtotal_row(gle.party)
+				self.init_subtotal_row(ple.party)
 
 		if self.filters.get("group_by_party"):
 			self.init_subtotal_row("Total")
 
-	def get_invoices(self, gle):
-		if gle.voucher_type in ("Sales Invoice", "Purchase Invoice"):
+	def get_invoices(self, ple):
+		if ple.voucher_type in ("Sales Invoice", "Purchase Invoice"):
 			if self.filters.get("sales_person"):
-				if gle.voucher_no in self.sales_person_records.get(
+				if ple.voucher_no in self.sales_person_records.get(
 					"Sales Invoice", []
-				) or gle.party in self.sales_person_records.get("Customer", []):
-					self.invoices.add(gle.voucher_no)
+				) or ple.party in self.sales_person_records.get("Customer", []):
+					self.invoices.add(ple.voucher_no)
 			else:
-				self.invoices.add(gle.voucher_no)
+				self.invoices.add(ple.voucher_no)
 
 	def init_subtotal_row(self, party):
 		if not self.total_row_map.get(party):
@@ -162,39 +165,49 @@
 			"range5",
 		]
 
-	def update_voucher_balance(self, gle):
+	def get_voucher_balance(self, ple):
+		if self.filters.get("sales_person"):
+			if not (
+				ple.party in self.sales_person_records.get("Customer", [])
+				or ple.against_voucher_no in self.sales_person_records.get("Sales Invoice", [])
+			):
+				return
+
+		key = (ple.against_voucher_type, ple.against_voucher_no, ple.party)
+		row = self.voucher_balance.get(key)
+		return row
+
+	def update_voucher_balance(self, ple):
 		# get the row where this balance needs to be updated
 		# if its a payment, it will return the linked invoice or will be considered as advance
-		row = self.get_voucher_balance(gle)
+		row = self.get_voucher_balance(ple)
 		if not row:
 			return
-		# gle_balance will be the total "debit - credit" for receivable type reports and
-		# and vice-versa for payable type reports
-		gle_balance = self.get_gle_balance(gle)
-		gle_balance_in_account_currency = self.get_gle_balance_in_account_currency(gle)
 
-		if gle_balance > 0:
-			if gle.voucher_type in ("Journal Entry", "Payment Entry") and gle.against_voucher:
-				# debit against sales / purchase invoice
-				row.paid -= gle_balance
-				row.paid_in_account_currency -= gle_balance_in_account_currency
+		amount = ple.amount
+		amount_in_account_currency = ple.amount_in_account_currency
+
+		# update voucher
+		if ple.amount > 0:
+			if (
+				ple.voucher_type in ["Journal Entry", "Payment Entry"]
+				and ple.voucher_no != ple.against_voucher_no
+			):
+				row.paid -= amount
+				row.paid_in_account_currency -= amount_in_account_currency
 			else:
-				# invoice
-				row.invoiced += gle_balance
-				row.invoiced_in_account_currency += gle_balance_in_account_currency
+				row.invoiced += amount
+				row.invoiced_in_account_currency += amount_in_account_currency
 		else:
-			# payment or credit note for receivables
-			if self.is_invoice(gle):
-				# stand alone debit / credit note
-				row.credit_note -= gle_balance
-				row.credit_note_in_account_currency -= gle_balance_in_account_currency
+			if self.is_invoice(ple):
+				row.credit_note -= amount
+				row.credit_note_in_account_currency -= amount_in_account_currency
 			else:
-				# advance / unlinked payment or other adjustment
-				row.paid -= gle_balance
-				row.paid_in_account_currency -= gle_balance_in_account_currency
+				row.paid -= amount
+				row.paid_in_account_currency -= amount_in_account_currency
 
-		if gle.cost_center:
-			row.cost_center = str(gle.cost_center)
+		if ple.cost_center:
+			row.cost_center = str(ple.cost_center)
 
 	def update_sub_total_row(self, row, party):
 		total_row = self.total_row_map.get(party)
@@ -210,39 +223,6 @@
 			self.data.append({})
 			self.update_sub_total_row(sub_total_row, "Total")
 
-	def get_voucher_balance(self, gle):
-		if self.filters.get("sales_person"):
-			against_voucher = gle.against_voucher or gle.voucher_no
-			if not (
-				gle.party in self.sales_person_records.get("Customer", [])
-				or against_voucher in self.sales_person_records.get("Sales Invoice", [])
-			):
-				return
-
-		voucher_balance = None
-		if gle.against_voucher:
-			# find invoice
-			against_voucher = gle.against_voucher
-
-			# If payment is made against credit note
-			# and credit note is made against a Sales Invoice
-			# then consider the payment against original sales invoice.
-			if gle.against_voucher_type in ("Sales Invoice", "Purchase Invoice"):
-				if gle.against_voucher in self.return_entries:
-					return_against = self.return_entries.get(gle.against_voucher)
-					if return_against:
-						against_voucher = return_against
-
-			voucher_balance = self.voucher_balance.get(
-				(gle.against_voucher_type, against_voucher, gle.party)
-			)
-
-		if not voucher_balance:
-			# no invoice, this is an invoice / stand-alone payment / credit note
-			voucher_balance = self.voucher_balance.get((gle.voucher_type, gle.voucher_no, gle.party))
-
-		return voucher_balance
-
 	def build_data(self):
 		# set outstanding for all the accumulated balances
 		# as we can use this to filter out invoices without outstanding
@@ -260,6 +240,7 @@
 			if (abs(row.outstanding) > 1.0 / 10**self.currency_precision) and (
 				abs(row.outstanding_in_account_currency) > 1.0 / 10**self.currency_precision
 			):
+
 				# non-zero oustanding, we must consider this row
 
 				if self.is_invoice(row) and self.filters.based_on_payment_terms:
@@ -669,48 +650,53 @@
 			index = 4
 		row["range" + str(index + 1)] = row.outstanding
 
-	def get_gl_entries(self):
+	def get_ple_entries(self):
 		# get all the GL entries filtered by the given filters
 
-		conditions, values = self.prepare_conditions()
-		order_by = self.get_order_by_condition()
+		self.prepare_conditions()
 
 		if self.filters.show_future_payments:
-			values.insert(2, self.filters.report_date)
-
-			date_condition = """AND (posting_date <= %s
-				OR (against_voucher IS NULL AND DATE(creation) <= %s))"""
+			self.qb_selection_filter.append(
+				(
+					self.ple.posting_date.lte(self.filters.report_date)
+					| (
+						(self.ple.voucher_no == self.ple.against_voucher_no)
+						& (Date(self.ple.creation).lte(self.filters.report_date))
+					)
+				)
+			)
 		else:
-			date_condition = "AND posting_date <=%s"
+			self.qb_selection_filter.append(self.ple.posting_date.lte(self.filters.report_date))
 
-		if self.filters.get(scrub(self.party_type)):
-			select_fields = "debit_in_account_currency as debit, credit_in_account_currency as credit"
-		else:
-			select_fields = "debit, credit"
-
-		doc_currency_fields = "debit_in_account_currency, credit_in_account_currency"
-
-		remarks = ", remarks" if self.filters.get("show_remarks") else ""
-
-		self.gl_entries = frappe.db.sql(
-			"""
-			select
-				name, posting_date, account, party_type, party, voucher_type, voucher_no, cost_center,
-				against_voucher_type, against_voucher, account_currency, {0}, {1} {remarks}
-			from
-				`tabGL Entry`
-			where
-				docstatus < 2
-				and is_cancelled = 0
-				and party_type=%s
-				and (party is not null and party != '')
-				{2} {3} {4}""".format(
-				select_fields, doc_currency_fields, date_condition, conditions, order_by, remarks=remarks
-			),
-			values,
-			as_dict=True,
+		ple = qb.DocType("Payment Ledger Entry")
+		query = (
+			qb.from_(ple)
+			.select(
+				ple.account,
+				ple.voucher_type,
+				ple.voucher_no,
+				ple.against_voucher_type,
+				ple.against_voucher_no,
+				ple.party_type,
+				ple.cost_center,
+				ple.party,
+				ple.posting_date,
+				ple.due_date,
+				ple.account_currency.as_("currency"),
+				ple.amount,
+				ple.amount_in_account_currency,
+			)
+			.where(ple.delinked == 0)
+			.where(Criterion.all(self.qb_selection_filter))
 		)
 
+		if self.filters.get("group_by_party"):
+			query = query.orderby(self.ple.party, self.ple.posting_date)
+		else:
+			query = query.orderby(self.ple.posting_date, self.ple.party)
+
+		self.ple_entries = query.run(as_dict=True)
+
 	def get_sales_invoices_or_customers_based_on_sales_person(self):
 		if self.filters.get("sales_person"):
 			lft, rgt = frappe.db.get_value("Sales Person", self.filters.get("sales_person"), ["lft", "rgt"])
@@ -731,23 +717,21 @@
 				self.sales_person_records.setdefault(d.parenttype, set()).add(d.parent)
 
 	def prepare_conditions(self):
-		conditions = [""]
-		values = [self.party_type, self.filters.report_date]
+		self.qb_selection_filter = []
 		party_type_field = scrub(self.party_type)
 
-		self.add_common_filters(conditions, values, party_type_field)
+		self.add_common_filters(party_type_field=party_type_field)
 
 		if party_type_field == "customer":
-			self.add_customer_filters(conditions, values)
+			self.add_customer_filters()
 
 		elif party_type_field == "supplier":
-			self.add_supplier_filters(conditions, values)
+			self.add_supplier_filters()
 
 		if self.filters.cost_center:
-			self.get_cost_center_conditions(conditions)
+			self.get_cost_center_conditions()
 
-		self.add_accounting_dimensions_filters(conditions, values)
-		return " and ".join(conditions), values
+		self.add_accounting_dimensions_filters()
 
 	def get_cost_center_conditions(self, conditions):
 		lft, rgt = frappe.db.get_value("Cost Center", self.filters.cost_center, ["lft", "rgt"])
@@ -755,32 +739,20 @@
 			center.name
 			for center in frappe.get_list("Cost Center", filters={"lft": (">=", lft), "rgt": ("<=", rgt)})
 		]
+		self.qb_selection_filter.append(self.ple.cost_center.isin(cost_center_list))
 
-		cost_center_string = '", "'.join(cost_center_list)
-		conditions.append('cost_center in ("{0}")'.format(cost_center_string))
-
-	def get_order_by_condition(self):
-		if self.filters.get("group_by_party"):
-			return "order by party, posting_date"
-		else:
-			return "order by posting_date, party"
-
-	def add_common_filters(self, conditions, values, party_type_field):
+	def add_common_filters(self, party_type_field):
 		if self.filters.company:
-			conditions.append("company=%s")
-			values.append(self.filters.company)
+			self.qb_selection_filter.append(self.ple.company == self.filters.company)
 
 		if self.filters.finance_book:
-			conditions.append("ifnull(finance_book, '') in (%s, '')")
-			values.append(self.filters.finance_book)
+			self.qb_selection_filter.append(self.ple.finance_book == self.filters.finance_book)
 
 		if self.filters.get(party_type_field):
-			conditions.append("party=%s")
-			values.append(self.filters.get(party_type_field))
+			self.qb_selection_filter.append(self.ple.party == self.filters.get(party_type_field))
 
 		if self.filters.party_account:
-			conditions.append("account =%s")
-			values.append(self.filters.party_account)
+			self.qb_selection_filter.append(self.ple.account == self.filters.party_account)
 		else:
 			# get GL with "receivable" or "payable" account_type
 			account_type = "Receivable" if self.party_type == "Customer" else "Payable"
@@ -792,46 +764,68 @@
 			]
 
 			if accounts:
-				conditions.append("account in (%s)" % ",".join(["%s"] * len(accounts)))
-				values += accounts
+				self.qb_selection_filter.append(self.ple.account.isin(accounts))
 
-	def add_customer_filters(self, conditions, values):
+	def add_customer_filters(
+		self,
+	):
+		self.customter = qb.DocType("Customer")
+
 		if self.filters.get("customer_group"):
-			conditions.append(self.get_hierarchical_filters("Customer Group", "customer_group"))
+			self.get_hierarchical_filters("Customer Group", "customer_group")
 
 		if self.filters.get("territory"):
-			conditions.append(self.get_hierarchical_filters("Territory", "territory"))
+			self.get_hierarchical_filters("Territory", "territory")
 
 		if self.filters.get("payment_terms_template"):
-			conditions.append("party in (select name from tabCustomer where payment_terms=%s)")
-			values.append(self.filters.get("payment_terms_template"))
+			self.qb_selection_filter.append(
+				self.ple.party_isin(
+					qb.from_(self.customer).where(
+						self.customer.payment_terms == self.filters.get("payment_terms_template")
+					)
+				)
+			)
 
 		if self.filters.get("sales_partner"):
-			conditions.append("party in (select name from tabCustomer where default_sales_partner=%s)")
-			values.append(self.filters.get("sales_partner"))
-
-	def add_supplier_filters(self, conditions, values):
-		if self.filters.get("supplier_group"):
-			conditions.append(
-				"""party in (select name from tabSupplier
-				where supplier_group=%s)"""
+			self.qb_selection_filter.append(
+				self.ple.party_isin(
+					qb.from_(self.customer).where(
+						self.customer.default_sales_partner == self.filters.get("payment_terms_template")
+					)
+				)
 			)
-			values.append(self.filters.get("supplier_group"))
+
+	def add_supplier_filters(self):
+		supplier = qb.DocType("Supplier")
+		if self.filters.get("supplier_group"):
+			self.qb_selection_filter.append(
+				self.ple.party.isin(
+					qb.from_(supplier)
+					.select(supplier.name)
+					.where(supplier.supplier_group == self.filters.get("supplier_group"))
+				)
+			)
 
 		if self.filters.get("payment_terms_template"):
-			conditions.append("party in (select name from tabSupplier where payment_terms=%s)")
-			values.append(self.filters.get("payment_terms_template"))
+			self.qb_selection_filter.append(
+				self.ple.party.isin(
+					qb.from_(supplier)
+					.select(supplier.name)
+					.where(supplier.payment_terms == self.filters.get("supplier_group"))
+				)
+			)
 
 	def get_hierarchical_filters(self, doctype, key):
 		lft, rgt = frappe.db.get_value(doctype, self.filters.get(key), ["lft", "rgt"])
 
-		return """party in (select name from tabCustomer
-			where exists(select name from `tab{doctype}` where lft >= {lft} and rgt <= {rgt}
-				and name=tabCustomer.{key}))""".format(
-			doctype=doctype, lft=lft, rgt=rgt, key=key
-		)
+		doc = qb.DocType(doctype)
+		ple = self.ple
+		customer = self.customer
+		groups = qb.from_(doc).select(doc.name).where((doc.lft >= lft) & (doc.rgt <= rgt))
+		customers = qb.from_(customer).select(customer.name).where(customer[key].isin(groups))
+		self.qb_selection_filter.append(ple.isin(ple.party.isin(customers)))
 
-	def add_accounting_dimensions_filters(self, conditions, values):
+	def add_accounting_dimensions_filters(self):
 		accounting_dimensions = get_accounting_dimensions(as_list=False)
 
 		if accounting_dimensions:
@@ -841,30 +835,16 @@
 						self.filters[dimension.fieldname] = get_dimension_with_children(
 							dimension.document_type, self.filters.get(dimension.fieldname)
 						)
-					conditions.append("{0} in %s".format(dimension.fieldname))
-					values.append(tuple(self.filters.get(dimension.fieldname)))
+						self.qb_selection_filter.append(
+							self.ple[dimension.fieldname].isin(self.filters[dimension.fieldname])
+						)
+					else:
+						self.qb_selection_filter.append(
+							self.ple[dimension.fieldname] == self.filters[dimension.fieldname]
+						)
 
-	def get_gle_balance(self, gle):
-		# get the balance of the GL (debit - credit) or reverse balance based on report type
-		return gle.get(self.dr_or_cr) - self.get_reverse_balance(gle)
-
-	def get_gle_balance_in_account_currency(self, gle):
-		# get the balance of the GL (debit - credit) or reverse balance based on report type
-		return gle.get(
-			self.dr_or_cr + "_in_account_currency"
-		) - self.get_reverse_balance_in_account_currency(gle)
-
-	def get_reverse_balance_in_account_currency(self, gle):
-		return gle.get(
-			"debit_in_account_currency" if self.dr_or_cr == "credit" else "credit_in_account_currency"
-		)
-
-	def get_reverse_balance(self, gle):
-		# get "credit" balance if report type is "debit" and vice versa
-		return gle.get("debit" if self.dr_or_cr == "credit" else "credit")
-
-	def is_invoice(self, gle):
-		if gle.voucher_type in ("Sales Invoice", "Purchase Invoice"):
+	def is_invoice(self, ple):
+		if ple.voucher_type in ("Sales Invoice", "Purchase Invoice"):
 			return True
 
 	def get_party_details(self, party):
@@ -926,9 +906,6 @@
 			width=180,
 		)
 
-		if self.filters.show_remarks:
-			self.add_column(label=_("Remarks"), fieldname="remarks", fieldtype="Text", width=200),
-
 		self.add_column(label="Due Date", fieldtype="Date")
 
 		if self.party_type == "Supplier":
diff --git a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
index f38890e..edddbbc 100644
--- a/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
+++ b/erpnext/accounts/report/accounts_receivable/test_accounts_receivable.py
@@ -12,6 +12,7 @@
 	def test_accounts_receivable(self):
 		frappe.db.sql("delete from `tabSales Invoice` where company='_Test Company 2'")
 		frappe.db.sql("delete from `tabGL Entry` where company='_Test Company 2'")
+		frappe.db.sql("delete from `tabPayment Ledger Entry` where company='_Test Company 2'")
 
 		filters = {
 			"company": "_Test Company 2",
diff --git a/erpnext/accounts/utils.py b/erpnext/accounts/utils.py
index 8711395..65e0541 100644
--- a/erpnext/accounts/utils.py
+++ b/erpnext/accounts/utils.py
@@ -9,7 +9,10 @@
 import frappe.defaults
 from frappe import _, qb, throw
 from frappe.model.meta import get_field_precision
+from frappe.query_builder.utils import DocType
 from frappe.utils import cint, cstr, flt, formatdate, get_number_format_info, getdate, now, nowdate
+from pypika import Order
+from pypika.terms import ExistsCriterion
 
 import erpnext
 
@@ -42,37 +45,32 @@
 
 	if not fiscal_years:
 		# if year start date is 2012-04-01, year end date should be 2013-03-31 (hence subdate)
-		cond = ""
-		if fiscal_year:
-			cond += " and fy.name = {0}".format(frappe.db.escape(fiscal_year))
-		if company:
-			cond += """
-				and (not exists (select name
-					from `tabFiscal Year Company` fyc
-					where fyc.parent = fy.name)
-				or exists(select company
-					from `tabFiscal Year Company` fyc
-					where fyc.parent = fy.name
-					and fyc.company=%(company)s)
-				)
-			"""
+		FY = DocType("Fiscal Year")
 
-		fiscal_years = frappe.db.sql(
-			"""
-			select
-				fy.name, fy.year_start_date, fy.year_end_date
-			from
-				`tabFiscal Year` fy
-			where
-				disabled = 0 {0}
-			order by
-				fy.year_start_date desc""".format(
-				cond
-			),
-			{"company": company},
-			as_dict=True,
+		query = (
+			frappe.qb.from_(FY)
+			.select(FY.name, FY.year_start_date, FY.year_end_date)
+			.where(FY.disabled == 0)
 		)
 
+		if fiscal_year:
+			query = query.where(FY.name == fiscal_year)
+
+		if company:
+			FYC = DocType("Fiscal Year Company")
+			query = query.where(
+				ExistsCriterion(frappe.qb.from_(FYC).select(FYC.name).where(FYC.parent == FY.name)).negate()
+				| ExistsCriterion(
+					frappe.qb.from_(FYC)
+					.select(FYC.company)
+					.where(FYC.parent == FY.name)
+					.where(FYC.company == company)
+				)
+			)
+
+		query = query.orderby(FY.year_start_date, Order.desc)
+		fiscal_years = query.run(as_dict=True)
+
 		frappe.cache().hset("fiscal_years", company, fiscal_years)
 
 	if not transaction_date and not fiscal_year:
diff --git a/erpnext/regional/report/irs_1099/irs_1099.py b/erpnext/regional/report/irs_1099/irs_1099.py
index 92aeb5e..0f578be 100644
--- a/erpnext/regional/report/irs_1099/irs_1099.py
+++ b/erpnext/regional/report/irs_1099/irs_1099.py
@@ -10,7 +10,7 @@
 from frappe.utils.jinja import render_template
 from frappe.utils.pdf import get_pdf
 from frappe.utils.print_format import read_multi_pdf
-from PyPDF2 import PdfFileWriter
+from PyPDF2 import PdfWriter
 
 from erpnext.accounts.utils import get_fiscal_year
 
@@ -106,7 +106,7 @@
 
 	columns, data = execute(filters)
 	template = frappe.get_doc("Print Format", "IRS 1099 Form").html
-	output = PdfFileWriter()
+	output = PdfWriter()
 
 	for row in data:
 		row["fiscal_year"] = fiscal_year
diff --git a/erpnext/tests/test_exotel.py b/erpnext/tests/test_exotel.py
index 76bbb3e..31baf75 100644
--- a/erpnext/tests/test_exotel.py
+++ b/erpnext/tests/test_exotel.py
@@ -59,7 +59,6 @@
 			f"/api/method/erpnext.erpnext_integrations.exotel_integration.{api_method}",
 			data=frappe.as_json(data),
 			content_type="application/json",
-			as_tuple=True,
 		)
 		# restart db connection to get latest data
 		frappe.connect()
diff --git a/erpnext/translations/fr.csv b/erpnext/translations/fr.csv
index 40fb5ad..ffc46d2 100644
--- a/erpnext/translations/fr.csv
+++ b/erpnext/translations/fr.csv
@@ -9908,5 +9908,5 @@
 Default Payment Discount Account,Compte par défaut des paiements de remise
 Unrealized Profit / Loss Account,Compte de perte
 Enable Provisional Accounting For Non Stock Items,Activer la provision pour les articles non stockés
-Publish in Website=Publier sur le Site Web
+Publish in Website,Publier sur le Site Web
 List View,Vue en liste
diff --git a/erpnext/utilities/bot.py b/erpnext/utilities/bot.py
deleted file mode 100644
index 5c2e576..0000000
--- a/erpnext/utilities/bot.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and Contributors
-# See license.txt
-
-
-import frappe
-from frappe import _
-from frappe.utils.bot import BotParser
-
-
-class FindItemBot(BotParser):
-	def get_reply(self):
-		if self.startswith("where is", "find item", "locate"):
-			if not frappe.has_permission("Warehouse"):
-				raise frappe.PermissionError
-
-			item = "%{0}%".format(self.strip_words(self.query, "where is", "find item", "locate"))
-			items = frappe.db.sql(
-				"""select name from `tabItem` where item_code like %(txt)s
-				or item_name like %(txt)s or description like %(txt)s""",
-				dict(txt=item),
-			)
-
-			if items:
-				out = []
-				warehouses = frappe.get_all("Warehouse")
-				for item in items:
-					found = False
-					for warehouse in warehouses:
-						qty = frappe.db.get_value(
-							"Bin", {"item_code": item[0], "warehouse": warehouse.name}, "actual_qty"
-						)
-						if qty:
-							out.append(
-								_("{0} units of [{1}](/app/Form/Item/{1}) found in [{2}](/app/Form/Warehouse/{2})").format(
-									qty, item[0], warehouse.name
-								)
-							)
-							found = True
-
-					if not found:
-						out.append(_("[{0}](/app/Form/Item/{0}) is out of stock").format(item[0]))
-
-				return "\n\n".join(out)
-
-			else:
-				return _("Did not find any item called {0}").format(item)