feat(helpdesk): align module with core UI standards and fix KPI accuracy

- Add server-side ticket summary endpoint for exact KPI counts instead
  of client-side calculation from first 200 tickets
- Extract summarizeTickets() as shared static method to avoid duplication
- Replace custom .helpdesk-spinner CSS with core [aria-busy] pattern
- Migrate settings connection-test feedback to showAsyncFlash()
- Replace helpdesk-clickable-rows.js with delegated event handling
- Remove unused helpdesk-search.js (dead code, never loaded)
- Harmonize German wording: Debitoren → Kunden (8 i18n keys)
- Add 6 PHPUnit tests for loadTicketSummary()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-02 21:05:54 +02:00
parent a0d7670dd7
commit a37486c0e3
13 changed files with 275 additions and 129 deletions

View File

@@ -104,6 +104,57 @@ class DebitorDetailService
return ['ok' => true, 'tickets' => $tickets];
}
/**
* Load a summary of ticket KPIs for a customer (exact counts, not sampled).
*
* @return array{ok: bool, total?: int, open?: int, last_activity?: string, error?: string}
*/
public function loadTicketSummary(string $customerNo, string $customerName): array
{
$result = $this->loadTickets($customerNo, $customerName);
if (!$result['ok']) {
return $result;
}
return self::summarizeTickets($result['tickets'] ?? []);
}
/**
* Aggregate ticket KPIs from a raw ticket array.
*
* Used by both the service method and the data endpoint (which has its own cache layer).
*
* @param array<int, array<string, mixed>> $tickets
* @return array{ok: true, total: int, open: int, last_activity: string}
*/
public static function summarizeTickets(array $tickets): array
{
$closedStates = ['Erledigt', 'Resolved', 'Closed', 'Geschlossen'];
$openCount = 0;
$lastActivity = '';
foreach ($tickets as $ticket) {
$state = (string) ($ticket['Ticket_State'] ?? '');
if (!in_array($state, $closedStates, true)) {
$openCount++;
}
$activityDate = (string) ($ticket['Last_Activity_Date'] ?? '');
if ($activityDate !== '' && $activityDate !== '0001-01-01T00:00:00Z' && $activityDate > $lastActivity) {
$lastActivity = $activityDate;
}
}
return [
'ok' => true,
'total' => count($tickets),
'open' => $openCount,
'last_activity' => $lastActivity,
];
}
/**
* Load activity log for a ticket (for async data endpoint).
*