date('Y-m-d H:i:s', time() - $h * 3600); $dept = (int) $pdo->query('SELECT id FROM departments ORDER BY id LIMIT 1')->fetchColumn(); $prio = (int) $pdo->query('SELECT id FROM priorities ORDER BY id LIMIT 1')->fetchColumn(); $pdo->exec('DELETE FROM staff'); $pdo->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)')->execute(['Agent', 'agent@test.de', 'x', 'agent', $dept, $now]); $agent = (int) $pdo->lastInsertId(); $mk = function (string $n, string $status, array $o = []) use ($pdo, $dept, $prio, $now, $agent): int { $pdo->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, assigned_staff_id, resolution_due_at, first_response_due_at, is_overdue, snoozed_until, snoozed_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)') ->execute([$n, 'S ' . $n, 'K', 'k@example.org', $dept, $prio, $status, 'web', $agent, $o['due'] ?? null, $o['rdue'] ?? null, $o['overdue'] ?? 0, $o['snooze'] ?? null, $o['snooze'] ?? null ? $agent : null, $o['created'] ?? $now, $o['updated'] ?? $now]); return (int) $pdo->lastInsertId(); }; $row = fn(int $id) => $pdo->query("SELECT * FROM tickets WHERE id = {$id}")->fetch(PDO::FETCH_ASSOC); // 1. Erledigen löscht „überfällig“ und die Wiedervorlage, für gelöst und geschlossen gleich foreach (['resolved', 'closed'] as $st) { $t = $mk('D-' . $st, 'open', ['due' => $ago(5), 'overdue' => 1, 'snooze' => $ago(-5), 'created' => $ago(60)]); $pdo->prepare('UPDATE tickets SET status = ? WHERE id = ?')->execute([$st, $t]); ticket_sla_status_transition($pdo, $t, 'open', $st); $r = $row($t); $check((int) $r['is_overdue'] === 0 && $r['snoozed_until'] === null, "offen → {$st}: nicht mehr überfällig, Wiedervorlage entfällt"); } // Ein offenes Ticket bleibt unberührt $tOpen = $mk('D-open', 'open', ['due' => $ago(5), 'overdue' => 1, 'created' => $ago(60)]); ticket_sla_status_transition($pdo, $tOpen, 'open', 'answered'); $check((int) $row($tOpen)['is_overdue'] === 1, 'offen → beantwortet: bleibt überfällig'); // gelöst → geschlossen: nichts Neues $tc = $mk('D-rc', 'closed', ['due' => $ago(5), 'created' => $ago(60)]); ticket_sla_status_transition($pdo, $tc, 'resolved', 'closed'); $check((int) $row($tc)['is_overdue'] === 0, 'gelöst → geschlossen: weiter nicht überfällig'); // 2. SLA-Prüfung fasst erledigte Tickets nicht an und meldet nichts $a = $mk('S-closed', 'closed', ['due' => $ago(9), 'rdue' => $ago(20), 'created' => $ago(80)]); $b = $mk('S-resolved', 'resolved', ['due' => $ago(9), 'rdue' => $ago(20), 'created' => $ago(80)]); $c = $mk('S-flag', 'closed', ['due' => $ago(9), 'overdue' => 1, 'created' => $ago(80)]); $res = run_check_sla($pdo); $check($row($a)['is_overdue'] == 0 && $row($b)['is_overdue'] == 0, 'SLA-Prüfung: geschlossene und gelöste Tickets werden nicht überfällig'); $check((int) $row($c)['is_overdue'] === 0, 'SLA-Prüfung: ein übrig gebliebenes „überfällig“ bei einem erledigten Ticket wird entfernt'); $check($res['newlyOverdue'] === 0, 'SLA-Prüfung: keine neue Überschreitung gemeldet'); $hist = (int) $pdo->query("SELECT COUNT(*) FROM ticket_sla_history WHERE ticket_id IN ({$a},{$b}) AND event_type = 'overdue'")->fetchColumn(); $check($hist === 0, 'SLA-Verlauf: für erledigte Tickets entsteht kein „überschritten“'); // 3. Eskalationsregeln erfassen keine erledigten Tickets require_once dirname(__DIR__) . '/cron/check_escalations.php'; $pdo->prepare("INSERT INTO escalation_rules (name,is_active,trigger_type,trigger_hours,action_notify_admin,action_notify_staff,sort_order,created_at) VALUES (?,?,?,?,?,?,?,?)") ->execute(['Alt', 1, 'overdue', 1, 1, 1, 1, $now]); $eo = $mk('E-open', 'open', ['due' => $ago(5), 'overdue' => 1, 'created' => $ago(50)]); $ec = $mk('E-closed', 'closed', ['due' => $ago(5), 'overdue' => 1, 'created' => $ago(50)]); $r = run_check_escalations($pdo); $check((int) $pdo->query("SELECT COUNT(*) FROM escalation_log WHERE ticket_id = {$eo}")->fetchColumn() === 1, 'Eskalation: die Regel greift bei einem offenen Ticket (Gegenprobe)'); $hit = (int) $pdo->query("SELECT COUNT(*) FROM escalation_log WHERE ticket_id IN ({$a},{$b},{$c},{$ec})")->fetchColumn(); $check($hit === 0, 'Eskalation: erledigte Tickets werden nicht eskaliert'); // 4. Wiedervorlage: bei erledigten Tickets still entfernt, bei offenen mit Benachrichtigung $sc = $mk('W-closed', 'closed', ['snooze' => $ago(1)]); $so = $mk('W-open', 'open', ['snooze' => $ago(1)]); $woken = ticket_snooze_wake_due($pdo); $check($woken === 1, "Wiedervorlage: nur das offene Ticket wird zurückgeholt (war {$woken})"); $check($row($sc)['snoozed_until'] === null && $row($so)['snoozed_until'] === null, 'Wiedervorlage: bei beiden aufgeräumt'); $act = fn(int $id) => (int) $pdo->query("SELECT COUNT(*) FROM activity_log WHERE ticket_id = {$id} AND action LIKE '%Wiedervorlage fällig%'")->fetchColumn(); $check($act($sc) === 0 && $act($so) === 1, 'Wiedervorlage: Hinweis „fällig“ nur beim offenen Ticket'); // 5. Erinnerung „Ticket wartet auf Antwort“ erfasst nur offene Tickets set_setting('wait_reminder_enabled', '1'); set_setting('wait_reminder_hours', '1'); $wo = $mk('A-open', 'open', ['due' => $ago(-40), 'created' => $ago(100), 'updated' => $ago(100)]); $wc = $mk('A-closed', 'closed', ['due' => $ago(-40), 'created' => $ago(100), 'updated' => $ago(100)]); $wr = wait_reminders_run($pdo, $now); $check((int) ($wr['sent'] ?? 0) === 1, 'Erinnerung: das offene Ticket wird erinnert (Gegenprobe)'); $check($row($wc)['wait_reminded_at'] === null && $row($wo)['wait_reminded_at'] !== null, 'Erinnerung: für das erledigte Ticket wird nichts gesendet'); echo $failures ? "FEHLGESCHLAGEN:\n - " . implode("\n - ", $failures) . "\n" : ''; echo "Ticket-erledigt-Test: " . ($total - count($failures)) . " von {$total} Prüfungen in Ordnung.\n"; exit($failures ? 1 : 0);