fix(helpdesk): consolidation fixes from drift/performance/security review

- Fix docblock: getTicketsForTeam() uses PBI_FP_Tickets, not LV
- Process_Stage check now uses isset() guard — FP entity does not
  expose this field, prevents false positives from default 0
- Remove exception message from JSON error response (security)
- Remove dead JS functions: formatAge(), formatDate()
- Remove debug console.error and verbose error display from JS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-05 20:25:51 +02:00
parent b82cef31a6
commit fc98f2684a
4 changed files with 9 additions and 27 deletions

View File

@@ -590,7 +590,8 @@ class BcODataGateway
/**
* Get ticket rows across all customers for team workload aggregation.
*
* Uses PBI_LV_Tickets without Customer_No filter to return a global snapshot.
* Uses PBI_FP_Tickets (FactPage) without Customer_No filter to return a
* global snapshot with Description and Company_Contact_Name.
* Support_User_Name is used for per-agent grouping.
*
* @return array<int, array<string, mixed>>

View File

@@ -1603,8 +1603,10 @@ class DebitorDetailService
foreach ($tickets as $ticket) {
$state = trim((string) ($ticket['Ticket_State'] ?? ''));
// Team query uses PBI_FP_Tickets which has no Process_Stage;
// fall back to Process_Stage only when present (LV entity).
$isClosed = self::isClosedTicketState($state)
|| (int) ($ticket['Process_Stage'] ?? 0) === 40;
|| (isset($ticket['Process_Stage']) && (int) $ticket['Process_Stage'] === 40);
$rawUser = trim((string) ($ticket['Support_User_Name'] ?? ''));
$normalizedUser = $rawUser === '' ? '' : strtolower($rawUser);

View File

@@ -51,10 +51,10 @@ if ($tickets === null) {
try {
$tickets = $gateway->getTicketsForTeam();
} catch (\Throwable $e) {
} catch (\Throwable) {
Router::json([
'ok' => false,
'error' => 'Failed to load team workload data: ' . $e->getMessage(),
'error' => 'Failed to load team workload data',
]);
return;

View File

@@ -44,18 +44,6 @@ if (container) {
return name.substring(0, 2).toUpperCase();
}
function formatAge(hours) {
if (hours < 24) return hours + 'h';
return Math.floor(hours / 24) + 'd';
}
function formatDate(isoString) {
if (!isoString) return '—';
const d = new Date(isoString);
if (isNaN(d.getTime())) return '—';
return d.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: 'numeric' });
}
function formatDateTime(isoString) {
if (!isoString) return '—';
const d = new Date(isoString);
@@ -285,22 +273,13 @@ if (container) {
const res = await fetch(dataUrl + '?' + params.toString(), { credentials: 'same-origin' });
const json = await res.json();
if (!json.ok) {
console.error('[helpdesk-team] API error:', json.error || json);
if (elError) {
const p = elError.querySelector('p');
if (p && json.error) p.textContent = json.error;
}
showState('error');
return;
}
if (!json.ok) { showState('error'); return; }
if (!json.members || json.members.length === 0) { showState('empty'); return; }
renderCurrentTab(json.members);
renderPerformanceTab(json.members);
showState('success');
} catch (err) {
console.error('[helpdesk-team]', err);
} catch {
showState('error');
}
}