Zammad sync performance + liitteiden näyttö tikettilistassa

- Rajoita artikkelien haku max 10 tikettiin per sync (loput on-demand)
- Curl timeout 15s + connect timeout 5s
- Frontend: IMAP ja Zammad haetaan rinnakkain (Promise.allSettled)
- Auto-refresh: Zammad sync ei blokkaa tikettien latausta
- Hakaneula-ikoni (📎) tikettilistassa kun viestissä on liitteitä
- has_attachments, source, zammad_group, ticket_number tikettilistaan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 08:56:13 +02:00
parent dc0ed5c75c
commit a69fed75e4
3 changed files with 52 additions and 23 deletions

30
api.php
View File

@@ -250,12 +250,13 @@ class ZammadClient {
$this->token = $token;
}
private function request(string $method, string $endpoint, ?array $data = null): array {
private function request(string $method, string $endpoint, ?array $data = null, int $timeout = 15): array {
$url = $this->url . '/api/v1/' . ltrim($endpoint, '/');
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_TIMEOUT => $timeout,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_HTTPHEADER => [
'Authorization: Token token=' . $this->token,
'Content-Type: application/json',
@@ -3162,6 +3163,10 @@ switch ($action) {
foreach ($tickets as $t) {
$msgCount = count($t['messages'] ?? []);
$lastMsg = $msgCount > 0 ? $t['messages'][$msgCount - 1] : null;
$hasAttachments = false;
foreach ($t['messages'] ?? [] as $m) {
if (!empty($m['attachments'])) { $hasAttachments = true; break; }
}
$list[] = [
'id' => $t['id'],
'subject' => $t['subject'],
@@ -3184,6 +3189,10 @@ switch ($action) {
'message_count' => $msgCount,
'last_message_type' => $lastMsg ? ($lastMsg['type'] ?? '') : '',
'last_message_time' => $lastMsg ? ($lastMsg['timestamp'] ?? '') : '',
'has_attachments' => $hasAttachments,
'source' => $t['source'] ?? '',
'zammad_group' => $t['zammad_group'] ?? '',
'ticket_number' => $t['ticket_number'] ?? '',
];
}
}
@@ -5366,6 +5375,8 @@ switch ($action) {
$created = 0;
$updated = 0;
$messagesAdded = 0;
$articlesFetched = 0;
$maxArticleFetches = 10; // Max tikettejä joille haetaan artikkelit per synk (loput on-demand)
// Zammad state → intran status
$stateMap = [
@@ -5378,6 +5389,8 @@ switch ($action) {
'1 low' => 'matala', '2 normal' => 'normaali', '3 high' => 'korkea',
];
// Vaihe 1: Synkkaa kaikki tiketit (nopea — ei artikkeleja)
$newTicketIds = []; // ticketId => zammadId — artikkelit haetaan toisessa vaiheessa
foreach ($allTickets as $zt) {
$zammadId = (int)$zt['id'];
$existing = dbGetTicketByZammadId($companyId, $zammadId);
@@ -5407,6 +5420,7 @@ switch ($action) {
$zt['updated_at'] ? date('Y-m-d H:i:s', strtotime($zt['updated_at'])) : $now]
);
$created++;
$newTicketIds[$ticketId] = $zammadId;
} else {
$ticketId = $existing['id'];
$zammadUpdated = date('Y-m-d H:i:s', strtotime($zt['updated_at'] ?? 'now'));
@@ -5417,14 +5431,15 @@ switch ($action) {
);
$updated++;
}
}
// Synkkaa artikkelit vain uusille tiketeille (ei jokaiselle — liian hidas)
// Olemassa olevien tikettien artikkelit haetaan on-demand kun käyttäjä avaa tiketin
if ($existing) continue;
// Vaihe 2: Hae artikkelit max N uusimmalle uudelle tiketille (loput on-demand)
$articleQueue = array_slice($newTicketIds, 0, $maxArticleFetches, true);
foreach ($articleQueue as $ticketId => $zammadId) {
try {
$articles = $z->getArticles($zammadId);
$toEmail = '';
$articlesFetched++;
foreach ($articles as $art) {
if (($art['internal'] ?? false)) continue;
// Poimi zammad_to_email ensimmäisestä asiakkaan viestistä
@@ -5480,12 +5495,15 @@ switch ($action) {
}
}
$skippedArticles = max(0, count($newTicketIds) - $maxArticleFetches);
echo json_encode([
'ok' => true,
'tickets_found' => count($allTickets),
'created' => $created,
'updated' => $updated,
'messages_added' => $messagesAdded,
'articles_fetched' => $articlesFetched,
'articles_deferred' => $skippedArticles, // Haetaan on-demand kun tiketti avataan
]);
} catch (\Throwable $e) {
http_response_code(500);