Files
deskhub/DeskHub-Webseite/includes/vacation.php
T
2026-11-05 21:21:48 +00:00

154 lines
8.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Abwesenheit im Profil: Offene Tickets an die Vertretung weitergeben und nach dem Urlaub zurückgeben.
* Das Plugin „Urlaub und Vertretung“ macht dasselbe nach Datum; ist dort eine Abwesenheit aktiv, übernimmt das Plugin und der Server hält sich heraus.
* Welche Tickets weitergegeben wurden, steht in vacation_moves: Zurückgegeben wird nur, was noch bei der Vertretung liegt und offen ist
* (hat jemand ein Ticket inzwischen umgehängt oder erledigt, bleibt es so).
*/
/** Wer arbeitet an Stelle von $staffId? Folgt Vertretungsketten (höchstens drei Stufen, ohne Schleifen); null ohne nutzbare Vertretung. */
function vacation_target(PDO $pdo, int $staffId): ?int
{
$get = $pdo->prepare('SELECT id, is_active, is_on_vacation, vacation_substitute_id FROM staff WHERE id = ?');
$get->execute([$staffId]);
$row = $get->fetch();
if (!$row || !(int) $row['is_on_vacation']) return null;
$seen = [$staffId];
for ($i = 0; $i < 3; $i++) {
$sub = (int) ($row['vacation_substitute_id'] ?? 0);
if ($sub <= 0 || in_array($sub, $seen, true)) return null;
$get->execute([$sub]);
$row = $get->fetch();
if (!$row || !(int) $row['is_active']) return null;
if (!(int) $row['is_on_vacation']) return $sub;
$seen[] = $sub;
}
return null;
}
/** Übernimmt das Plugin „Urlaub und Vertretung“ gerade diese Abwesenheit? */
function vacation_plugin_owns(PDO $pdo, int $staffId): bool
{
try {
$q = $pdo->prepare("SELECT COUNT(*) FROM plugin_vertretung_absences WHERE staff_id = ? AND status = 'active'");
$q->execute([$staffId]);
return (int) $q->fetchColumn() > 0;
} catch (Throwable $e) {
return false; // Plugin nicht installiert
}
}
/** Gibt die offenen Tickets von $staffId an die Vertretung weiter (die Abwesenheit muss schon eingetragen sein). Liefert die Zahl. */
function vacation_hand_over(PDO $pdo, int $staffId): int
{
if (vacation_plugin_owns($pdo, $staffId)) return 0;
$to = vacation_target($pdo, $staffId);
if ($to === null || $to === $staffId) return 0;
$name = (string) $pdo->query('SELECT name FROM staff WHERE id = ' . $staffId)->fetchColumn();
$toName = (string) $pdo->query('SELECT name FROM staff WHERE id = ' . $to)->fetchColumn();
$t = $pdo->prepare("SELECT id, ticket_number, subject FROM tickets WHERE assigned_staff_id = ? AND is_spam = 0 AND status NOT IN ('resolved','closed') ORDER BY id");
$t->execute([$staffId]);
$tickets = $t->fetchAll();
$own = !$pdo->inTransaction();
if ($own) $pdo->beginTransaction(); // entweder alles (Zuweisung und Vermerk zur Rückgabe) oder nichts
try {
$move = $pdo->prepare('UPDATE tickets SET assigned_staff_id = ? WHERE id = ?');
$note = $pdo->prepare('INSERT INTO vacation_moves (ticket_id, from_staff_id, to_staff_id, created_at) VALUES (?,?,?,?)');
foreach ($tickets as $row) {
$move->execute([$to, $row['id']]);
$note->execute([$row['id'], $staffId, $to, db_now()]);
log_activity($pdo, (int) $row['id'], $staffId, 'An die Vertretung ' . $toName . ' weitergegeben (' . $name . ' ist abwesend)');
}
if ($own) $pdo->commit();
} catch (Throwable $e) {
if ($own && $pdo->inTransaction()) $pdo->rollBack();
throw $e;
}
if ($tickets) {
$list = '';
foreach (array_slice($tickets, 0, 15) as $row) $list .= '<li>#' . e((string) $row['ticket_number']) . ' – ' . e(mb_strimwidth((string) $row['subject'], 0, 70, '…')) . '</li>';
$mail = $pdo->prepare('SELECT email FROM staff WHERE id = ?');
$mail->execute([$to]);
$addr = (string) $mail->fetchColumn();
if ($addr !== '') send_mail($addr, 'Vertretung für ' . $name . ': ' . count($tickets) . ' Tickets', email_wrap(
'<h2 style="margin:0 0 14px;color:#0f172a;font-size:22px;line-height:1.3;font-weight:700;font-family:Segoe UI,Arial,sans-serif;">Sie vertreten ' . e($name) . '</h2>'
. '<p style="margin:0 0 12px;color:#334155">' . count($tickets) . ' offene Tickets wurden Ihnen zugewiesen. Neue Tickets von ' . e($name) . ' kommen ebenfalls zu Ihnen, solange die Abwesenheit dauert.</p>'
. '<ul style="color:#334155">' . $list . '</ul>'));
}
return count($tickets);
}
/** Gibt weitergegebene Tickets zurück, die noch bei der Vertretung liegen und offen sind. Liefert die Zahl der zurückgegebenen Tickets. */
function vacation_take_back(PDO $pdo, int $staffId): int
{
if (vacation_plugin_owns($pdo, $staffId)) return 0;
$m = $pdo->prepare('SELECT m.id, m.ticket_id, m.to_staff_id, t.assigned_staff_id, t.status FROM vacation_moves m JOIN tickets t ON t.id = m.ticket_id WHERE m.from_staff_id = ? AND m.returned_at IS NULL ORDER BY m.id');
$m->execute([$staffId]);
$back = 0;
$name = (string) $pdo->query('SELECT name FROM staff WHERE id = ' . $staffId)->fetchColumn();
foreach ($m->fetchAll() as $row) {
$stillThere = (int) $row['assigned_staff_id'] === (int) $row['to_staff_id'] && !in_array($row['status'], ['resolved', 'closed'], true);
if ($stillThere) {
$pdo->prepare('UPDATE tickets SET assigned_staff_id = ? WHERE id = ?')->execute([$staffId, $row['ticket_id']]);
log_activity($pdo, (int) $row['ticket_id'], $staffId, 'Zurück an ' . $name . ' (Abwesenheit beendet)');
$back++;
}
$pdo->prepare('UPDATE vacation_moves SET returned_at = ? WHERE id = ?')->execute([db_now(), $row['id']]);
}
return $back;
}
/**
* Wird nach dem Speichern der Abwesenheit im Profil aufgerufen. $before/$after: [is_on_vacation, vacation_substitute_id].
* Beim Speichern mit Haken: weitergeben (auch erneut, es zählen nur Tickets, die noch beim Abwesenden liegen); Ende → zurückgeben; neue Vertretung → erst zurück, dann an die neue.
* Liefert [weitergegeben, zurückgegeben].
*/
function vacation_apply_change(PDO $pdo, int $staffId, array $before, array $after, bool $handOver): array
{
$handed = $back = 0;
$wasOn = (int) $before[0] === 1;
$isOn = (int) $after[0] === 1;
$changedSub = (int) ($before[1] ?? 0) !== (int) ($after[1] ?? 0);
if ($wasOn && (!$isOn || $changedSub)) $back = vacation_take_back($pdo, $staffId);
if ($isOn && $handOver) $handed = vacation_hand_over($pdo, $staffId); // idempotent: nur Tickets, die noch beim Abwesenden liegen
return [$handed, $back];
}
/**
* Ersetzt Mitarbeiter im Urlaub durch ihre Vertretung (folgt Ketten), damit Benachrichtigungen dort ankommen, wo jemand arbeitet.
* Ohne nutzbare Vertretung entfällt der Eintrag. Ergebnis: eindeutige Mitarbeiter-IDs, die aktiv und anwesend sind.
* @param int[] $ids
* @return int[]
*/
function vacation_expand_ids(PDO $pdo, array $ids): array
{
$out = [];
$ids = array_values(array_filter(array_unique(array_map('intval', $ids)), fn($id) => $id > 0));
if (!$ids) return [];
// Wer im Urlaub ist, in einer Abfrage ermitteln (statt eine je Mitarbeiter)
$inIds = implode(',', array_fill(0, count($ids), '?'));
$away = $pdo->prepare("SELECT id FROM staff WHERE is_on_vacation = 1 AND id IN ($inIds)");
$away->execute($ids);
$awayIds = array_flip(array_map('intval', $away->fetchAll(PDO::FETCH_COLUMN)));
foreach ($ids as $id) {
$target = isset($awayIds[$id]) ? vacation_target($pdo, $id) : $id;
if ($target !== null) $out[$target] = $target;
}
if (!$out) return [];
$in = implode(',', array_fill(0, count($out), '?'));
$q = $pdo->prepare("SELECT id FROM staff WHERE is_active = 1 AND is_on_vacation = 0 AND id IN ($in)");
$q->execute(array_values($out));
return array_map('intval', $q->fetchAll(PDO::FETCH_COLUMN));
}
/** E-Mail-Adressen der Mitarbeiter-IDs (nur aktive, anwesende). @param int[] $ids @return string[] */
function staff_emails_by_ids(PDO $pdo, array $ids): array
{
$ids = array_values(array_unique(array_map('intval', $ids)));
if (!$ids) return [];
$in = implode(',', array_fill(0, count($ids), '?'));
$q = $pdo->prepare("SELECT DISTINCT email FROM staff WHERE is_active = 1 AND is_on_vacation = 0 AND id IN ($in)");
$q->execute($ids);
return array_map('strval', $q->fetchAll(PDO::FETCH_COLUMN));
}