diff --git a/api.php b/api.php index e8037c1..f787b2f 100644 --- a/api.php +++ b/api.php @@ -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); diff --git a/index.html b/index.html index 21a21cd..2ea7b8b 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ Noxus HUB - + @@ -2271,6 +2271,6 @@ - + diff --git a/script.js b/script.js index 05d4c98..6b28f0e 100644 --- a/script.js +++ b/script.js @@ -1491,7 +1491,7 @@ function renderTickets() { ${ticketStatusLabels[t.status] || t.status} ${t.customer_name ? esc(t.customer_name) : '-'} ${typeLabel} - ${prioBadge}${companyBadge}${t.ticket_number ? `#${t.ticket_number}` : ''}${esc(t.subject)} + ${prioBadge}${companyBadge}${t.ticket_number ? `#${t.ticket_number}` : ''}${t.has_attachments ? '📎' : ''}${esc(t.subject)} ${esc(t.mailbox_name || t.from_name || t.from_email)} ${lastType} ${t.message_count} ${timeAgo(t.updated)} @@ -2145,22 +2145,33 @@ document.getElementById('btn-fetch-emails').addEventListener('click', async () = status.textContent = 'Yhdistetään sähköpostipalvelimeen...'; try { - const result = await apiCall('ticket_fetch', 'POST'); - let statusMsg = `Valmis! ${result.new_tickets} uutta tikettiä, ${result.threaded} ketjutettu viestiä.`; + // Hae IMAP ja Zammad rinnakkain — ei enää peräkkäin + status.textContent = 'Haetaan sähköpostit ja synkataan Zammad...'; + const [imapResult, zammadResult] = await Promise.allSettled([ + apiCall('ticket_fetch', 'POST'), + apiCall('zammad_sync', 'POST', { full: true }), + ]); - // Hae myös Zammadista (full sync) - let zammadMsg = ''; - try { - status.textContent = 'Synkataan Zammad...'; - const zResult = await apiCall('zammad_sync', 'POST', { full: true }); - if (zResult.created || zResult.updated || zResult.messages_added) { - zammadMsg = ` Zammad: ${zResult.created} uutta, ${zResult.updated} päivitettyä, ${zResult.messages_added} viestiä.`; - } - } catch (ze) { /* Zammad ei käytössä tai virhe — ohitetaan */ } + let parts = []; + if (imapResult.status === 'fulfilled') { + const r = imapResult.value; + parts.push(`📧 ${r.new_tickets} uutta, ${r.threaded} ketjutettu`); + } else if (imapResult.reason) { + parts.push(`📧 Virhe: ${imapResult.reason.message || 'tuntematon'}`); + } + if (zammadResult.status === 'fulfilled' && zammadResult.value.ok) { + const z = zammadResult.value; + const zParts = []; + if (z.created) zParts.push(`${z.created} uutta`); + if (z.updated) zParts.push(`${z.updated} päivitettyä`); + if (z.messages_added) zParts.push(`${z.messages_added} viestiä`); + if (z.articles_deferred) zParts.push(`${z.articles_deferred} tiketin viestit haetaan kun avaat`); + if (zParts.length) parts.push(`🔗 Zammad: ${zParts.join(', ')}`); + } status.style.background = '#eafaf1'; status.style.color = '#27ae60'; - status.textContent = statusMsg + zammadMsg; + status.textContent = parts.length ? parts.join(' | ') : 'Valmis — ei uusia viestejä.'; await loadTickets(); } catch (e) { status.style.background = '#fef2f2'; @@ -2185,8 +2196,8 @@ function startTicketAutoRefresh() { const supportActive = document.getElementById('tab-content-support').classList.contains('active'); const listVisible = document.getElementById('ticket-list-view').style.display !== 'none'; if (supportActive && listVisible) { - // Synkkaa Zammad taustalla ennen tikettien latausta - try { await apiCall('zammad_sync', 'POST'); } catch (e) { /* Zammad ei käytössä */ } + // Synkkaa Zammad taustalla JA lataa tiketit rinnakkain — ei blokkaa + apiCall('zammad_sync', 'POST').catch(() => {}); loadTickets(); } }, seconds * 1000);