diff --git a/frontend/src/views/ShopfloorDashboard.vue b/frontend/src/views/ShopfloorDashboard.vue
index 2494348..f98521f 100644
--- a/frontend/src/views/ShopfloorDashboard.vue
+++ b/frontend/src/views/ShopfloorDashboard.vue
@@ -14,7 +14,7 @@
@@ -99,8 +99,8 @@
/>
{{ rec.employeename || rec.employeesso }}
@@ -193,6 +193,10 @@ import { withBase } from '@/utils/basePath'
const loading = ref(true)
const facilityName = ref('ShopDB')
const siteLogo = ref(withBase('/ge-aerospace-logo.svg'))
+// Employee photo placeholder: the shipped GE monogram (square avatar). Kept
+// separate from siteLogo so a blank/broken site_logo setting never leaves an
+// employee tile with no image.
+const employeePhotoFallback = ref(withBase('/ge-monogram.svg'))
// ServiceNow ticket-link config; loaded on mount. Empty/disabled = plain text.
const servicenowConfig = ref({ enabled: true, incidentUrl: '', changeUrl: '' })
const businessUnit = ref('')
@@ -436,7 +440,11 @@ function getTicketUrl(ticketnumber) {
}
function handlePhotoError(e) {
- e.target.src = siteLogo.value
+ // Photo URL 404'd (e.g. no photo file for this SSO): fall back to the GE
+ // monogram. Guard against a loop if the fallback itself fails to load.
+ if (e.target.dataset.fellback) { return }
+ e.target.dataset.fellback = '1'
+ e.target.src = employeePhotoFallback.value
e.target.classList.add('ge-logo-fallback')
}
diff --git a/plugins/employees/api/routes.py b/plugins/employees/api/routes.py
index a65fe10..ea3e2e0 100644
--- a/plugins/employees/api/routes.py
+++ b/plugins/employees/api/routes.py
@@ -124,23 +124,41 @@ def resolve_employee_display_name(sso):
None on any miss."""
if sso is None or not str(sso).isdigit():
return None
+ # 1. The employee directory (selfhosted table or external wjf_employees).
if _selfhosted():
emp = db.session.get(DirectoryEmployee, int(sso))
if emp:
- return f'{emp.firstname} {emp.lastname}'.strip() or None
- return None
+ name = f'{emp.firstname} {emp.lastname}'.strip()
+ if name:
+ return name
+ 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:
+ first = row.get('First_Name') or ''
+ last = row.get('Last_Name') or ''
+ name = f'{first.strip()} {last.strip()}'.strip()
+ if name:
+ return name
+ except Exception:
+ pass
+
+ # 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
+ # record; GE usernames are the SSO. Photo still falls back to the GE mark.
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
+ from shopdb.core.models import User
+ user = User.query.filter_by(username=str(sso)).first()
+ if user:
+ name = f'{(user.firstname or "").strip()} {(user.lastname or "").strip()}'.strip()
+ if name:
+ return name
except Exception:
pass
return None
diff --git a/plugins/geenforce/seed_display_scope.py b/plugins/geenforce/seed_display_scope.py
index 301deaf..81ad7d9 100644
--- a/plugins/geenforce/seed_display_scope.py
+++ b/plugins/geenforce/seed_display_scope.py
@@ -152,7 +152,9 @@ $shell = New-Object -ComObject WScript.Shell
# left alone.
Get-ChildItem -LiteralPath $startup -Filter '*.lnk' -ErrorAction SilentlyContinue | ForEach-Object {{
$drop = $false
- if ($_.Name -like 'ShopDB Kiosk*') {{ $drop = $true }}
+ if ($_.Name -like 'ShopDB Kiosk*' -or
+ $_.Name -like 'GE Aerospace Dashboard*' -or
+ $_.Name -like 'GE Aerospace Lobby*') {{ $drop = $true }}
else {{
try {{
$existing = $shell.CreateShortcut($_.FullName)
diff --git a/plugins/notifications/api/routes.py b/plugins/notifications/api/routes.py
index 96166c0..a3aa612 100644
--- a/plugins/notifications/api/routes.py
+++ b/plugins/notifications/api/routes.py
@@ -734,10 +734,13 @@ def get_shopfloor_notifications():
else:
result['employeesso'] = n.employeesso
# Stored name first (import/manual entry), else resolve live from
- # the directory so shopdb-only imports still show names.
+ # the directory. Also resolve when the stored "name" is just the bare
+ # SSO: WJ notifications were imported with SSOs and never converted to
+ # names, so a digits-only stored name must still be looked up.
name = n.employeename
- if not name and n.employeesso and ',' not in n.employeesso:
- name = _employee_name(n.employeesso)
+ if (n.employeesso and ',' not in n.employeesso
+ and (not name or name.strip().isdigit())):
+ name = _employee_name(n.employeesso) or name
result['employeename'] = name
result['employeepicture'] = _employee_picture(n.employeesso) if show_photo else None
@@ -754,11 +757,18 @@ def get_shopfloor_notifications():
show_photo = bool(ntype and ntype.showemployeephoto)
ssos = [s.strip() for s in n.employeesso.split(',')]
names = n.employeename.split(', ') if n.employeename else []
+
+ def _name_for(i, sso):
+ stored = names[i].strip() if i < len(names) and names[i] else None
+ # A stored bare SSO (digits) is not a real name - look it up.
+ if stored and not stored.isdigit():
+ return stored
+ return _employee_name(sso) or stored or sso
+
return [
notification_to_shopfloor(n, {
'sso': sso,
- 'name': (names[i] if i < len(names) and names[i] else None)
- or _employee_name(sso) or sso,
+ 'name': _name_for(i, sso),
'picture': _employee_picture(sso) if show_photo else None,
})
for i, sso in enumerate(ssos)