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

@@ -260,6 +260,88 @@ class DebitorDetailServiceTest extends TestCase
$this->assertSame('Timeout', $result['error']);
}
// --- loadTicketSummary() ---
public function testLoadTicketSummaryReturnsFalseForEmptyCustomerNo(): void
{
$service = $this->createService();
$result = $service->loadTicketSummary('', 'Test GmbH');
$this->assertFalse($result['ok']);
}
public function testLoadTicketSummaryReturnsFalseWhenNotConfigured(): void
{
$service = $this->createService(null, false);
$result = $service->loadTicketSummary('10001', 'Test GmbH');
$this->assertFalse($result['ok']);
}
public function testLoadTicketSummaryReturnsExactCounts(): void
{
$tickets = [
['No' => 'T001', 'Ticket_State' => 'Offen', 'Last_Activity_Date' => '2026-03-15T10:00:00Z'],
['No' => 'T002', 'Ticket_State' => 'In Bearbeitung', 'Last_Activity_Date' => '2026-03-20T14:30:00Z'],
['No' => 'T003', 'Ticket_State' => 'Erledigt', 'Last_Activity_Date' => '2026-03-10T08:00:00Z'],
['No' => 'T004', 'Ticket_State' => 'Closed', 'Last_Activity_Date' => '2026-02-01T12:00:00Z'],
['No' => 'T005', 'Ticket_State' => 'Open', 'Last_Activity_Date' => '2026-03-25T16:45:00Z'],
];
$gateway = $this->createMock(BcODataGateway::class);
$gateway->method('getTicketsForCustomer')->willReturn($tickets);
$service = $this->createService($gateway);
$result = $service->loadTicketSummary('10001', 'Test GmbH');
$this->assertTrue($result['ok']);
$this->assertSame(5, $result['total']);
$this->assertSame(3, $result['open']); // Offen, In Bearbeitung, Open
$this->assertSame('2026-03-25T16:45:00Z', $result['last_activity']);
}
public function testLoadTicketSummaryHandlesEmptyTicketList(): void
{
$gateway = $this->createMock(BcODataGateway::class);
$gateway->method('getTicketsForCustomer')->willReturn([]);
$service = $this->createService($gateway);
$result = $service->loadTicketSummary('10001', 'Test GmbH');
$this->assertTrue($result['ok']);
$this->assertSame(0, $result['total']);
$this->assertSame(0, $result['open']);
$this->assertSame('', $result['last_activity']);
}
public function testLoadTicketSummaryIgnoresNullDateEntries(): void
{
$tickets = [
['No' => 'T001', 'Ticket_State' => 'Offen', 'Last_Activity_Date' => '0001-01-01T00:00:00Z'],
['No' => 'T002', 'Ticket_State' => 'Offen', 'Last_Activity_Date' => ''],
];
$gateway = $this->createMock(BcODataGateway::class);
$gateway->method('getTicketsForCustomer')->willReturn($tickets);
$service = $this->createService($gateway);
$result = $service->loadTicketSummary('10001', 'Test GmbH');
$this->assertTrue($result['ok']);
$this->assertSame(2, $result['open']);
$this->assertSame('', $result['last_activity']);
}
public function testLoadTicketSummaryReturnsErrorOnException(): void
{
$gateway = $this->createMock(BcODataGateway::class);
$gateway->method('getTicketsForCustomer')->willThrowException(new \RuntimeException('Timeout'));
$service = $this->createService($gateway);
$result = $service->loadTicketSummary('10001', 'Test GmbH');
$this->assertFalse($result['ok']);
$this->assertSame('Timeout', $result['error']);
}
// --- loadTicketLog() ---
public function testLoadTicketLogReturnsFalseForEmptyTicketNo(): void