Remediate the 2026-09-03 feature-test findings
This commit is contained in:
@@ -25,7 +25,7 @@ from shopdb.api import (
|
||||
employee_connection,
|
||||
require_role,
|
||||
)
|
||||
from shopdb.api import Setting
|
||||
from shopdb.api import Setting, intarg
|
||||
from shopdb.api import send_upload
|
||||
|
||||
from ..models import DirectoryEmployee
|
||||
@@ -287,7 +287,7 @@ def search_employees():
|
||||
- limit: Max results (default 10)
|
||||
"""
|
||||
query = request.args.get('q', '').strip()
|
||||
limit = min(int(request.args.get('limit', 10)), 50)
|
||||
limit = min(intarg('limit', 10), 50)
|
||||
|
||||
if len(query) < 2:
|
||||
return error_response(
|
||||
@@ -295,12 +295,31 @@ def search_employees():
|
||||
'Search query must be at least 2 characters'
|
||||
)
|
||||
|
||||
# A multi-word query is a FULL NAME. Each word was matched against first
|
||||
# name or last name on its own, so "Dana Reyes" found nobody: no single
|
||||
# column contains that string. Somebody typing the whole name - which is
|
||||
# what a person does before pressing Enter, rather than picking from the
|
||||
# list - got an empty result and their entry became free text linked to no
|
||||
# employee record.
|
||||
words = query.split()
|
||||
|
||||
if _selfhosted():
|
||||
term = f'%{query}%'
|
||||
rows = (DirectoryEmployee.query
|
||||
.filter(db.or_(DirectoryEmployee.firstname.ilike(term),
|
||||
DirectoryEmployee.lastname.ilike(term),
|
||||
db.cast(DirectoryEmployee.sso, db.String).ilike(term)))
|
||||
rows = DirectoryEmployee.query
|
||||
if len(words) > 1:
|
||||
# Every word has to appear in the name, in either column, so
|
||||
# "Reyes Dana" works as well as "Dana Reyes".
|
||||
for word in words:
|
||||
term = f'%{word}%'
|
||||
rows = rows.filter(db.or_(
|
||||
DirectoryEmployee.firstname.ilike(term),
|
||||
DirectoryEmployee.lastname.ilike(term)))
|
||||
else:
|
||||
term = f'%{query}%'
|
||||
rows = rows.filter(db.or_(
|
||||
DirectoryEmployee.firstname.ilike(term),
|
||||
DirectoryEmployee.lastname.ilike(term),
|
||||
db.cast(DirectoryEmployee.sso, db.String).ilike(term)))
|
||||
rows = (rows
|
||||
.order_by(DirectoryEmployee.lastname, DirectoryEmployee.firstname)
|
||||
.limit(limit).all())
|
||||
return success_response([_with_photo_url(e.to_dict()) for e in rows])
|
||||
@@ -308,15 +327,32 @@ def search_employees():
|
||||
try:
|
||||
conn = employee_connection()
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(f'''
|
||||
SELECT {_FIELDS}
|
||||
FROM employees
|
||||
WHERE First_Name LIKE %s
|
||||
OR Last_Name LIKE %s
|
||||
OR CAST(SSO AS CHAR) LIKE %s
|
||||
ORDER BY Last_Name, First_Name
|
||||
LIMIT %s
|
||||
''', (f'%{query}%', f'%{query}%', f'%{query}%', limit))
|
||||
if len(words) > 1:
|
||||
# Same rule as the self-hosted branch: every word must appear
|
||||
# somewhere in the name, so a full name finds the person.
|
||||
clauses = ' AND '.join(
|
||||
['(First_Name LIKE %s OR Last_Name LIKE %s)'] * len(words))
|
||||
params = []
|
||||
for word in words:
|
||||
params.extend([f'%{word}%', f'%{word}%'])
|
||||
params.append(limit)
|
||||
cur.execute(f'''
|
||||
SELECT {_FIELDS}
|
||||
FROM employees
|
||||
WHERE {clauses}
|
||||
ORDER BY Last_Name, First_Name
|
||||
LIMIT %s
|
||||
''', tuple(params))
|
||||
else:
|
||||
cur.execute(f'''
|
||||
SELECT {_FIELDS}
|
||||
FROM employees
|
||||
WHERE First_Name LIKE %s
|
||||
OR Last_Name LIKE %s
|
||||
OR CAST(SSO AS CHAR) LIKE %s
|
||||
ORDER BY Last_Name, First_Name
|
||||
LIMIT %s
|
||||
''', (f'%{query}%', f'%{query}%', f'%{query}%', limit))
|
||||
employees = cur.fetchall()
|
||||
conn.close()
|
||||
return success_response([_with_photo_url(e) for e in employees])
|
||||
|
||||
Reference in New Issue
Block a user