Add 3 new ticket list filters: Support User (dynamic select), Contact (dynamic select), and Period (30/90/180/365 days). Extend categories endpoint to also return support_users and contacts arrays from the same cached ticket data. Add server-side filtering for all three in the data endpoint. Resolve support user abbreviation codes to full names in the communication chat widget. For single-ticket view, infer code-to-name from Support_User_Name when only one support code appears. For debitor view, build map from already-fetched entries across tickets with no additional API calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
283 lines
12 KiB
PHP
283 lines
12 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Tests\Module\Helpdesk\Service;
|
|
|
|
use MintyPHP\Module\Helpdesk\Service\BcODataGateway;
|
|
use MintyPHP\Module\Helpdesk\Service\BcSoapGateway;
|
|
use MintyPHP\Module\Helpdesk\Service\TicketCommunicationService;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
class TicketCommunicationServiceTest extends TestCase
|
|
{
|
|
public function testLoadTicketCommunicationFeedMergesSoapAndOData(): void
|
|
{
|
|
$soapGateway = $this->createMock(BcSoapGateway::class);
|
|
$soapGateway->method('getTicketCommunicationText')->willReturn([
|
|
'ok' => true,
|
|
'soap_used' => true,
|
|
'return_value' => 0,
|
|
'communication_text' => "2026-04-03 08:10 - Alice: Hallo\n2026-04-03 08:11 - Alice: Update",
|
|
'message_entries' => '',
|
|
]);
|
|
|
|
$odataGateway = $this->createMock(BcODataGateway::class);
|
|
$odataGateway->expects($this->any())->method('getTicketLog')->with('T26-1216')->willReturn([
|
|
[
|
|
'Entry_No' => 1,
|
|
'Created_By' => 'ICTEST',
|
|
'Creation_Date' => '2026-04-03',
|
|
'Creation_Time' => '07:59:00.000',
|
|
'Type' => 'Status',
|
|
'State_Subtype' => 'Offen',
|
|
'Record_ID' => 'ICI Support Ticket: T26-1216',
|
|
],
|
|
]);
|
|
|
|
$service = new TicketCommunicationService($soapGateway, $odataGateway);
|
|
$result = $service->loadTicketCommunicationFeed('T26-1216', 40);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertIsArray($result['entries']);
|
|
$this->assertGreaterThanOrEqual(3, count($result['entries']));
|
|
$this->assertSame(true, $result['meta']['soap_used']);
|
|
$this->assertSame(false, $result['meta']['fallback_used']);
|
|
|
|
$sources = array_unique(array_map(static fn (array $entry): string => (string) ($entry['source'] ?? ''), $result['entries']));
|
|
$this->assertContains('soap', $sources);
|
|
$this->assertContains('odata', $sources);
|
|
}
|
|
|
|
public function testLoadTicketCommunicationFeedFallsBackToODataWhenSoapFails(): void
|
|
{
|
|
$soapGateway = $this->createMock(BcSoapGateway::class);
|
|
$soapGateway->method('getTicketCommunicationText')->willReturn([
|
|
'ok' => false,
|
|
'soap_used' => false,
|
|
'return_value' => 400,
|
|
'communication_text' => '',
|
|
'message_entries' => '',
|
|
'error' => 'BC SOAP returned error code 400',
|
|
]);
|
|
|
|
$odataGateway = $this->createMock(BcODataGateway::class);
|
|
$odataGateway->expects($this->any())->method('getTicketLog')->with('T26-1216')->willReturn([
|
|
[
|
|
'Entry_No' => 1,
|
|
'Created_By' => 'ICTEST',
|
|
'Creation_Date' => '2026-04-03',
|
|
'Creation_Time' => '07:59:00.000',
|
|
'Type' => 'Nachricht',
|
|
'State_Subtype' => '',
|
|
'Record_ID' => 'Fallback entry',
|
|
],
|
|
]);
|
|
|
|
$service = new TicketCommunicationService($soapGateway, $odataGateway);
|
|
$result = $service->loadTicketCommunicationFeed('T26-1216', 40);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertNotEmpty($result['entries']);
|
|
$this->assertSame(false, $result['meta']['soap_used']);
|
|
$this->assertSame(true, $result['meta']['fallback_used']);
|
|
$this->assertSame('BC SOAP returned error code 400', $result['meta']['soap_error']);
|
|
$this->assertSame('odata', $result['entries'][0]['source']);
|
|
}
|
|
|
|
public function testLoadDebitorCommunicationFeedUsesLastTenTickets(): void
|
|
{
|
|
$soapGateway = $this->createMock(BcSoapGateway::class);
|
|
$soapGateway->method('getTicketCommunicationText')->willReturn([
|
|
'ok' => false,
|
|
'soap_used' => false,
|
|
'return_value' => 400,
|
|
'communication_text' => '',
|
|
'message_entries' => '',
|
|
'error' => 'SOAP unavailable',
|
|
]);
|
|
|
|
$tickets = [];
|
|
for ($i = 1; $i <= 12; $i++) {
|
|
$tickets[] = [
|
|
'No' => 'T' . $i,
|
|
'Last_Activity_Date' => sprintf('2026-04-%02dT08:00:00Z', $i),
|
|
'Created_On' => sprintf('2026-03-%02dT08:00:00Z', $i),
|
|
];
|
|
}
|
|
|
|
$odataGateway = $this->createMock(BcODataGateway::class);
|
|
$odataGateway->expects($this->any())->method('getTicketsForCustomer')->with('10001', 'Test GmbH')->willReturn($tickets);
|
|
$odataGateway->expects($this->any())->method('getContactsForCustomer')->with('', 'Test GmbH')->willReturn([]);
|
|
$odataGateway->method('getTicketLog')->willReturnCallback(static function (string $ticketNo): array {
|
|
return [[
|
|
'Entry_No' => 1,
|
|
'Created_By' => 'ICTEST',
|
|
'Creation_Date' => '2026-04-03',
|
|
'Creation_Time' => '07:59:00.000',
|
|
'Type' => 'Nachricht',
|
|
'State_Subtype' => '',
|
|
'Record_ID' => 'Entry ' . $ticketNo,
|
|
]];
|
|
});
|
|
|
|
$service = new TicketCommunicationService($soapGateway, $odataGateway);
|
|
$result = $service->loadDebitorCommunicationFeed('10001', 'Test GmbH', 10, 40, 250);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertSame(10, $result['meta']['tickets_considered']);
|
|
$this->assertSame(10, $result['meta']['tickets_with_data']);
|
|
$this->assertSame(10, $result['meta']['soap_partial_failures']);
|
|
|
|
$ticketNos = array_unique(array_map(static fn (array $entry): string => (string) ($entry['ticket_no'] ?? ''), $result['entries']));
|
|
$this->assertCount(10, $ticketNos);
|
|
$this->assertNotContains('T1', $ticketNos);
|
|
$this->assertNotContains('T2', $ticketNos);
|
|
$this->assertContains('T12', $ticketNos);
|
|
}
|
|
|
|
public function testLoadDebitorCommunicationFeedResolvesContactActorsOncePerDebitor(): void
|
|
{
|
|
$soapGateway = $this->createMock(BcSoapGateway::class);
|
|
$soapGateway->method('getTicketCommunicationText')->willReturn([
|
|
'ok' => false,
|
|
'soap_used' => false,
|
|
'return_value' => 400,
|
|
'communication_text' => '',
|
|
'message_entries' => '',
|
|
'error' => 'SOAP unavailable',
|
|
]);
|
|
|
|
$odataGateway = $this->createMock(BcODataGateway::class);
|
|
$odataGateway->expects($this->any())->method('getTicketsForCustomer')->with('10001', 'Test GmbH')->willReturn([
|
|
['No' => 'T1', 'Last_Activity_Date' => '2026-04-10T08:00:00Z', 'Created_On' => '2026-04-09T08:00:00Z'],
|
|
['No' => 'T2', 'Last_Activity_Date' => '2026-04-09T08:00:00Z', 'Created_On' => '2026-04-08T08:00:00Z'],
|
|
]);
|
|
$odataGateway->expects($this->once())
|
|
->method('getContactsForCustomer')
|
|
->with('', 'Test GmbH')
|
|
->willReturn([
|
|
['No' => 'KT002967', 'Name' => 'Felix Schneider'],
|
|
]);
|
|
$odataGateway->expects($this->never())
|
|
->method('getTicket');
|
|
$odataGateway->method('getTicketLog')->willReturn([
|
|
[
|
|
'Entry_No' => 1,
|
|
'Created_By' => 'KT002967',
|
|
'Created_By_Type' => 'Contact',
|
|
'Creation_Date' => '2026-04-03',
|
|
'Creation_Time' => '07:59:00.000',
|
|
'Type' => 'Nachricht',
|
|
'State_Subtype' => '',
|
|
'Record_ID' => 'Hallo',
|
|
],
|
|
]);
|
|
|
|
$service = new TicketCommunicationService($soapGateway, $odataGateway);
|
|
$result = $service->loadDebitorCommunicationFeed('10001', 'Test GmbH', 2, 40, 250);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertNotEmpty($result['entries']);
|
|
$actors = array_unique(array_map(static fn (array $entry): string => (string) ($entry['actor'] ?? ''), $result['entries']));
|
|
$this->assertSame(['Felix Schneider'], $actors);
|
|
}
|
|
|
|
public function testLoadTicketCommunicationFeedRespectsEntryLimit(): void
|
|
{
|
|
$soapGateway = $this->createMock(BcSoapGateway::class);
|
|
$soapGateway->method('getTicketCommunicationText')->willReturn([
|
|
'ok' => true,
|
|
'soap_used' => true,
|
|
'return_value' => 0,
|
|
'communication_text' => "2026-04-03 08:10 - Alice: A\n2026-04-03 08:11 - Alice: B\n2026-04-03 08:12 - Alice: C",
|
|
'message_entries' => '',
|
|
]);
|
|
|
|
$odataGateway = $this->createMock(BcODataGateway::class);
|
|
$odataGateway->expects($this->any())->method('getTicketLog')->with('T26-1216')->willReturn([]);
|
|
|
|
$service = new TicketCommunicationService($soapGateway, $odataGateway);
|
|
$result = $service->loadTicketCommunicationFeed('T26-1216', 2);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertCount(2, $result['entries']);
|
|
}
|
|
|
|
public function testLoadTicketCommunicationFeedMapsSoapDataBlobByEntryNo(): void
|
|
{
|
|
$soapGateway = $this->createMock(BcSoapGateway::class);
|
|
$soapGateway->method('getTicketCommunicationText')->willReturn([
|
|
'ok' => true,
|
|
'soap_used' => true,
|
|
'return_value' => 100,
|
|
'communication_text' => '[{"EntryNo":"19356","DataBlob":"<p>Hallo <strong>Team</strong></p>"}]',
|
|
'message_entries' => '',
|
|
]);
|
|
|
|
$odataGateway = $this->createMock(BcODataGateway::class);
|
|
$odataGateway->method('getTicketLog')->willReturn([
|
|
[
|
|
'Entry_No' => 19356,
|
|
'Created_By' => 'KT002452',
|
|
'Creation_Date' => '2026-04-02',
|
|
'Creation_Time' => '13:54:00.000',
|
|
'Type' => 'Nachricht',
|
|
'State_Subtype' => '',
|
|
'Record_ID' => '',
|
|
],
|
|
]);
|
|
|
|
$service = new TicketCommunicationService($soapGateway, $odataGateway);
|
|
$result = $service->loadTicketCommunicationFeed('T26-1204', 40);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertNotEmpty($result['entries']);
|
|
$this->assertSame(false, $result['meta']['fallback_used']);
|
|
$this->assertSame('soap', $result['entries'][0]['source']);
|
|
$this->assertSame('Hallo Team', $result['entries'][0]['message']);
|
|
}
|
|
|
|
public function testLoadTicketCommunicationFeedResolvesContactCodeToName(): void
|
|
{
|
|
$soapGateway = $this->createMock(BcSoapGateway::class);
|
|
$soapGateway->method('getTicketCommunicationText')->willReturn([
|
|
'ok' => false,
|
|
'soap_used' => false,
|
|
'return_value' => 400,
|
|
'communication_text' => '',
|
|
'message_entries' => '',
|
|
'error' => 'BC SOAP returned error code 400',
|
|
]);
|
|
|
|
$odataGateway = $this->createMock(BcODataGateway::class);
|
|
$odataGateway->expects($this->any())->method('getTicketLog')->with('T26-1204')->willReturn([
|
|
[
|
|
'Entry_No' => 19356,
|
|
'Created_By' => 'KT002967',
|
|
'Creation_Date' => '2026-04-02',
|
|
'Creation_Time' => '13:54:00.000',
|
|
'Type' => 'Nachricht',
|
|
'State_Subtype' => '',
|
|
'Record_ID' => 'Hallo',
|
|
],
|
|
]);
|
|
$odataGateway->expects($this->any())->method('getTicket')->with('T26-1204')->willReturn([
|
|
'No' => 'T26-1204',
|
|
'Company_Contact_Name' => 'Aero-Dienst GmbH',
|
|
'Current_Contact_Name' => 'Fallback Kontakt',
|
|
]);
|
|
$odataGateway->expects($this->any())->method('getContactsForCustomer')->with('', 'Aero-Dienst GmbH')->willReturn([
|
|
[
|
|
'No' => 'KT002967',
|
|
'Name' => 'Felix Schneider',
|
|
],
|
|
]);
|
|
|
|
$service = new TicketCommunicationService($soapGateway, $odataGateway);
|
|
$result = $service->loadTicketCommunicationFeed('T26-1204', 40);
|
|
|
|
$this->assertTrue($result['ok']);
|
|
$this->assertNotEmpty($result['entries']);
|
|
$this->assertSame('Felix Schneider', $result['entries'][0]['actor']);
|
|
}
|
|
}
|