feat(helpdesk): add ticket grid filters and resolve support user names in chat
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>
This commit is contained in:
@@ -69,10 +69,18 @@ class TicketCommunicationService
|
||||
$odataEntries = $this->applyActorNameMap($odataEntries, $actorNameMap);
|
||||
$soapEntries = $this->applyActorNameMap($soapEntries, $actorNameMap);
|
||||
} elseif ($allowPerTicketActorLookup) {
|
||||
$resolvedActorNameMap = $this->resolveContactActorNames($ticketNo, $odataEntries, $soapEntries);
|
||||
if ($resolvedActorNameMap !== []) {
|
||||
$odataEntries = $this->applyActorNameMap($odataEntries, $resolvedActorNameMap);
|
||||
$soapEntries = $this->applyActorNameMap($soapEntries, $resolvedActorNameMap);
|
||||
$ticket = null;
|
||||
try {
|
||||
$ticket = $this->bcODataGateway->getTicket($ticketNo);
|
||||
} catch (\Throwable) {
|
||||
// ignore
|
||||
}
|
||||
$resolvedActorNameMap = $this->resolveContactActorNames($ticket, $odataEntries, $soapEntries);
|
||||
$supportActorNameMap = $this->resolveSupportActorNames($ticket, $odataEntries, $soapEntries);
|
||||
$combinedMap = array_merge($resolvedActorNameMap, $supportActorNameMap);
|
||||
if ($combinedMap !== []) {
|
||||
$odataEntries = $this->applyActorNameMap($odataEntries, $combinedMap);
|
||||
$soapEntries = $this->applyActorNameMap($soapEntries, $combinedMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +180,19 @@ class TicketCommunicationService
|
||||
$soapPartialFailures = 0;
|
||||
$actorNameMap = $this->buildContactActorNameMapForDebitor($customerName);
|
||||
|
||||
// Collect support user name → ticket code mappings
|
||||
$supportUserNamesByTicket = [];
|
||||
foreach ($tickets as $ticket) {
|
||||
if (!is_array($ticket)) {
|
||||
continue;
|
||||
}
|
||||
$no = trim((string) ($ticket['No'] ?? ''));
|
||||
$name = trim((string) ($ticket['Support_User_Name'] ?? ''));
|
||||
if ($no !== '' && $name !== '') {
|
||||
$supportUserNamesByTicket[$no] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (array_keys($ticketsToProcess) as $ticketNo) {
|
||||
$feed = $this->loadTicketCommunicationFeed(
|
||||
$ticketNo,
|
||||
@@ -199,6 +220,12 @@ class TicketCommunicationService
|
||||
}
|
||||
}
|
||||
|
||||
// Build support code→name map from entries + ticket data, then apply
|
||||
$supportActorNameMap = $this->buildSupportActorNameMapFromEntries($allEntries, $supportUserNamesByTicket);
|
||||
if ($supportActorNameMap !== []) {
|
||||
$allEntries = $this->applyActorNameMap($allEntries, $supportActorNameMap);
|
||||
}
|
||||
|
||||
$entries = $this->mergeEntries($allEntries, [], $maxEntriesTotal);
|
||||
|
||||
return [
|
||||
@@ -753,24 +780,18 @@ class TicketCommunicationService
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $ticket Pre-fetched ticket data
|
||||
* @param array<int, array<string, string>> $odataEntries
|
||||
* @param array<int, array<string, string>> $soapEntries
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function resolveContactActorNames(string $ticketNo, array $odataEntries, array $soapEntries): array
|
||||
private function resolveContactActorNames(?array $ticket, array $odataEntries, array $soapEntries): array
|
||||
{
|
||||
$contactActorIds = $this->extractContactActorIds([...$odataEntries, ...$soapEntries]);
|
||||
if ($contactActorIds === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$ticket = null;
|
||||
try {
|
||||
$ticket = $this->bcODataGateway->getTicket($ticketNo);
|
||||
} catch (\Throwable) {
|
||||
$ticket = null;
|
||||
}
|
||||
|
||||
$currentContactName = trim((string) (($ticket['Current_Contact_Name'] ?? '')));
|
||||
$customerName = trim((string) (($ticket['Company_Contact_Name'] ?? '')));
|
||||
|
||||
@@ -884,6 +905,97 @@ class TicketCommunicationService
|
||||
return $nameMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve support user actor codes to full names for a single ticket.
|
||||
*
|
||||
* Strategy: use the pre-fetched ticket's Support_User_Name, then look at
|
||||
* which support-type Created_By codes appear in the log entries. If there
|
||||
* is exactly one unique support code, map it to the Support_User_Name.
|
||||
*
|
||||
* @param array<string, mixed>|null $ticket Pre-fetched ticket data
|
||||
* @param array<int, array<string, string>> $odataEntries
|
||||
* @param array<int, array<string, string>> $soapEntries
|
||||
* @return array<string, string>
|
||||
*/
|
||||
private function resolveSupportActorNames(?array $ticket, array $odataEntries, array $soapEntries): array
|
||||
{
|
||||
$supportUserName = trim((string) ($ticket['Support_User_Name'] ?? ''));
|
||||
if ($supportUserName === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$supportCodes = [];
|
||||
foreach ([...$odataEntries, ...$soapEntries] as $entry) {
|
||||
$actorRole = trim((string) ($entry['actor_role'] ?? ''));
|
||||
if ($actorRole !== 'support') {
|
||||
continue;
|
||||
}
|
||||
$actor = strtoupper(trim((string) ($entry['actor'] ?? '')));
|
||||
if ($actor !== '' && !$this->isContactActorId($actor) && mb_strlen($actor) <= 10) {
|
||||
$supportCodes[$actor] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($supportCodes) !== 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$code = array_keys($supportCodes)[0];
|
||||
|
||||
return [$code => $supportUserName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a support user code→name map from already-fetched communication entries.
|
||||
*
|
||||
* Groups entries by ticket_no, finds unique support-type actor codes per
|
||||
* ticket, and maps each code to the ticket's Support_User_Name (from the
|
||||
* pre-built $supportUserNamesByTicket lookup). No additional API calls.
|
||||
*
|
||||
* @param array<int, array<string, string>> $entries
|
||||
* @param array<string, string> $supportUserNamesByTicket ticket_no → Support_User_Name
|
||||
* @return array<string, string> code → full name
|
||||
*/
|
||||
private function buildSupportActorNameMapFromEntries(array $entries, array $supportUserNamesByTicket): array
|
||||
{
|
||||
if ($entries === [] || $supportUserNamesByTicket === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Group support actor codes per ticket
|
||||
$codesByTicket = [];
|
||||
foreach ($entries as $entry) {
|
||||
$actorRole = trim((string) ($entry['actor_role'] ?? ''));
|
||||
if ($actorRole !== 'support') {
|
||||
continue;
|
||||
}
|
||||
$actor = strtoupper(trim((string) ($entry['actor'] ?? '')));
|
||||
if ($actor === '' || $this->isContactActorId($actor) || mb_strlen($actor) > 10) {
|
||||
continue;
|
||||
}
|
||||
$ticketNo = trim((string) ($entry['ticket_no'] ?? ''));
|
||||
if ($ticketNo === '') {
|
||||
continue;
|
||||
}
|
||||
$codesByTicket[$ticketNo][$actor] = true;
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($codesByTicket as $ticketNo => $codes) {
|
||||
if (count($codes) !== 1) {
|
||||
continue;
|
||||
}
|
||||
$supportUserName = $supportUserNamesByTicket[$ticketNo] ?? '';
|
||||
if ($supportUserName === '') {
|
||||
continue;
|
||||
}
|
||||
$code = array_keys($codes)[0];
|
||||
$result[$code] = $supportUserName;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function normalizeActorRole(string $createdByType, string $actor): string
|
||||
{
|
||||
$type = mb_strtolower(trim($createdByType));
|
||||
|
||||
Reference in New Issue
Block a user