Merge pull request #57718 from frappe/pg-audit/search-ranking-case-insensitive

fix(controllers): restore case-insensitive employee/lead/bom search ranking
This commit is contained in:
Mihir Kandoi
2026-08-03 12:08:33 +05:30
committed by GitHub
2 changed files with 49 additions and 9 deletions

View File

@@ -73,14 +73,17 @@ def employee_query(
.where(Criterion.any(search_conditions))
.orderby(
Case()
.when(Locate(txt_no_percent, Employee.name) > 0, Locate(txt_no_percent, Employee.name))
.when(
Locate(Lower(txt_no_percent), Lower(Employee.name)) > 0,
Locate(Lower(txt_no_percent), Lower(Employee.name)),
)
.else_(99999)
)
.orderby(
Case()
.when(
Locate(txt_no_percent, Employee.employee_name) > 0,
Locate(txt_no_percent, Employee.employee_name),
Locate(Lower(txt_no_percent), Lower(Employee.employee_name)) > 0,
Locate(Lower(txt_no_percent), Lower(Employee.employee_name)),
)
.else_(99999)
)
@@ -136,17 +139,28 @@ def lead_query(
query.where(Lead.docstatus < 2)
.where(Lead.status.isnull() | (Lead.status != "Converted"))
.where(Criterion.any(search_conditions))
.orderby(
Case().when(Locate(txt_no_percent, Lead.name) > 0, Locate(txt_no_percent, Lead.name)).else_(99999)
)
.orderby(
Case()
.when(Locate(txt_no_percent, Lead.lead_name) > 0, Locate(txt_no_percent, Lead.lead_name))
.when(
Locate(Lower(txt_no_percent), Lower(Lead.name)) > 0,
Locate(Lower(txt_no_percent), Lower(Lead.name)),
)
.else_(99999)
)
.orderby(
Case()
.when(Locate(txt_no_percent, Lead.company_name) > 0, Locate(txt_no_percent, Lead.company_name))
.when(
Locate(Lower(txt_no_percent), Lower(Lead.lead_name)) > 0,
Locate(Lower(txt_no_percent), Lower(Lead.lead_name)),
)
.else_(99999)
)
.orderby(
Case()
.when(
Locate(Lower(txt_no_percent), Lower(Lead.company_name)) > 0,
Locate(Lower(txt_no_percent), Lower(Lead.company_name)),
)
.else_(99999)
)
.orderby(Lead.idx, order=Order.desc)
@@ -387,7 +401,12 @@ def bom(
.where(BOM.is_active == 1)
.where(BOM[searchfield].like(f"%{txt}%"))
.orderby(
Case().when(Locate(txt_no_percent, BOM.name) > 0, Locate(txt_no_percent, BOM.name)).else_(99999)
Case()
.when(
Locate(Lower(txt_no_percent), Lower(BOM.name)) > 0,
Locate(Lower(txt_no_percent), Lower(BOM.name)),
)
.else_(99999)
)
.orderby(BOM.idx, order=Order.desc)
.orderby(BOM.name)

View File

@@ -29,6 +29,27 @@ class TestQueries(ERPNextTestSuite):
self.assertGreaterEqual(len(query(txt="_Test Lead")), 4)
self.assertEqual(len(query(txt="_Test Lead 4")), 1)
def test_lead_query_ranking_is_case_insensitive(self):
"""A match at the start must rank first whatever its case.
The filter uses .like(), which frappe renders as ILIKE on PostgreSQL, so both leads match.
Ranking used a bare Locate(), which becomes case-sensitive strpos() there: the upper-cased
lead scores no match, falls back to 99999 and sorts last, while MariaDB's case-insensitive
LOCATE ranks it first. Same query, different order -- and a different page when page_len is
small enough to cut between them.
"""
early, late = "ZZABCD Ranking Lead", "Ranking Lead zzabcd"
for lead_name in (early, late):
if not frappe.db.exists("Lead", {"lead_name": lead_name}):
frappe.get_doc({"doctype": "Lead", "lead_name": lead_name}).insert()
query = add_default_params(queries.lead_query, "Lead")
names = [row[1] for row in query(txt="zzabcd")]
self.assertIn(early, names)
self.assertIn(late, names)
self.assertLess(names.index(early), names.index(late))
def test_item_query(self):
query = add_default_params(queries.item_query, "Item")