Shopfloor feed resolves employee names live when none is stored
Photos already resolved through the directory at read time, but names only came from the stored employeename column - empty after a shopdb-only import, so recertification/recognition cards showed bare SSOs. New resolve_employee_display_name in the employees plugin (mode-aware: self-hosted table or external HR) backs a fallback in both the single-card and split-per-employee paths; stored names still win when present.
This commit is contained in:
@@ -116,6 +116,36 @@ def _hr_picture(sso):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_employee_display_name(sso):
|
||||||
|
"""Display name ("First Last") for an SSO in either directory mode.
|
||||||
|
|
||||||
|
The shopfloor feed uses this as a live fallback when a notification has
|
||||||
|
no stored employeename (e.g. imported without the employee source).
|
||||||
|
None on any miss."""
|
||||||
|
if sso is None or not str(sso).isdigit():
|
||||||
|
return None
|
||||||
|
if _selfhosted():
|
||||||
|
emp = db.session.get(DirectoryEmployee, int(sso))
|
||||||
|
if emp:
|
||||||
|
return f'{emp.firstname} {emp.lastname}'.strip() or None
|
||||||
|
return None
|
||||||
|
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:
|
||||||
|
first = row.get('First_Name') or ''
|
||||||
|
last = row.get('Last_Name') or ''
|
||||||
|
return f'{first.strip()} {last.strip()}'.strip() or None
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def resolve_employee_photo_url(sso, external_picture=None):
|
def resolve_employee_photo_url(sso, external_picture=None):
|
||||||
"""Single resolver both consumers share: the display photo URL for an SSO.
|
"""Single resolver both consumers share: the display photo URL for an SSO.
|
||||||
|
|
||||||
|
|||||||
@@ -149,6 +149,16 @@ def _config_version():
|
|||||||
return hashlib.md5('||'.join(parts).encode()).hexdigest()[:12]
|
return hashlib.md5('||'.join(parts).encode()).hexdigest()[:12]
|
||||||
|
|
||||||
|
|
||||||
|
def _employee_name(sso):
|
||||||
|
"""Live directory name for an SSO; None on any miss. Used as the fallback
|
||||||
|
when a notification has no stored employeename (see the shopfloor feed)."""
|
||||||
|
try:
|
||||||
|
from plugins.employees.api.routes import resolve_employee_display_name
|
||||||
|
return resolve_employee_display_name(sso)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def _employee_picture(sso):
|
def _employee_picture(sso):
|
||||||
"""Resolved display photo URL for an SSO, via the shared employees-plugin
|
"""Resolved display photo URL for an SSO, via the shared employees-plugin
|
||||||
resolver so kiosk cards match EmployeeDetail in both directory modes
|
resolver so kiosk cards match EmployeeDetail in both directory modes
|
||||||
@@ -723,7 +733,12 @@ def get_shopfloor_notifications():
|
|||||||
result['employeepicture'] = employee_override.get('picture')
|
result['employeepicture'] = employee_override.get('picture')
|
||||||
else:
|
else:
|
||||||
result['employeesso'] = n.employeesso
|
result['employeesso'] = n.employeesso
|
||||||
result['employeename'] = n.employeename
|
# Stored name first (import/manual entry), else resolve live from
|
||||||
|
# the directory so shopdb-only imports still show names.
|
||||||
|
name = n.employeename
|
||||||
|
if not name and n.employeesso and ',' not in n.employeesso:
|
||||||
|
name = _employee_name(n.employeesso)
|
||||||
|
result['employeename'] = name
|
||||||
result['employeepicture'] = _employee_picture(n.employeesso) if show_photo else None
|
result['employeepicture'] = _employee_picture(n.employeesso) if show_photo else None
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -742,7 +757,8 @@ def get_shopfloor_notifications():
|
|||||||
return [
|
return [
|
||||||
notification_to_shopfloor(n, {
|
notification_to_shopfloor(n, {
|
||||||
'sso': sso,
|
'sso': sso,
|
||||||
'name': names[i] if i < len(names) else sso,
|
'name': (names[i] if i < len(names) and names[i] else None)
|
||||||
|
or _employee_name(sso) or sso,
|
||||||
'picture': _employee_picture(sso) if show_photo else None,
|
'picture': _employee_picture(sso) if show_photo else None,
|
||||||
})
|
})
|
||||||
for i, sso in enumerate(ssos)
|
for i, sso in enumerate(ssos)
|
||||||
|
|||||||
@@ -70,3 +70,32 @@ def test_single_employee_recognition_stays_single_card(client, db):
|
|||||||
current = resp.get_json()['data']['current']
|
current = resp.get_json()['data']['current']
|
||||||
assert len(current) == 1
|
assert len(current) == 1
|
||||||
assert current[0]['employeesso'] == '111'
|
assert current[0]['employeesso'] == '111'
|
||||||
|
|
||||||
|
|
||||||
|
def test_shopfloor_names_resolve_live_when_not_stored(client, app, db):
|
||||||
|
"""A notification imported without employeename shows the directory name,
|
||||||
|
not the bare SSO - single and split-per-employee paths both."""
|
||||||
|
from plugins.employees.models import DirectoryEmployee
|
||||||
|
from plugins.notifications.models import Notification, NotificationType
|
||||||
|
from shopdb.core.models import Setting
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
Setting.set('employee_directory_mode', 'selfhosted',
|
||||||
|
valuetype='string', category='employees')
|
||||||
|
db.session.add(DirectoryEmployee(
|
||||||
|
sso=502000777, firstname='Recert', lastname='Person'))
|
||||||
|
ntype = NotificationType(typename='Recertification',
|
||||||
|
typecolor='recertification',
|
||||||
|
splitperemployee=True)
|
||||||
|
db.session.add(ntype)
|
||||||
|
db.session.flush()
|
||||||
|
db.session.add(Notification(
|
||||||
|
notificationtypeid=ntype.notificationtypeid,
|
||||||
|
notification='Recert due', isshopfloor=True,
|
||||||
|
employeesso='502000777', employeename=None))
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
feed = client.get('/api/notifications/shopfloor').get_json()['data']
|
||||||
|
cards = feed['current'] + feed['upcoming']
|
||||||
|
card = next(c for c in cards if c['notification'] == 'Recert due')
|
||||||
|
assert card['employeename'] == 'Recert Person'
|
||||||
|
|||||||
Reference in New Issue
Block a user