Optimoi Zammad-sync: inkrementaalinen haku oletuksena

Auto-refresh hakee nyt vain viimeisen synkkauksen jälkeen muuttuneet
tiketit (updated_at + 5min marginaali). Artikkelit haetaan vain
uusille tai muuttuneille tiketeille. "Hae postit" -nappi tekee
edelleen full syncin (full=true). Nopeuttaa autopäivitystä merkittävästi.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 00:39:24 +02:00
parent ecc0b06ba5
commit cbcfdaa2a3
2 changed files with 36 additions and 9 deletions

37
api.php
View File

@@ -280,12 +280,16 @@ class ZammadClient {
} }
/** Hae tikettejä (search API palauttaa kaikki joihin on oikeus) */ /** Hae tikettejä (search API palauttaa kaikki joihin on oikeus) */
public function getTickets(array $groupIds = [], int $page = 1, int $perPage = 100): array { public function getTickets(array $groupIds = [], int $page = 1, int $perPage = 100, ?string $updatedSince = null): array {
if (!empty($groupIds)) { if (!empty($groupIds)) {
$query = implode(' OR ', array_map(fn($id) => 'group_id:' . $id, $groupIds)); $parts = array_map(fn($id) => 'group_id:' . $id, $groupIds);
$query = '(' . implode(' OR ', $parts) . ')';
} else { } else {
$query = '*'; $query = '*';
} }
if ($updatedSince) {
$query .= ' AND updated_at:>=' . $updatedSince;
}
return $this->request('GET', 'tickets/search?query=' . urlencode($query) . '&per_page=' . $perPage . '&page=' . $page . '&expand=true'); return $this->request('GET', 'tickets/search?query=' . urlencode($query) . '&per_page=' . $perPage . '&page=' . $page . '&expand=true');
} }
@@ -5094,21 +5098,37 @@ switch ($action) {
$integ = dbGetIntegration($companyId, 'zammad'); $integ = dbGetIntegration($companyId, 'zammad');
if (!$integ || !$integ['enabled']) { http_response_code(400); echo json_encode(['error' => 'Zammad ei käytössä']); break; } if (!$integ || !$integ['enabled']) { http_response_code(400); echo json_encode(['error' => 'Zammad ei käytössä']); break; }
// full=1 pakottaa täyden synkkauksen (esim. "Hae postit" -napista)
$fullSync = ($_GET['full'] ?? $_POST['full'] ?? '') === '1';
$input = json_decode(file_get_contents('php://input'), true) ?: [];
if (!empty($input['full'])) $fullSync = true;
try { try {
$cfg = $integ['config']; $cfg = $integ['config'];
$z = new ZammadClient($cfg['url'], $cfg['token']); $z = new ZammadClient($cfg['url'], $cfg['token']);
$groupIds = $cfg['group_ids'] ?? []; $groupIds = $cfg['group_ids'] ?? [];
$syncedGroupNames = $cfg['group_names'] ?? []; $syncedGroupNames = $cfg['group_names'] ?? [];
// Hae viimeisin synkkausaika — inkrementaalinen haku
$lastSync = null;
if (!$fullSync) {
$row = _dbFetchOne("SELECT MAX(updated) as last_updated FROM tickets WHERE company_id = ? AND source = 'zammad'", [$companyId]);
if ($row && $row['last_updated']) {
// Hae 5 min marginaalilla ettei menetä mitään
$lastSync = date('Y-m-d\TH:i:s', strtotime($row['last_updated']) - 300);
}
}
// Hae tikettejä Zammadista // Hae tikettejä Zammadista
$allTickets = []; $allTickets = [];
$page = 1; $page = 1;
$maxPages = $fullSync ? 10 : 3;
do { do {
$batch = $z->getTickets($groupIds, $page, 100); $batch = $z->getTickets($groupIds, $page, 100, $lastSync);
if (empty($batch)) break; if (empty($batch)) break;
$allTickets = array_merge($allTickets, $batch); $allTickets = array_merge($allTickets, $batch);
$page++; $page++;
} while (count($batch) >= 100 && $page <= 10); } while (count($batch) >= 100 && $page <= $maxPages);
$created = 0; $created = 0;
$updated = 0; $updated = 0;
@@ -5156,11 +5176,18 @@ switch ($action) {
$created++; $created++;
} else { } else {
$ticketId = $existing['id']; $ticketId = $existing['id'];
$zammadUpdated = date('Y-m-d H:i:s', strtotime($zt['updated_at'] ?? 'now'));
// Päivitä status/priority/group // Päivitä status/priority/group
_dbExecute( _dbExecute(
"UPDATE tickets SET status = ?, type = ?, priority = ?, subject = ?, zammad_group = ?, updated = ? WHERE id = ?", "UPDATE tickets SET status = ?, type = ?, priority = ?, subject = ?, zammad_group = ?, updated = ? WHERE id = ?",
[$status, $type, $priority, $zt['title'] ?? '', $group, date('Y-m-d H:i:s', strtotime($zt['updated_at'] ?? 'now')), $ticketId] [$status, $type, $priority, $zt['title'] ?? '', $group, $zammadUpdated, $ticketId]
); );
// Ohita artikkelien haku jos tiketti ei muuttunut (inkrementaalisessa syncissä)
$existingUpdated = $existing['updated'] ?? '';
if (!$fullSync && $existingUpdated === $zammadUpdated) {
$updated++;
continue;
}
$updated++; $updated++;
} }

View File

@@ -2006,13 +2006,13 @@ document.getElementById('btn-fetch-emails').addEventListener('click', async () =
const result = await apiCall('ticket_fetch', 'POST'); const result = await apiCall('ticket_fetch', 'POST');
let statusMsg = `Valmis! ${result.new_tickets} uutta tikettiä, ${result.threaded} ketjutettu viestiä.`; let statusMsg = `Valmis! ${result.new_tickets} uutta tikettiä, ${result.threaded} ketjutettu viestiä.`;
// Hae myös Zammadista // Hae myös Zammadista (full sync)
let zammadMsg = ''; let zammadMsg = '';
try { try {
status.textContent = 'Synkataan Zammad...'; status.textContent = 'Synkataan Zammad...';
const zResult = await apiCall('zammad_sync', 'POST'); const zResult = await apiCall('zammad_sync', 'POST', { full: true });
if (zResult.created || zResult.updated || zResult.messages) { if (zResult.created || zResult.updated || zResult.messages_added) {
zammadMsg = ` Zammad: ${zResult.created} uutta, ${zResult.updated} päivitettyä, ${zResult.messages} viestiä.`; zammadMsg = ` Zammad: ${zResult.created} uutta, ${zResult.updated} päivitettyä, ${zResult.messages_added} viestiä.`;
} }
} catch (ze) { /* Zammad ei käytössä tai virhe — ohitetaan */ } } catch (ze) { /* Zammad ei käytössä tai virhe — ohitetaan */ }