dashboard: resolve employee names from directory/user, GE monogram photo fallback, kiosk sweep + label
- notifications shopfloor feed: resolve the employee name live when the stored value is a bare SSO (WJ notifications imported as SSOs, never converted), for both single and split-per-employee cards - employee name resolver: after a directory miss, fall back to the shopdb User account (firstname/lastname, keyed by SSO username) so users from other locations still show a name - shopfloor dashboard: employee photo falls back to the GE monogram (own asset, independent of the site_logo setting) with a loop-guarded onerror; recognition + recert tiles both covered - shopfloor dashboard: 'All Business Units' filter label -> 'All Locations' - geenforce display dispatcher: startup sweep also matches the imaging installers' 'GE Aerospace Dashboard/Lobby' shortcuts by name
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="clock">{{ currentTime }}</div>
|
<div class="clock">{{ currentTime }}</div>
|
||||||
<select v-model="businessUnit" class="filter-select" @change="loadData">
|
<select v-model="businessUnit" class="filter-select" @change="loadData">
|
||||||
<option value="">All Business Units</option>
|
<option value="">All Locations</option>
|
||||||
<option v-for="bu in businessUnits" :key="bu.businessunitid" :value="bu.businessunitid">
|
<option v-for="bu in businessUnits" :key="bu.businessunitid" :value="bu.businessunitid">
|
||||||
{{ bu.businessunit }}
|
{{ bu.businessunit }}
|
||||||
</option>
|
</option>
|
||||||
@@ -55,8 +55,8 @@
|
|||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else
|
||||||
:src="siteLogo"
|
:src="employeePhotoFallback"
|
||||||
alt="Site logo"
|
alt="No photo"
|
||||||
class="recognition-photo ge-logo-fallback"
|
class="recognition-photo ge-logo-fallback"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -99,8 +99,8 @@
|
|||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
v-else
|
v-else
|
||||||
:src="siteLogo"
|
:src="employeePhotoFallback"
|
||||||
alt="Site logo"
|
alt="No photo"
|
||||||
class="recert-photo ge-logo-fallback"
|
class="recert-photo ge-logo-fallback"
|
||||||
/>
|
/>
|
||||||
<div class="recert-name">{{ rec.employeename || rec.employeesso }}</div>
|
<div class="recert-name">{{ rec.employeename || rec.employeesso }}</div>
|
||||||
@@ -193,6 +193,10 @@ import { withBase } from '@/utils/basePath'
|
|||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const facilityName = ref('ShopDB')
|
const facilityName = ref('ShopDB')
|
||||||
const siteLogo = ref(withBase('/ge-aerospace-logo.svg'))
|
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.
|
// ServiceNow ticket-link config; loaded on mount. Empty/disabled = plain text.
|
||||||
const servicenowConfig = ref({ enabled: true, incidentUrl: '', changeUrl: '' })
|
const servicenowConfig = ref({ enabled: true, incidentUrl: '', changeUrl: '' })
|
||||||
const businessUnit = ref('')
|
const businessUnit = ref('')
|
||||||
@@ -436,7 +440,11 @@ function getTicketUrl(ticketnumber) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePhotoError(e) {
|
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')
|
e.target.classList.add('ge-logo-fallback')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -124,23 +124,41 @@ def resolve_employee_display_name(sso):
|
|||||||
None on any miss."""
|
None on any miss."""
|
||||||
if sso is None or not str(sso).isdigit():
|
if sso is None or not str(sso).isdigit():
|
||||||
return None
|
return None
|
||||||
|
# 1. The employee directory (selfhosted table or external wjf_employees).
|
||||||
if _selfhosted():
|
if _selfhosted():
|
||||||
emp = db.session.get(DirectoryEmployee, int(sso))
|
emp = db.session.get(DirectoryEmployee, int(sso))
|
||||||
if emp:
|
if emp:
|
||||||
return f'{emp.firstname} {emp.lastname}'.strip() or None
|
name = f'{emp.firstname} {emp.lastname}'.strip()
|
||||||
return None
|
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:
|
try:
|
||||||
conn = employee_connection()
|
from shopdb.core.models import User
|
||||||
with conn.cursor() as cur:
|
user = User.query.filter_by(username=str(sso)).first()
|
||||||
cur.execute(
|
if user:
|
||||||
'SELECT First_Name, Last_Name FROM employees WHERE SSO = %s',
|
name = f'{(user.firstname or "").strip()} {(user.lastname or "").strip()}'.strip()
|
||||||
(int(sso),))
|
if name:
|
||||||
row = cur.fetchone()
|
return name
|
||||||
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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -152,7 +152,9 @@ $shell = New-Object -ComObject WScript.Shell
|
|||||||
# left alone.
|
# left alone.
|
||||||
Get-ChildItem -LiteralPath $startup -Filter '*.lnk' -ErrorAction SilentlyContinue | ForEach-Object {{
|
Get-ChildItem -LiteralPath $startup -Filter '*.lnk' -ErrorAction SilentlyContinue | ForEach-Object {{
|
||||||
$drop = $false
|
$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 {{
|
else {{
|
||||||
try {{
|
try {{
|
||||||
$existing = $shell.CreateShortcut($_.FullName)
|
$existing = $shell.CreateShortcut($_.FullName)
|
||||||
|
|||||||
@@ -734,10 +734,13 @@ def get_shopfloor_notifications():
|
|||||||
else:
|
else:
|
||||||
result['employeesso'] = n.employeesso
|
result['employeesso'] = n.employeesso
|
||||||
# Stored name first (import/manual entry), else resolve live from
|
# 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
|
name = n.employeename
|
||||||
if not name and n.employeesso and ',' not in n.employeesso:
|
if (n.employeesso and ',' not in n.employeesso
|
||||||
name = _employee_name(n.employeesso)
|
and (not name or name.strip().isdigit())):
|
||||||
|
name = _employee_name(n.employeesso) or name
|
||||||
result['employeename'] = name
|
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
|
||||||
|
|
||||||
@@ -754,11 +757,18 @@ def get_shopfloor_notifications():
|
|||||||
show_photo = bool(ntype and ntype.showemployeephoto)
|
show_photo = bool(ntype and ntype.showemployeephoto)
|
||||||
ssos = [s.strip() for s in n.employeesso.split(',')]
|
ssos = [s.strip() for s in n.employeesso.split(',')]
|
||||||
names = n.employeename.split(', ') if n.employeename else []
|
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 [
|
return [
|
||||||
notification_to_shopfloor(n, {
|
notification_to_shopfloor(n, {
|
||||||
'sso': sso,
|
'sso': sso,
|
||||||
'name': (names[i] if i < len(names) and names[i] else None)
|
'name': _name_for(i, sso),
|
||||||
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user