prepare('INSERT INTO departments (name, email) VALUES (?,?)')->execute(['Support', 'support@firma.example']); $dept = (int) $pdo->lastInsertId(); $person = function (string $name, string $role, int $d) use ($pdo, $now): int { $pdo->prepare('INSERT INTO staff (name, email, password_hash, role, department_id, is_active, created_at) VALUES (?,?,?,?,?,1,?)') ->execute([$name, strtolower($name) . '@firma.example', 'x', $role, $d, $now]); return (int) $pdo->lastInsertId(); }; $anna = $person('Anna', 'agent', $dept); $ben = $person('Ben', 'agent', $dept); $chef = $person('Chef', 'admin', $dept); $away = fn(int $id, bool $on, ?int $sub = null) => $pdo->prepare('UPDATE staff SET is_on_vacation = ?, vacation_substitute_id = ? WHERE id = ?')->execute([$on ? 1 : 0, $sub, $id]); // Mails über die Warteschlange abfangen: SMTP absichtlich ohne Server, Mails landen in mail_queue set_setting('mail_enabled', '1'); set_setting('smtp_host', '127.0.0.1'); set_setting('smtp_port', '1'); set_setting('smtp_encryption', 'none'); set_setting('smtp_from_email', 'noreply@firma.example'); $queued = function () use ($pdo): array { return $pdo->query('SELECT to_email FROM mail_queue ORDER BY id')->fetchAll(PDO::FETCH_COLUMN); }; $reset = fn() => $pdo->exec('DELETE FROM mail_queue'); $ticket = ['id' => 1, 'ticket_number' => '261030-0001', 'subject' => 'Test', 'requester_name' => 'Kunde', 'requester_email' => 'kunde@x.example', 'department_id' => $dept, 'department_name' => 'Support', 'assigned_staff_id' => null]; // 1) Neues Ticket $reset(); notify_staff_new_ticket($ticket); $to = $queued(); sort($to); $check(in_array('anna@firma.example', $to, true) && in_array('ben@firma.example', $to, true) && in_array('chef@firma.example', $to, true), 'Ohne Urlaub gehen Mails an alle: ' . json_encode($to)); $reset(); $away($ben, true); notify_staff_new_ticket($ticket); $check(!in_array('ben@firma.example', $queued(), true), 'Neues Ticket: Mitarbeiter im Urlaub bekommt keine Mail'); $away($ben, false); // 2) Überfällig an Admins $reset(); $away($chef, true); notify_admins_overdue_ticket($ticket); $check(!in_array('chef@firma.example', $queued(), true), 'Überfällig: Administrator im Urlaub bekommt keine Mail'); $away($chef, false); // 3) SLA-Empfänger: Bearbeiter im Urlaub ohne Vertretung bekommt nichts, mit Vertretung bekommt diese die Mail $away($anna, true); $r = sla_overdue_recipients($pdo, ['assigned_staff_id' => $anna, 'department_id' => $dept]); $check(!in_array('anna@firma.example', $r, true), 'SLA: Bearbeiter im Urlaub ohne Vertretung bekommt keine Mail: ' . json_encode($r)); $away($anna, true, $ben); $r = sla_overdue_recipients($pdo, ['assigned_staff_id' => $anna, 'department_id' => $dept]); $check($r === ['ben@firma.example'], 'SLA: mit Vertretung bekommt diese die Mail: ' . json_encode($r)); $away($anna, false); // 4) Erwähnung per Mail $pdo->prepare("INSERT INTO tickets (ticket_number, subject, status, department_id, requester_name, requester_email, priority_id, created_at, updated_at) VALUES (?,?,?,?,?,?,(SELECT MIN(id) FROM priorities),?,?)") ->execute(['261030-0001', 'Test', 'open', $dept, 'Kunde', 'kunde@x.example', $now, $now]); $ticket['id'] = (int) $pdo->lastInsertId(); $pdo->prepare("INSERT INTO ticket_threads (ticket_id, author_type, message, is_internal_note, created_at) VALUES (?,?,?,0,?)")->execute([$ticket['id'], 'staff', 'x', $now]); $threadId = (int) $pdo->lastInsertId(); $reset(); $away($ben, true); if (true) { save_mentions($pdo, (int) $ticket['id'], $threadId, $anna, '@Ben bitte prüfen', $ticket + ['ticket_number' => '261030-0001']); $check(!in_array('ben@firma.example', $queued(), true), 'Erwähnung: Mitarbeiter im Urlaub bekommt keine Mail'); } $away($ben, false); // 5) Push-Hilfsfunktion und Abfragen $check(staff_is_on_vacation($ben) === false, 'Ben ist zurück'); $away($ben, true); $check(staff_is_on_vacation($ben) === true, 'Ben ist im Urlaub'); $src = file_get_contents(dirname(__DIR__) . '/includes/push.php') . file_get_contents(dirname(__DIR__) . '/includes/fcm.php'); $check(str_contains($src, 'vacation_expand_ids') && substr_count($src, 'vacation_expand_ids') >= 3, 'Web-Push und Android-Push ersetzen Mitarbeiter im Urlaub durch die Vertretung'); $away($ben, false); // 6) Android-Push: Geräte von Mitarbeitern im Urlaub werden übersprungen $pdo->prepare('INSERT INTO push_devices (staff_id, token, token_hash, created_at) VALUES (?,?,?,?)')->execute([$anna, str_repeat('a', 40), hash('sha256', 'a'), $now]); $pdo->prepare('INSERT INTO push_devices (staff_id, token, token_hash, created_at) VALUES (?,?,?,?)')->execute([$ben, str_repeat('b', 40), hash('sha256', 'b'), $now]); $away($ben, true); $sel = $pdo->prepare("SELECT d.staff_id FROM push_devices d JOIN staff s ON s.id = d.staff_id WHERE s.is_active = 1 AND s.is_on_vacation = 0 AND d.staff_id IN (?,?)"); $sel->execute([$anna, $ben]); $check($sel->fetchAll(PDO::FETCH_COLUMN) == [$anna], 'Android: nur das Gerät des anwesenden Mitarbeiters bleibt'); // 7) Die Vertretung bekommt stattdessen die Benachrichtigung $reset(); $away($ben, true, $anna); notify_staff_new_ticket($ticket); $to = $queued(); $check(in_array('anna@firma.example', $to, true) && !in_array('ben@firma.example', $to, true), 'Neues Ticket: die Vertretung bekommt die Mail des Abwesenden: ' . json_encode($to)); $check(count(array_keys($to, 'anna@firma.example', true)) === 1, 'Die Vertretung bekommt die Mail nur einmal, auch wenn sie selbst Empfänger ist'); $reset(); $away($chef, true, $ben); $away($ben, true, $anna); notify_admins_overdue_ticket($ticket); $to = $queued(); $check(in_array('anna@firma.example', $to, true) && !in_array('chef@firma.example', $to, true) && !in_array('ben@firma.example', $to, true), 'Kette: Admin im Urlaub → Vertretung im Urlaub → deren Vertretung bekommt die Mail: ' . json_encode($to)); $away($chef, false); $away($ben, false); // Push: Abos der Abwesenden gehen an die Vertretung $sub = fn(int $id, string $ep) => $pdo->prepare('INSERT INTO push_subscriptions (staff_id, endpoint, p256dh, auth, created_at) VALUES (?,?,?,?,?)')->execute([$id, $ep, 'k', 'a', $now]); $sub($anna, 'https://push.example/anna'); $sub($ben, 'https://push.example/ben'); $endpoints = fn() => array_map(fn($r) => $r['endpoint'], push_subscriptions_for_department($pdo, $dept)); $check(in_array('https://push.example/ben', $endpoints(), true), 'Ohne Urlaub bekommt Ben das Browser-Push'); $away($ben, true, $anna); $e = $endpoints(); $check(!in_array('https://push.example/ben', $e, true) && in_array('https://push.example/anna', $e, true), 'Im Urlaub geht das Browser-Push an die Vertretung: ' . json_encode($e)); $away($ben, true, null); $e = $endpoints(); $check(!in_array('https://push.example/ben', $e, true), 'Ohne Vertretung bekommt der Abwesende nichts'); $check(vacation_expand_ids($pdo, [$ben]) === [], 'Ohne Vertretung entfällt der Abwesende'); $away($ben, true, $anna); $check(vacation_expand_ids($pdo, [$ben, $anna]) === [$anna], 'Vertretung wird nicht doppelt gezählt'); $away($ben, false); @unlink($db); if ($failures) { fwrite(STDERR, "Urlaub ohne Benachrichtigungen fehlgeschlagen:\n- " . implode("\n- ", $failures) . "\n"); exit(1); } echo "Urlaub ohne Benachrichtigungen bestanden.\n";