Refactor employee name handling to utilize workfeed_employee_name_formatter for improved name resolution and fallback logic

This commit is contained in:
Jeppe Bundgaard
2026-05-18 10:59:23 +02:00
parent f53b99ad94
commit 3261ed8414
9 changed files with 714 additions and 131 deletions
@@ -8,6 +8,7 @@ use classes\response;
use classes\router;
use classes\weatherapi;
use classes\workfeed;
use classes\workfeed_employee_name_formatter;
use classes\workfeed_shift_time_resolver;
use DateInterval;
use DateTime;
@@ -1484,14 +1485,14 @@ class moduleWeatherAPIRoute
return null;
}
private function normalizeDepartmentName(string $name): string
private static function normalizeDepartmentName(string $name): string
{
$collapsed = preg_replace('/\s+/', ' ', trim($name));
return strtolower($collapsed ?? trim($name));
}
private function normalizeWorkfeedCollection(array|object $payload): array
private static function normalizeWorkfeedCollection(array|object $payload): array
{
if (is_array($payload)) {
return $payload;
@@ -1514,7 +1515,7 @@ class moduleWeatherAPIRoute
return [];
}
private function normalizeWorkfeedRecord(mixed $record): array
private static function normalizeWorkfeedRecord(mixed $record): array
{
if (is_array($record)) {
return $record;
@@ -1526,7 +1527,7 @@ class moduleWeatherAPIRoute
return [];
}
private function extractWorkfeedDepartmentId(mixed $shift): ?string
private static function extractWorkfeedDepartmentId(mixed $shift): ?string
{
$record = self::normalizeWorkfeedRecord($shift);
@@ -1545,7 +1546,7 @@ class moduleWeatherAPIRoute
return $normalized === '' ? null : $normalized;
}
private function parseDateTimeValue(mixed $value): ?DateTime
private static function parseDateTimeValue(mixed $value): ?DateTime
{
if (is_string($value)) {
$normalized = trim($value);
@@ -1599,7 +1600,7 @@ class moduleWeatherAPIRoute
return null;
}
private function getNestedRecordValue(array $record, string $path): mixed
private static function getNestedRecordValue(array $record, string $path): mixed
{
$segments = explode('.', $path);
$current = $record;
@@ -1627,7 +1628,7 @@ class moduleWeatherAPIRoute
return $current;
}
private function firstShiftDateTimeFromPaths(array $record, array $paths): ?DateTime
private static function firstShiftDateTimeFromPaths(array $record, array $paths): ?DateTime
{
foreach ($paths as $path) {
$value = self::getNestedRecordValue($record, $path);
@@ -1640,7 +1641,7 @@ class moduleWeatherAPIRoute
return null;
}
private function lastShiftDateTimeFromPaths(array $record, array $paths): ?DateTime
private static function lastShiftDateTimeFromPaths(array $record, array $paths): ?DateTime
{
$latest = null;
@@ -1659,7 +1660,7 @@ class moduleWeatherAPIRoute
return $latest;
}
private function hasShiftApproval(array $record): bool
private static function hasShiftApproval(array $record): bool
{
if (!array_key_exists('approval', $record)) {
return false;
@@ -1680,7 +1681,7 @@ class moduleWeatherAPIRoute
return true;
}
private function resolveEffectiveShiftEnd(array $record, DateTime $shift_start, DateTime $shift_end): DateTime
private static function resolveEffectiveShiftEnd(array $record, DateTime $shift_start, DateTime $shift_end): DateTime
{
if (self::hasShiftApproval($record)) {
return $shift_end;
@@ -1711,7 +1712,7 @@ class moduleWeatherAPIRoute
return $update_time;
}
private function calculateWorkfeedEmployeeHoursForHour(
private static function calculateWorkfeedEmployeeHoursForHour(
array $shifts,
string|array $workfeed_department_ids,
DateTime $slot_start,
@@ -1775,8 +1776,8 @@ class moduleWeatherAPIRoute
if (defined('redis')) {
try {
$employeeNameCache = new redis();
} catch (Exception) {
$employeeNameCache = null;
} catch (Exception $e) {
// Redis is unavailable, proceed without caching.
}
}
@@ -1822,7 +1823,8 @@ class moduleWeatherAPIRoute
$employees = $this->resolveMissingWorkfeedEmployeeNames(
$employees,
$employeeNameCache,
$resolved_employee_names_by_id
$resolved_employee_names_by_id,
$workfeed
);
if ($employees === []) {
continue;
@@ -1851,7 +1853,7 @@ class moduleWeatherAPIRoute
}
}
private function calculateWorkfeedEmployeeHoursForHourByEmployee(
private static function calculateWorkfeedEmployeeHoursForHourByEmployee(
array $shifts,
string|array $workfeed_department_ids,
DateTime $slot_start,
@@ -1902,7 +1904,11 @@ class moduleWeatherAPIRoute
$employee_identity = self::extractWorkfeedEmployeeIdentity($shift);
$employee_id = $employee_identity['id'];
$employee_name = self::normalizeShiftTextValue($employee_identity['name'] ?? null) ?? 'Unknown employee';
$employee_name = self::normalizeShiftTextValue($employee_identity['name'] ?? null);
if ($employee_id === null && $employee_name === null) {
continue;
}
$employee_key = $employee_id ?? ('name:' . strtolower($employee_name));
if (!isset($hours_by_employee_key[$employee_key])) {
$hours_by_employee_key[$employee_key] = [
@@ -1929,8 +1935,14 @@ class moduleWeatherAPIRoute
}));
}
private function resolveMissingWorkfeedEmployeeNames(array $employees, ?redis $employee_name_cache, array &$resolved_names_by_employee_id): array
private function resolveMissingWorkfeedEmployeeNames(
array $employees,
?redis $employee_name_cache,
array &$resolved_names_by_employee_id,
?workfeed $workfeed = null
): array
{
$ids_to_fetch = [];
foreach ($employees as &$employee) {
$employee_id = self::normalizeShiftTextValue($employee['employee_id'] ?? null);
if ($employee_id === null) {
@@ -1949,6 +1961,35 @@ class moduleWeatherAPIRoute
);
}
if ($resolved_names_by_employee_id[$employee_id] === null) {
$ids_to_fetch[$employee_id] = true;
}
}
unset($employee);
if ($ids_to_fetch !== [] && $workfeed !== null) {
$api_names_by_employee_id = $this->fetchWorkfeedEmployeeDisplayNames(
$workfeed,
array_keys($ids_to_fetch),
$employee_name_cache
);
foreach (array_keys($ids_to_fetch) as $employee_id) {
$resolved_names_by_employee_id[$employee_id] = $api_names_by_employee_id[$employee_id] ?? null;
}
}
foreach ($employees as &$employee) {
$employee_id = self::normalizeShiftTextValue($employee['employee_id'] ?? null);
if ($employee_id === null) {
continue;
}
$employee_name = self::normalizeShiftTextValue($employee['employee_name'] ?? null);
if (!self::isMissingEmployeeDisplayName($employee_name, $employee_id)) {
continue;
}
$resolved_name = $resolved_names_by_employee_id[$employee_id] ?? null;
if ($resolved_name !== null) {
$employee['employee_name'] = $resolved_name;
@@ -1960,7 +2001,172 @@ class moduleWeatherAPIRoute
return strcasecmp((string)($left['employee_name'] ?? ''), (string)($right['employee_name'] ?? ''));
});
return $employees;
return array_values(array_filter($employees, static function (array $employee): bool {
$employee_id = self::normalizeShiftTextValue($employee['employee_id'] ?? null);
$employee_name = self::normalizeShiftTextValue($employee['employee_name'] ?? null);
return !self::isMissingEmployeeDisplayName($employee_name, $employee_id);
}));
}
/**
* @param array<int,string> $employee_ids
* @return array<string,string>
*/
private function fetchWorkfeedEmployeeDisplayNames(workfeed $workfeed, array $employee_ids, ?redis $employee_name_cache): array
{
$employee_id_lookup = [];
foreach ($employee_ids as $employee_id) {
$normalized = self::normalizeShiftTextValue($employee_id);
if ($normalized !== null) {
$employee_id_lookup[$normalized] = true;
}
}
if ($employee_id_lookup === []) {
return [];
}
$names_by_employee_id = [];
try {
$employees = self::normalizeWorkfeedCollection($workfeed->listEmployees());
} catch (Exception) {
$employees = [];
}
foreach ($employees as $employee) {
$this->appendWorkfeedEmployeeDisplayNames($employee, $employee_id_lookup, $names_by_employee_id, $employee_name_cache);
}
foreach (array_keys($employee_id_lookup) as $employee_id) {
if (isset($names_by_employee_id[$employee_id])) {
continue;
}
try {
$employee = $workfeed->getEmployee($employee_id);
} catch (Exception) {
continue;
}
$this->appendWorkfeedEmployeeDisplayNames($employee, $employee_id_lookup, $names_by_employee_id, $employee_name_cache);
}
return $names_by_employee_id;
}
/**
* @param array<string,bool> $employee_id_lookup
* @param array<string,string> $names_by_employee_id
*/
private function appendWorkfeedEmployeeDisplayNames(
mixed $employee,
array $employee_id_lookup,
array &$names_by_employee_id,
?redis $employee_name_cache
): void {
foreach (self::extractWorkfeedEmployeeIds($employee) as $employee_id) {
if (!isset($employee_id_lookup[$employee_id])) {
continue;
}
$employee_name = self::extractWorkfeedEmployeeDisplayName($employee, $employee_id);
if ($employee_name === null) {
continue;
}
$names_by_employee_id[$employee_id] = $employee_name;
$this->cacheWorkfeedEmployeeDisplayName($employee_name_cache, $employee_id, $employee_name);
}
}
private static function extractWorkfeedEmployeeDisplayName(mixed $employee, ?string $employee_id = null): ?string
{
return workfeed_employee_name_formatter::fromRecord($employee, [
'firstname',
'firstName',
'first_name',
'employee.firstname',
'employee.firstName',
'employee.first_name',
'user.firstname',
'user.firstName',
'user.first_name',
], [
'lastname',
'lastName',
'last_name',
'employee.lastname',
'employee.lastName',
'employee.last_name',
'user.lastname',
'user.lastName',
'user.last_name',
], [
'employeeName',
'employee.name',
'employee.fullName',
'employee.full_name',
'employee.displayName',
'employee.display_name',
'name',
'fullName',
'full_name',
'displayName',
'display_name',
'user.name',
'user.fullName',
'user.full_name',
'user.displayName',
'user.display_name',
], $employee_id);
}
/**
* @return array<int,string>
*/
private static function extractWorkfeedEmployeeIds(mixed $employee): array
{
$record = self::normalizeWorkfeedRecord($employee);
if ($record === []) {
return [];
}
$employee_ids = [];
foreach ([
'id',
'employeeID',
'employeeId',
'employee_id',
'uuid',
'employee.id',
'employee.employeeID',
'employee.employeeId',
'employee.employee_id',
'employee.uuid',
'employeeUUID',
'employee_uuid',
'user.id',
'userId',
] as $path) {
$employee_id = self::normalizeShiftTextValue(self::getNestedRecordValue($record, $path));
if ($employee_id !== null) {
$employee_ids[$employee_id] = true;
}
}
return array_keys($employee_ids);
}
private function cacheWorkfeedEmployeeDisplayName(?redis $employee_name_cache, string $employee_id, string $employee_name): void
{
if ($employee_name_cache === null) {
return;
}
try {
$employee_name_cache->cache_workfeed_employee_name($employee_id, $employee_name);
} catch (Exception) {
}
}
private function fetchCachedWorkfeedEmployeeDisplayName(?redis $employee_name_cache, string $employee_id): ?string
@@ -1978,20 +2184,12 @@ class moduleWeatherAPIRoute
return self::isMissingEmployeeDisplayName($employee_name, $employee_id) ? null : $employee_name;
}
private function isMissingEmployeeDisplayName(?string $employee_name, ?string $employee_id = null): bool
private static function isMissingEmployeeDisplayName(?string $employee_name, ?string $employee_id = null): bool
{
if ($employee_name === null) {
return true;
}
if ($employee_id !== null && strcasecmp($employee_name, 'Employee ' . $employee_id) === 0) {
return true;
}
return strcasecmp($employee_name, 'Unknown employee') === 0;
return workfeed_employee_name_formatter::isMissingDisplayName($employee_name, $employee_id);
}
private function extractWorkfeedEmployeeIdentity(mixed $shift): array
private static function extractWorkfeedEmployeeIdentity(mixed $shift): array
{
$record = self::normalizeWorkfeedRecord($shift);
@@ -2017,8 +2215,27 @@ class moduleWeatherAPIRoute
}
}
$employee_name = null;
foreach ([
$employee_name = workfeed_employee_name_formatter::fromRecord($record, [
'employee.firstname',
'employee.firstName',
'employee.first_name',
'firstname',
'firstName',
'first_name',
'user.firstname',
'user.firstName',
'user.first_name',
], [
'employee.lastname',
'employee.lastName',
'employee.last_name',
'lastname',
'lastName',
'last_name',
'user.lastname',
'user.lastName',
'user.last_name',
], [
'employeeName',
'employee.name',
'employee.fullName',
@@ -2035,37 +2252,7 @@ class moduleWeatherAPIRoute
'user.full_name',
'user.displayName',
'user.display_name',
] as $path) {
$value = self::normalizeShiftTextValue(self::getNestedRecordValue($record, $path));
if ($value !== null) {
$employee_name = $value;
break;
}
}
if ($employee_name === null) {
$first_name = self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'employee.firstname'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'employee.firstName'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'employee.first_name'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'firstname'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'firstName'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'first_name'));
$last_name = self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'employee.lastname'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'employee.lastName'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'employee.last_name'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'lastname'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'lastName'))
?? self::normalizeShiftTextValue(self::getNestedRecordValue($record, 'last_name'));
$combined_name = trim((string)($first_name ?? '') . ' ' . (string)($last_name ?? ''));
if ($combined_name !== '') {
$employee_name = $combined_name;
}
}
if ($employee_name === null) {
$employee_name = 'Unknown employee';
}
], $employee_id);
return [
'id' => $employee_id,
@@ -2073,7 +2260,7 @@ class moduleWeatherAPIRoute
];
}
private function normalizeShiftTextValue(mixed $value): ?string
private static function normalizeShiftTextValue(mixed $value): ?string
{
if (!is_scalar($value)) {
return null;