Add support/sales/controlling dashboards with KPIs, trend charts and risk indicators. Add debitor communication timeline, contact filters, system recommendations engine, and configurable controlling risk rules. Rename search→index to fix query-string preservation on back navigation. Remove fake escalation rate metric, add KPI info tooltips, and switch trend chart colors to red (created) / green (closed) for clarity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
109 lines
3.2 KiB
PHP
109 lines
3.2 KiB
PHP
<?php
|
|
|
|
use MintyPHP\Http\SessionStoreInterface;
|
|
use MintyPHP\Module\Helpdesk\HelpdeskAuthorizationPolicy;
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorCacheControl;
|
|
use MintyPHP\Module\Helpdesk\Service\DebitorDetailService;
|
|
use MintyPHP\Router;
|
|
use MintyPHP\Support\Guard;
|
|
|
|
Guard::requireLogin();
|
|
Guard::requireAbilityOrForbidden(HelpdeskAuthorizationPolicy::ABILITY_ACCESS);
|
|
|
|
gridRequireGetRequest();
|
|
|
|
$request = requestInput();
|
|
$customerNo = trim((string) $request->query('customerNo', ''));
|
|
$customerName = trim((string) $request->query('customerName', ''));
|
|
$refreshRequested = DebitorCacheControl::parseRefreshFlag($request->query('refresh', ''));
|
|
|
|
if ($customerNo === '' || $customerName === '') {
|
|
http_response_code(400);
|
|
Router::json(['ok' => false, 'error' => 'Missing parameters']);
|
|
|
|
return;
|
|
}
|
|
|
|
$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);
|
|
$tickets = null;
|
|
$cacheUsed = false;
|
|
|
|
if (!$refreshRequested && is_array($cached) && isset($cached['fetched_at']) && (time() - (int) $cached['fetched_at']) < $cacheTtl) {
|
|
$tickets = $cached['tickets'] ?? [];
|
|
$cacheUsed = true;
|
|
}
|
|
|
|
if ($tickets === null) {
|
|
$service = app(DebitorDetailService::class);
|
|
$ticketsResult = $service->loadTickets($customerNo, $customerName);
|
|
|
|
if (!($ticketsResult['ok'] ?? false)) {
|
|
Router::json(['ok' => false, 'error' => $ticketsResult['error'] ?? 'Failed to load tickets']);
|
|
|
|
return;
|
|
}
|
|
|
|
$tickets = $ticketsResult['tickets'] ?? [];
|
|
if (!is_array($tickets)) {
|
|
$tickets = [];
|
|
}
|
|
|
|
$sessionStore->set($cacheKey, [
|
|
'tickets' => $tickets,
|
|
'fetched_at' => time(),
|
|
]);
|
|
}
|
|
|
|
if (!is_array($tickets)) {
|
|
$tickets = [];
|
|
}
|
|
|
|
$ticketSortStamp = static function (array $ticket): int {
|
|
$activity = trim((string) ($ticket['Last_Activity_Date'] ?? ''));
|
|
if ($activity !== '' && $activity !== '0001-01-01T00:00:00Z') {
|
|
$activityTs = strtotime($activity);
|
|
if (is_int($activityTs)) {
|
|
return $activityTs;
|
|
}
|
|
}
|
|
|
|
$created = trim((string) ($ticket['Created_On'] ?? ''));
|
|
if ($created !== '' && $created !== '0001-01-01T00:00:00Z') {
|
|
$createdTs = strtotime($created);
|
|
if (is_int($createdTs)) {
|
|
return $createdTs;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
$recentActivity = array_values($tickets);
|
|
usort($recentActivity, static function (array $a, array $b) use ($ticketSortStamp): int {
|
|
$sa = $ticketSortStamp($a);
|
|
$sb = $ticketSortStamp($b);
|
|
|
|
return $sb <=> $sa;
|
|
});
|
|
$recentActivity = array_slice($recentActivity, 0, 5);
|
|
|
|
Router::json([
|
|
'ok' => true,
|
|
'recent_activity' => $recentActivity,
|
|
'meta' => [
|
|
'total_tickets' => count($tickets),
|
|
'cache_used' => $cacheUsed,
|
|
'cache_bypassed' => $refreshRequested,
|
|
'cache_refreshed' => $refreshRequested,
|
|
],
|
|
]);
|