Resolve all non-false-positive PHPStan 2 findings, reducing the baseline from 486 to 382 entries (only unused-public false positives remain). Categories fixed: - Remove 39 redundant type checks (is_string, is_array, is_object, method_exists) - Remove 12 no-op array_values() on already-sequential lists - Simplify 13 null-coalesce on guaranteed offsets - Remove 8 always-true instanceof checks - Replace 12 redundant test assertions (assertTrue(true) → addToAssertionCount) - Simplify 6 unnecessary nullsafe operators (?-> → ->) - Fix 6 always-true !== '' comparisons - Fix remaining: unset.offset, return.unusedType, staticMethod narrowing 53 files changed across core/, modules/, pages/, and tests/. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
268 lines
9.1 KiB
PHP
268 lines
9.1 KiB
PHP
<?php
|
|
|
|
use MintyPHP\Module\Helpdesk\HelpdeskAuthorizationPolicy;
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorCacheControl;
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorDetailService;
|
|
use MintyPHP\Http\SessionStoreInterface;
|
|
use MintyPHP\Router;
|
|
use MintyPHP\Support\Guard;
|
|
|
|
Guard::requireLogin();
|
|
Guard::requireAbilityOrForbidden(HelpdeskAuthorizationPolicy::ABILITY_ACCESS);
|
|
|
|
gridRequireGetRequest();
|
|
|
|
$filters = gridParseFiltersFromSchemaFile(__DIR__ . '/debitor-tickets-filter-schema.php');
|
|
$request = requestInput();
|
|
|
|
$customerNo = (string) ($filters['customerNo'] ?? '');
|
|
$customerName = (string) ($filters['customerName'] ?? '');
|
|
$refreshRequested = DebitorCacheControl::parseRefreshFlag($request->query('refresh', ''));
|
|
|
|
if ($customerNo === '' || $customerName === '') {
|
|
Router::json([
|
|
'data' => [],
|
|
'total' => 0,
|
|
'meta' => [
|
|
'cache_used' => false,
|
|
'cache_bypassed' => $refreshRequested,
|
|
'cache_refreshed' => false,
|
|
],
|
|
]);
|
|
|
|
return;
|
|
}
|
|
|
|
// --- Session cache (lazy TTL = 120s) ---
|
|
|
|
$sessionStore = app(SessionStoreInterface::class);
|
|
$session = $sessionStore->all();
|
|
$tenantScope = DebitorCacheControl::resolveTenantScope($session);
|
|
if ($refreshRequested) {
|
|
DebitorCacheControl::invalidateDebitorCaches($sessionStore, $tenantScope, $customerNo, true);
|
|
}
|
|
|
|
$cacheKey = DebitorCacheControl::ticketsKey($tenantScope, $customerNo);
|
|
$cacheTtl = DebitorCacheControl::TTL_STANDARD_SECONDS;
|
|
$cached = $sessionStore->get($cacheKey);
|
|
$allTickets = null;
|
|
$cacheUsed = false;
|
|
$serviceMeta = [
|
|
'source' => 'primary.company_contact_name',
|
|
'fallback_used' => false,
|
|
'fallback_reason' => '',
|
|
];
|
|
|
|
if (!$refreshRequested && is_array($cached) && isset($cached['fetched_at']) && (time() - (int) $cached['fetched_at']) < $cacheTtl) {
|
|
$allTickets = $cached['tickets'] ?? [];
|
|
$cacheUsed = true;
|
|
if (is_array($cached['service_meta'] ?? null)) {
|
|
$serviceMeta = [
|
|
'source' => (string) ($cached['service_meta']['source'] ?? $serviceMeta['source']),
|
|
'fallback_used' => (bool) ($cached['service_meta']['fallback_used'] ?? false),
|
|
'fallback_reason' => (string) ($cached['service_meta']['fallback_reason'] ?? ''),
|
|
];
|
|
}
|
|
}
|
|
|
|
if ($allTickets === null) {
|
|
$service = app(DebitorDetailService::class);
|
|
$result = $service->loadTickets($customerNo, $customerName);
|
|
|
|
if (!($result['ok'] ?? false)) {
|
|
$resultMeta = is_array($result['meta'] ?? null) ? $result['meta'] : [];
|
|
Router::json([
|
|
'data' => [],
|
|
'total' => 0,
|
|
'meta' => [
|
|
'cache_used' => $cacheUsed,
|
|
'cache_bypassed' => $refreshRequested,
|
|
'cache_refreshed' => $refreshRequested,
|
|
'source' => (string) ($resultMeta['source'] ?? $serviceMeta['source']),
|
|
'fallback_used' => (bool) ($resultMeta['fallback_used'] ?? false),
|
|
'fallback_reason' => (string) ($resultMeta['fallback_reason'] ?? ''),
|
|
],
|
|
]);
|
|
|
|
return;
|
|
}
|
|
|
|
$allTickets = $result['tickets'] ?? [];
|
|
$resultMeta = is_array($result['meta'] ?? null) ? $result['meta'] : [];
|
|
$serviceMeta = [
|
|
'source' => (string) ($resultMeta['source'] ?? $serviceMeta['source']),
|
|
'fallback_used' => (bool) ($resultMeta['fallback_used'] ?? false),
|
|
'fallback_reason' => (string) ($resultMeta['fallback_reason'] ?? ''),
|
|
];
|
|
|
|
$sessionStore->set($cacheKey, [
|
|
'tickets' => $allTickets,
|
|
'service_meta' => $serviceMeta,
|
|
'fetched_at' => time(),
|
|
]);
|
|
}
|
|
|
|
// --- PHP-side filtering ---
|
|
|
|
$search = trim((string) ($filters['search'] ?? ''));
|
|
$status = trim((string) ($filters['status'] ?? ''));
|
|
$category = trim((string) ($filters['category'] ?? ''));
|
|
$supportUser = trim((string) ($filters['support'] ?? ''));
|
|
$contact = trim((string) ($filters['contact'] ?? ''));
|
|
$period = trim((string) ($filters['period'] ?? ''));
|
|
$order = (string) ($filters['order'] ?? 'Created_On');
|
|
$dir = (string) ($filters['dir'] ?? 'desc');
|
|
$limit = (int) ($filters['limit'] ?? 10);
|
|
$offset = (int) ($filters['offset'] ?? 0);
|
|
|
|
$filtered = $allTickets;
|
|
|
|
// Search: stripos on No + Description + Current_Contact_Name + Support_User_Name
|
|
if ($search !== '') {
|
|
$searchLower = mb_strtolower($search);
|
|
$filtered = array_values(array_filter($filtered, static function (array $ticket) use ($searchLower): bool {
|
|
$haystack = mb_strtolower(
|
|
($ticket['No'] ?? '') . ' '
|
|
. ($ticket['Description'] ?? '') . ' '
|
|
. ($ticket['Current_Contact_Name'] ?? '') . ' '
|
|
. ($ticket['Support_User_Name'] ?? '')
|
|
);
|
|
|
|
return str_contains($haystack, $searchLower);
|
|
}));
|
|
}
|
|
|
|
// Status filter
|
|
if ($status !== '') {
|
|
$statusMap = [
|
|
'open' => ['Offen', 'Open'],
|
|
'in_progress' => ['In Bearbeitung', 'In Progress'],
|
|
'closed' => ['Erledigt', 'Resolved', 'Closed', 'Geschlossen'],
|
|
];
|
|
$allowedStates = $statusMap[$status] ?? [];
|
|
if ($allowedStates !== []) {
|
|
$filtered = array_values(array_filter($filtered, static function (array $ticket) use ($allowedStates): bool {
|
|
return in_array((string) ($ticket['Ticket_State'] ?? ''), $allowedStates, true);
|
|
}));
|
|
}
|
|
}
|
|
|
|
// Category filter
|
|
if ($category !== '') {
|
|
$filtered = array_values(array_filter($filtered, static function (array $ticket) use ($category): bool {
|
|
return trim((string) ($ticket['Category_1_Description'] ?? '')) === $category;
|
|
}));
|
|
}
|
|
|
|
// Support user filter
|
|
if ($supportUser !== '') {
|
|
$filtered = array_values(array_filter($filtered, static function (array $ticket) use ($supportUser): bool {
|
|
return trim((string) ($ticket['Support_User_Name'] ?? '')) === $supportUser;
|
|
}));
|
|
}
|
|
|
|
// Contact filter
|
|
if ($contact !== '') {
|
|
$filtered = array_values(array_filter($filtered, static function (array $ticket) use ($contact): bool {
|
|
return trim((string) ($ticket['Current_Contact_Name'] ?? '')) === $contact;
|
|
}));
|
|
}
|
|
|
|
// Period filter (days from today)
|
|
if ($period !== '' && ctype_digit($period)) {
|
|
$cutoff = (new DateTimeImmutable('now'))->modify('-' . $period . ' days')->format('Y-m-d\TH:i:s\Z');
|
|
$filtered = array_values(array_filter($filtered, static function (array $ticket) use ($cutoff): bool {
|
|
$created = (string) ($ticket['Created_On'] ?? '');
|
|
if ($created === '' || $created === '0001-01-01T00:00:00Z') {
|
|
return false;
|
|
}
|
|
|
|
return $created >= $cutoff;
|
|
}));
|
|
}
|
|
|
|
// --- Sorting ---
|
|
|
|
$total = count($filtered);
|
|
|
|
usort($filtered, static function (array $a, array $b) use ($order, $dir): int {
|
|
$va = (string) ($a[$order] ?? '');
|
|
$vb = (string) ($b[$order] ?? '');
|
|
$cmp = strnatcasecmp($va, $vb);
|
|
|
|
return $dir === 'desc' ? -$cmp : $cmp;
|
|
});
|
|
|
|
// --- Pagination ---
|
|
|
|
$rows = array_slice($filtered, $offset, $limit);
|
|
|
|
// --- Row preparation ---
|
|
|
|
$stateVariantMap = [
|
|
'Offen' => 'warning',
|
|
'Open' => 'warning',
|
|
'In Bearbeitung' => 'warning',
|
|
'In Progress' => 'warning',
|
|
'Erledigt' => 'success',
|
|
'Resolved' => 'success',
|
|
'Closed' => 'success',
|
|
'Geschlossen' => 'success',
|
|
];
|
|
|
|
$ticketBaseUrl = lurl('helpdesk/ticket/');
|
|
|
|
$preparedRows = [];
|
|
foreach ($rows as $ticket) {
|
|
$ticketNo = (string) ($ticket['No'] ?? '');
|
|
$state = (string) ($ticket['Ticket_State'] ?? '');
|
|
$createdRaw = (string) ($ticket['Created_On'] ?? '');
|
|
$lastActivityRaw = (string) ($ticket['Last_Activity_Date'] ?? '');
|
|
|
|
$createdFormatted = '';
|
|
if ($createdRaw !== '' && $createdRaw !== '0001-01-01T00:00:00Z') {
|
|
try {
|
|
$createdFormatted = (new DateTimeImmutable($createdRaw))->format('d.m.Y H:i');
|
|
} catch (\Throwable) {
|
|
$createdFormatted = $createdRaw;
|
|
}
|
|
}
|
|
|
|
$lastActivityFormatted = '';
|
|
if ($lastActivityRaw !== '' && $lastActivityRaw !== '0001-01-01T00:00:00Z') {
|
|
try {
|
|
$lastActivityFormatted = (new DateTimeImmutable($lastActivityRaw))->format('d.m.Y H:i');
|
|
} catch (\Throwable) {
|
|
$lastActivityFormatted = $lastActivityRaw;
|
|
}
|
|
}
|
|
|
|
$preparedRows[] = [
|
|
'No' => $ticketNo,
|
|
'Description' => (string) ($ticket['Description'] ?? ''),
|
|
'Ticket_State' => $state,
|
|
'state_variant' => $stateVariantMap[$state] ?? 'neutral',
|
|
'Category_1_Description' => (string) ($ticket['Category_1_Description'] ?? ''),
|
|
'Support_User_Name' => (string) ($ticket['Support_User_Name'] ?? ''),
|
|
'Current_Contact_Name' => (string) ($ticket['Current_Contact_Name'] ?? ''),
|
|
'Created_On' => $createdRaw,
|
|
'Last_Activity_Date' => $lastActivityRaw,
|
|
'created_formatted' => $createdFormatted,
|
|
'last_activity_formatted' => $lastActivityFormatted,
|
|
'ticket_url' => $ticketBaseUrl . rawurlencode($ticketNo) . '?from=' . rawurlencode($customerNo),
|
|
];
|
|
}
|
|
|
|
Router::json([
|
|
'data' => $preparedRows,
|
|
'total' => max(0, $total),
|
|
'meta' => [
|
|
'cache_used' => $cacheUsed,
|
|
'cache_bypassed' => $refreshRequested,
|
|
'cache_refreshed' => $refreshRequested,
|
|
'source' => $serviceMeta['source'],
|
|
'fallback_used' => $serviceMeta['fallback_used'],
|
|
'fallback_reason' => $serviceMeta['fallback_reason'],
|
|
],
|
|
]);
|