Employee resolve diagnostic

This commit is contained in:
2026-08-11 16:10:21 -04:00
parent 721828b5cb
commit 2d19bcdf60
2 changed files with 154 additions and 2 deletions

View File

@@ -147,7 +147,13 @@ def resolve_employee_display_name(sso):
if name:
return name
except Exception:
pass
# Logged, not swallowed. This is the branch that fails on a live
# site - an unreachable HR host, a rotated credential, a renamed
# column - and a bare pass made all of those look identical to
# "no such employee": a lowercase SSO where a name should be, and
# no photo, with nothing written down anywhere.
current_app.logger.exception(
'Employee directory lookup failed for SSO %s', sso)
# 2. Fallback: a shopdb login account for this SSO (e.g. a user from another
# location not carried in the directory). Their name lives on the User
@@ -160,7 +166,8 @@ def resolve_employee_display_name(sso):
if name:
return name
except Exception:
pass
current_app.logger.exception(
'User-account name fallback failed for SSO %s', sso)
return None
@@ -189,6 +196,83 @@ def _with_photo_url(employee):
return employee
@employees_bp.route('/resolve/<sso>', methods=['GET'])
@jwt_required()
def resolve_employee(sso):
"""Resolve one SSO to a name and photo, and say HOW.
Built because the failure it diagnoses is invisible. The shopfloor board
resolves names and photos through two functions that returned None on any
problem, so an unreachable HR host, a rotated credential and a genuinely
unknown SSO all produced the same thing: a lowercase SSO where a name
should be, and no photo. Nothing distinguished them and nothing was logged.
This returns the same answer the board gets, plus which source produced it,
which mode the directory is in, and the error when a source failed. It is
the difference between "the board is broken" and "the HR host refused the
connection at 09:12".
"""
result = {
'sso': sso,
'mode': 'selfhosted' if _selfhosted() else 'external',
'name': None,
'photourl': None,
'source': None,
'error': None,
}
if sso is None or not str(sso).isdigit():
result['error'] = 'SSO must be digits'
return success_response(result)
if _selfhosted():
employee = db.session.get(DirectoryEmployee, int(sso))
if employee:
name = '{} {}'.format(employee.firstname or '',
employee.lastname or '').strip()
if name:
result['name'] = name
result['source'] = 'directory'
else:
try:
conn = employee_connection()
with conn.cursor() as cur:
cur.execute(
'SELECT First_Name, Last_Name FROM employees WHERE SSO = %s',
(int(sso),))
row = cur.fetchone()
conn.close()
if row:
name = '{} {}'.format((row.get('First_Name') or '').strip(),
(row.get('Last_Name') or '').strip()).strip()
if name:
result['name'] = name
result['source'] = 'hrdirectory'
else:
result['error'] = 'No row in the HR directory for this SSO'
except Exception as exception:
# Reported, not hidden. This string is the whole point of the
# endpoint.
result['error'] = '{}: {}'.format(type(exception).__name__, exception)
current_app.logger.exception(
'Employee directory lookup failed for SSO %s', sso)
if not result['name']:
from shopdb.api import User
user = User.query.filter_by(username=str(sso)).first()
if user:
name = '{} {}'.format((user.firstname or '').strip(),
(user.lastname or '').strip()).strip()
if name:
result['name'] = name
result['source'] = 'useraccount'
result['photourl'] = resolve_employee_photo_url(sso)
if result['name'] is None and result['error'] is None:
result['error'] = 'No name found in the directory or in a user account'
return success_response(result)
@employees_bp.route('/search', methods=['GET'])
def search_employees():
"""