Upload via GUI (94 Dateien)
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
// Urlaub und Vertretung. Alle Funktionen beginnen mit vertretung_.
|
||||
// Baut auf dem Urlaubsstatus im Profil auf (staff.is_on_vacation / vacation_substitute_id): Die automatische Zuweisung der Abteilungen
|
||||
// und der E-Mail-Abruf beachten ihn bereits. Dieses Plugin setzt ihn zum Beginn und nimmt ihn zum Ende zurück.
|
||||
|
||||
const VERTRETUNG = 'vertretung';
|
||||
|
||||
function vertretung_today(): string
|
||||
{
|
||||
return (string) ($GLOBALS['vertretung_today'] ?? date('Y-m-d'));
|
||||
}
|
||||
|
||||
function vertretung_status_labels(): array
|
||||
{
|
||||
return ['planned' => 'Geplant', 'active' => 'Läuft', 'done' => 'Beendet', 'cancelled' => 'Abgebrochen', 'missed' => 'Nicht angetreten'];
|
||||
}
|
||||
|
||||
function vertretung_staff(PDO $pdo, int $id): ?array
|
||||
{
|
||||
$s = $pdo->prepare('SELECT id, name, email, is_active, is_on_vacation, vacation_substitute_id FROM staff WHERE id = ?');
|
||||
$s->execute([$id]);
|
||||
return $s->fetch() ?: null;
|
||||
}
|
||||
|
||||
function vertretung_name(PDO $pdo, int $id): string
|
||||
{
|
||||
return (string) (vertretung_staff($pdo, $id)['name'] ?? ('#' . $id));
|
||||
}
|
||||
|
||||
function vertretung_can_manage(array $actor, int $staffId): bool
|
||||
{
|
||||
return ($actor['role'] ?? '') === 'admin' || (int) ($actor['id'] ?? 0) === $staffId;
|
||||
}
|
||||
|
||||
function vertretung_mail(PDO $pdo, int $staffId, string $subject, string $html): void
|
||||
{
|
||||
$st = vertretung_staff($pdo, $staffId);
|
||||
if (!$st || !(int) $st['is_active'] || trim((string) $st['email']) === '') return;
|
||||
try {
|
||||
send_mail((string) $st['email'], $subject, $html);
|
||||
} catch (Throwable $e) {
|
||||
error_log('vertretung mail: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wer arbeitet an Stelle von $staffId? Folgt Vertretungsketten (höchstens drei Stufen, ohne Schleifen). null, wenn die Person
|
||||
* anwesend ist oder keine nutzbare Vertretung hat.
|
||||
*/
|
||||
function vertretung_target(PDO $pdo, int $staffId): ?int
|
||||
{
|
||||
$row = vertretung_staff($pdo, $staffId);
|
||||
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;
|
||||
$row = vertretung_staff($pdo, $sub);
|
||||
if (!$row || !(int) $row['is_active']) return null;
|
||||
if (!(int) $row['is_on_vacation']) return $sub;
|
||||
$seen[] = $sub;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Gibt ein Ticket an $to weiter und merkt es für die Rückgabe. */
|
||||
function vertretung_move(PDO $pdo, int $ticketId, int $from, int $to, int $absenceId): void
|
||||
{
|
||||
$pdo->prepare('UPDATE tickets SET assigned_staff_id = ? WHERE id = ?')->execute([$to, $ticketId]);
|
||||
log_activity($pdo, $ticketId, null, 'An die Vertretung ' . vertretung_name($pdo, $to) . ' weitergegeben (' . vertretung_name($pdo, $from) . ' ist abwesend)');
|
||||
$pdo->prepare('INSERT INTO plugin_vertretung_moves (absence_id, ticket_id, from_staff_id, to_staff_id, created_at) VALUES (?,?,?,?,?)')
|
||||
->execute([$absenceId, $ticketId, $from, $to, db_now()]);
|
||||
}
|
||||
|
||||
/** Hook: Ticket gehört einer abwesenden Person → an die Vertretung. */
|
||||
function vertretung_redirect(PDO $pdo, int $ticketId): void
|
||||
{
|
||||
$t = $pdo->prepare('SELECT assigned_staff_id, status, is_spam FROM tickets WHERE id = ?');
|
||||
$t->execute([$ticketId]);
|
||||
$ticket = $t->fetch();
|
||||
if (!$ticket || !$ticket['assigned_staff_id'] || !empty($ticket['is_spam']) || in_array($ticket['status'], ['resolved', 'closed'], true)) return;
|
||||
$from = (int) $ticket['assigned_staff_id'];
|
||||
$to = vertretung_target($pdo, $from);
|
||||
if ($to === null || $to === $from) return;
|
||||
$a = $pdo->prepare("SELECT id FROM plugin_vertretung_absences WHERE staff_id = ? AND status = 'active' ORDER BY id DESC LIMIT 1");
|
||||
$a->execute([$from]);
|
||||
vertretung_move($pdo, $ticketId, $from, $to, (int) $a->fetchColumn());
|
||||
}
|
||||
|
||||
/** Prüft Eingaben für eine neue Abwesenheit. Liefert eine Fehlermeldung oder null. */
|
||||
function vertretung_validate(PDO $pdo, int $staffId, int $subId, string $start, string $end): ?string
|
||||
{
|
||||
$okDate = static fn(string $d) => preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $d, $m) === 1 && checkdate((int) $m[2], (int) $m[3], (int) $m[1]);
|
||||
if (!$okDate($start) || !$okDate($end)) return 'Bitte Beginn und Ende als Datum angeben.';
|
||||
if ($end < $start) return 'Das Ende darf nicht vor dem Beginn liegen.';
|
||||
if ($end < vertretung_today()) return 'Der Zeitraum liegt schon in der Vergangenheit.';
|
||||
$st = vertretung_staff($pdo, $staffId);
|
||||
if (!$st || !(int) $st['is_active']) return 'Dieser Mitarbeiter ist nicht aktiv.';
|
||||
$sub = vertretung_staff($pdo, $subId);
|
||||
if (!$sub || !(int) $sub['is_active']) return 'Bitte eine aktive Vertretung wählen.';
|
||||
if ($subId === $staffId) return 'Die Vertretung muss eine andere Person sein.';
|
||||
$o = $pdo->prepare("SELECT COUNT(*) FROM plugin_vertretung_absences WHERE staff_id = ? AND status IN ('planned','active') AND start_date <= ? AND end_date >= ?");
|
||||
$o->execute([$staffId, $end, $start]);
|
||||
if ((int) $o->fetchColumn() > 0) return 'In diesem Zeitraum ist für diese Person schon eine Abwesenheit eingetragen.';
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return array{ok:bool,error?:string,id?:int} */
|
||||
function vertretung_create(PDO $pdo, array $actor, int $staffId, int $subId, string $start, string $end, string $note): array
|
||||
{
|
||||
if (!vertretung_can_manage($actor, $staffId)) return ['ok' => false, 'error' => 'Sie dürfen nur Ihre eigene Abwesenheit eintragen.'];
|
||||
$err = vertretung_validate($pdo, $staffId, $subId, $start, $end);
|
||||
if ($err !== null) return ['ok' => false, 'error' => $err];
|
||||
$pdo->prepare("INSERT INTO plugin_vertretung_absences (staff_id, substitute_id, start_date, end_date, note, status, created_by, created_at) VALUES (?,?,?,?,?,'planned',?,?)")
|
||||
->execute([$staffId, $subId, $start, $end, mb_substr(trim($note), 0, 250), (int) ($actor['id'] ?? 0) ?: null, db_now()]);
|
||||
$id = (int) $pdo->lastInsertId();
|
||||
if ($start <= vertretung_today()) vertretung_tick($pdo); // beginnt sofort
|
||||
return ['ok' => true, 'id' => $id];
|
||||
}
|
||||
|
||||
function vertretung_cancel(PDO $pdo, array $actor, int $id): bool
|
||||
{
|
||||
$s = $pdo->prepare("SELECT * FROM plugin_vertretung_absences WHERE id = ? AND status IN ('planned','active')");
|
||||
$s->execute([$id]);
|
||||
$abs = $s->fetch();
|
||||
if (!$abs || !vertretung_can_manage($actor, (int) $abs['staff_id'])) return false;
|
||||
if ($abs['status'] === 'active') vertretung_deactivate($pdo, $abs, 'cancelled');
|
||||
else $pdo->prepare("UPDATE plugin_vertretung_absences SET status = 'cancelled' WHERE id = ?")->execute([$id]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Beginn: Urlaubsstatus setzen und offene Tickets an die Vertretung verteilen. Liefert die Zahl der weitergegebenen Tickets. */
|
||||
function vertretung_activate(PDO $pdo, array $abs): int
|
||||
{
|
||||
$staffId = (int) $abs['staff_id'];
|
||||
$pdo->prepare("UPDATE plugin_vertretung_absences SET status = 'active' WHERE id = ?")->execute([$abs['id']]);
|
||||
$pdo->prepare('UPDATE staff SET is_on_vacation = 1, vacation_substitute_id = ? WHERE id = ?')->execute([$abs['substitute_id'], $staffId]);
|
||||
$to = vertretung_target($pdo, $staffId);
|
||||
if ($to === null) return 0;
|
||||
$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();
|
||||
foreach ($tickets as $row) vertretung_move($pdo, (int) $row['id'], $staffId, $to, (int) $abs['id']);
|
||||
$name = vertretung_name($pdo, $staffId);
|
||||
$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>';
|
||||
vertretung_mail($pdo, $to, 'Vertretung für ' . $name . ': ' . count($tickets) . ' Tickets', '<p>Sie vertreten <strong>' . e($name) . '</strong> vom ' . e(vertretung_date((string) $abs['start_date'])) . ' bis ' . e(vertretung_date((string) $abs['end_date'])) . '.</p>'
|
||||
. '<p>' . count($tickets) . ' offene Tickets wurden Ihnen zugewiesen, neue Tickets von ' . e($name) . ' kommen ebenfalls zu Ihnen.</p>' . ($list ? '<ul>' . $list . '</ul>' : ''));
|
||||
return count($tickets);
|
||||
}
|
||||
|
||||
/** Ende oder Abbruch: Urlaubsstatus zurücknehmen und noch offene Tickets zurückgeben. Liefert die Zahl der zurückgegebenen Tickets. */
|
||||
function vertretung_deactivate(PDO $pdo, array $abs, string $newStatus): int
|
||||
{
|
||||
$staffId = (int) $abs['staff_id'];
|
||||
$pdo->prepare("UPDATE plugin_vertretung_absences SET status = ? WHERE id = ?")->execute([$newStatus, $abs['id']]);
|
||||
// nur zurücknehmen, was dieses Plugin gesetzt hat (hat jemand die Vertretung im Profil geändert, bleibt es so)
|
||||
$pdo->prepare('UPDATE staff SET is_on_vacation = 0, vacation_substitute_id = NULL WHERE id = ? AND is_on_vacation = 1 AND vacation_substitute_id = ?')->execute([$staffId, $abs['substitute_id']]);
|
||||
$m = $pdo->prepare('SELECT m.id, m.ticket_id, m.to_staff_id, t.assigned_staff_id, t.status, t.ticket_number, t.subject FROM plugin_vertretung_moves m JOIN tickets t ON t.id = m.ticket_id WHERE m.absence_id = ? AND m.returned_at IS NULL ORDER BY m.id');
|
||||
$m->execute([$abs['id']]);
|
||||
$back = [];
|
||||
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'], null, 'Zurück an ' . vertretung_name($pdo, $staffId) . ' (Abwesenheit beendet)');
|
||||
$back[] = $row;
|
||||
}
|
||||
$pdo->prepare('UPDATE plugin_vertretung_moves SET returned_at = ? WHERE id = ?')->execute([db_now(), $row['id']]);
|
||||
}
|
||||
if ($back) {
|
||||
$list = '';
|
||||
foreach (array_slice($back, 0, 15) as $row) $list .= '<li>#' . e((string) $row['ticket_number']) . ' – ' . e(mb_strimwidth((string) $row['subject'], 0, 70, '…')) . '</li>';
|
||||
vertretung_mail($pdo, $staffId, 'Willkommen zurück: ' . count($back) . ' Tickets', '<p>Ihre Abwesenheit ist beendet. Diese Tickets sind wieder bei Ihnen:</p><ul>' . $list . '</ul>');
|
||||
}
|
||||
return count($back);
|
||||
}
|
||||
|
||||
/** Cron: abgelaufene Abwesenheiten beenden, fällige beginnen. */
|
||||
function vertretung_tick(PDO $pdo): void
|
||||
{
|
||||
$today = vertretung_today();
|
||||
$q = $pdo->prepare("SELECT * FROM plugin_vertretung_absences WHERE status = 'active' AND end_date < ?");
|
||||
$q->execute([$today]);
|
||||
foreach ($q->fetchAll() as $abs) vertretung_deactivate($pdo, $abs, 'done');
|
||||
$pdo->prepare("UPDATE plugin_vertretung_absences SET status = 'missed' WHERE status = 'planned' AND end_date < ?")->execute([$today]);
|
||||
$q = $pdo->prepare("SELECT * FROM plugin_vertretung_absences WHERE status = 'planned' AND start_date <= ? AND end_date >= ? ORDER BY start_date, id");
|
||||
$q->execute([$today, $today]);
|
||||
foreach ($q->fetchAll() as $abs) vertretung_activate($pdo, $abs);
|
||||
vertretung_rebalance($pdo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ist die erste Vertretung zurück (oder hat sich die Kette geändert), wandern die für die Abwesende weitergegebenen Tickets,
|
||||
* die noch unberührt bei der bisherigen Vertretung liegen, zur jetzt zuständigen Person.
|
||||
*/
|
||||
function vertretung_rebalance(PDO $pdo): void
|
||||
{
|
||||
foreach ($pdo->query("SELECT id, staff_id FROM plugin_vertretung_absences WHERE status = 'active'")->fetchAll() as $abs) {
|
||||
$target = vertretung_target($pdo, (int) $abs['staff_id']);
|
||||
if ($target === null) continue;
|
||||
$m = $pdo->prepare('SELECT m.id, m.ticket_id, m.to_staff_id FROM plugin_vertretung_moves m JOIN tickets t ON t.id = m.ticket_id WHERE m.absence_id = ? AND m.returned_at IS NULL AND m.to_staff_id <> ? AND t.assigned_staff_id = m.to_staff_id');
|
||||
$m->execute([$abs['id'], $target]);
|
||||
foreach ($m->fetchAll() as $row) {
|
||||
$t = $pdo->prepare('SELECT status FROM tickets WHERE id = ?');
|
||||
$t->execute([$row['ticket_id']]);
|
||||
if (in_array($t->fetchColumn(), ['resolved', 'closed'], true)) continue;
|
||||
$pdo->prepare('UPDATE tickets SET assigned_staff_id = ? WHERE id = ?')->execute([$target, $row['ticket_id']]);
|
||||
$pdo->prepare('UPDATE plugin_vertretung_moves SET to_staff_id = ? WHERE id = ?')->execute([$target, $row['id']]);
|
||||
log_activity($pdo, (int) $row['ticket_id'], null, 'An die Vertretung ' . vertretung_name($pdo, $target) . ' weitergegeben (Vertretung ist zurück)');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function vertretung_date(string $ymd): string
|
||||
{
|
||||
$t = strtotime($ymd . ' 12:00:00');
|
||||
return $t ? date('d.m.Y', $t) : $ymd;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS plugin_vertretung_absences (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
staff_id INT NOT NULL,
|
||||
substitute_id INT NOT NULL,
|
||||
start_date DATE NOT NULL,
|
||||
end_date DATE NOT NULL,
|
||||
note VARCHAR(255) NOT NULL DEFAULT '',
|
||||
status VARCHAR(12) NOT NULL DEFAULT 'planned',
|
||||
created_by INT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
KEY idx_plugin_vertretung_status (status, start_date)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
CREATE TABLE IF NOT EXISTS plugin_vertretung_moves (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
absence_id INT NOT NULL,
|
||||
ticket_id INT NOT NULL,
|
||||
from_staff_id INT NOT NULL,
|
||||
to_staff_id INT NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
returned_at DATETIME NULL,
|
||||
KEY idx_plugin_vertretung_moves_absence (absence_id, returned_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
||||
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE IF NOT EXISTS plugin_vertretung_absences (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
staff_id INTEGER NOT NULL,
|
||||
substitute_id INTEGER NOT NULL,
|
||||
start_date TEXT NOT NULL,
|
||||
end_date TEXT NOT NULL,
|
||||
note TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL DEFAULT 'planned',
|
||||
created_by INTEGER NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_plugin_vertretung_status ON plugin_vertretung_absences (status, start_date);
|
||||
CREATE TABLE IF NOT EXISTS plugin_vertretung_moves (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
absence_id INTEGER NOT NULL,
|
||||
ticket_id INTEGER NOT NULL,
|
||||
from_staff_id INTEGER NOT NULL,
|
||||
to_staff_id INTEGER NOT NULL,
|
||||
created_at TEXT NOT NULL,
|
||||
returned_at TEXT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_plugin_vertretung_moves_absence ON plugin_vertretung_moves (absence_id, returned_at)
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
// Seite des Vertretungs-Plugins. Verfügbar: $staff, $pdo.
|
||||
$isAdmin = ($staff['role'] ?? '') === 'admin';
|
||||
$message = null;
|
||||
$error = null;
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
verify_csrf();
|
||||
$action = $_POST['action'] ?? '';
|
||||
if ($action === 'create') {
|
||||
$staffId = $isAdmin ? (int) ($_POST['staff_id'] ?? 0) : (int) $staff['id'];
|
||||
$r = vertretung_create($pdo, $staff, $staffId, (int) ($_POST['substitute_id'] ?? 0), (string) ($_POST['start_date'] ?? ''), (string) ($_POST['end_date'] ?? ''), (string) ($_POST['note'] ?? ''));
|
||||
if ($r['ok']) $message = 'Die Abwesenheit ist eingetragen.'; else $error = $r['error'];
|
||||
} elseif ($action === 'cancel') {
|
||||
if (vertretung_cancel($pdo, $staff, (int) ($_POST['id'] ?? 0))) $message = 'Die Abwesenheit wurde beendet, offene Tickets sind wieder bei der ursprünglichen Person.';
|
||||
else $error = 'Diese Abwesenheit kann nicht beendet werden.';
|
||||
}
|
||||
}
|
||||
|
||||
$people = $pdo->query('SELECT id, name FROM staff WHERE is_active = 1 ORDER BY name')->fetchAll();
|
||||
$labels = vertretung_status_labels();
|
||||
$names = [];
|
||||
foreach ($people as $p) $names[(int) $p['id']] = $p['name'];
|
||||
$name = fn($id) => e($names[(int) $id] ?? vertretung_name($pdo, (int) $id));
|
||||
$current = $pdo->query("SELECT a.*, (SELECT COUNT(*) FROM plugin_vertretung_moves m WHERE m.absence_id = a.id) AS moved, (SELECT COUNT(*) FROM plugin_vertretung_moves m WHERE m.absence_id = a.id AND m.returned_at IS NULL) AS holding FROM plugin_vertretung_absences a WHERE a.status IN ('planned','active') ORDER BY a.start_date, a.id")->fetchAll();
|
||||
$past = $pdo->query("SELECT a.*, (SELECT COUNT(*) FROM plugin_vertretung_moves m WHERE m.absence_id = a.id) AS moved FROM plugin_vertretung_absences a WHERE a.status NOT IN ('planned','active') ORDER BY a.id DESC LIMIT 20")->fetchAll();
|
||||
?>
|
||||
<h1>Abwesenheit und Vertretung</h1>
|
||||
<?php if ($message): ?><div class="flash success"><?= e($message) ?></div><?php endif; ?>
|
||||
<?php if ($error): ?><div class="flash error"><?= e($error) ?></div><?php endif; ?>
|
||||
|
||||
<form method="post" class="card" style="max-width:900px;margin-bottom:16px">
|
||||
<?= csrf_field() ?>
|
||||
<input type="hidden" name="action" value="create">
|
||||
<h3>Abwesenheit eintragen</h3>
|
||||
<p class="muted" style="font-size:.85rem">Zum Beginn gehen Ihre offenen Tickets an die Vertretung, neue Tickets ebenfalls (auch bei automatischer Zuweisung). Am Ende bekommen Sie die Tickets zurück, die dann noch bei der Vertretung liegen. Der Urlaubsstatus im Profil wird dafür automatisch gesetzt.</p>
|
||||
<div style="display:flex;gap:14px;flex-wrap:wrap">
|
||||
<?php if ($isAdmin): ?>
|
||||
<div><label>Wer ist abwesend?</label><select name="staff_id"><?php foreach ($people as $p): ?><option value="<?= (int) $p['id'] ?>" <?= (int) $p['id'] === (int) $staff['id'] ? 'selected' : '' ?>><?= e($p['name']) ?></option><?php endforeach; ?></select></div>
|
||||
<?php endif; ?>
|
||||
<div><label>Vertretung</label><select name="substitute_id" required><option value="">– wählen –</option><?php foreach ($people as $p): if ((int) $p['id'] === (int) $staff['id'] && !$isAdmin) continue; ?><option value="<?= (int) $p['id'] ?>"><?= e($p['name']) ?></option><?php endforeach; ?></select></div>
|
||||
<div><label>Von</label><input type="date" name="start_date" value="<?= e(vertretung_today()) ?>" required></div>
|
||||
<div><label>Bis (einschließlich)</label><input type="date" name="end_date" required></div>
|
||||
</div>
|
||||
<label style="margin-top:8px">Notiz (optional)</label><input type="text" name="note" maxlength="250" placeholder="zum Beispiel Sommerurlaub">
|
||||
<div class="btn-row"><button type="submit" class="btn">Eintragen</button></div>
|
||||
</form>
|
||||
|
||||
<div class="card" style="margin-bottom:16px">
|
||||
<h3>Laufend und geplant</h3>
|
||||
<?php if (!$current): ?><p class="muted">Keine Abwesenheiten eingetragen.</p><?php else: ?>
|
||||
<table style="width:100%">
|
||||
<thead><tr><th>Wer</th><th>Vertretung</th><th>Zeitraum</th><th>Stand</th><th>Tickets</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($current as $a): ?>
|
||||
<tr>
|
||||
<td><?= $name($a['staff_id']) ?><?php if ($a['note']): ?><div class="muted" style="font-size:.76rem"><?= e((string) $a['note']) ?></div><?php endif; ?></td>
|
||||
<td><?= $name($a['substitute_id']) ?></td>
|
||||
<td><?= e(vertretung_date((string) $a['start_date'])) ?> – <?= e(vertretung_date((string) $a['end_date'])) ?></td>
|
||||
<td><?= e($labels[$a['status']] ?? $a['status']) ?></td>
|
||||
<td><?= $a['status'] === 'active' ? (int) $a['holding'] . ' bei der Vertretung' : '–' ?></td>
|
||||
<td>
|
||||
<?php if (vertretung_can_manage($staff, (int) $a['staff_id'])): ?>
|
||||
<form method="post" style="display:inline" onsubmit="return confirm('Abwesenheit beenden? Offene Tickets gehen zurück.')"><?= csrf_field() ?><input type="hidden" name="action" value="cancel"><input type="hidden" name="id" value="<?= (int) $a['id'] ?>"><button class="btn secondary" type="submit"><?= $a['status'] === 'active' ? 'Jetzt beenden' : 'Löschen' ?></button></form>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($past): ?>
|
||||
<div class="card">
|
||||
<h3>Frühere Abwesenheiten</h3>
|
||||
<table style="width:100%">
|
||||
<thead><tr><th>Wer</th><th>Vertretung</th><th>Zeitraum</th><th>Ergebnis</th><th>Weitergegeben</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach ($past as $a): ?>
|
||||
<tr><td><?= $name($a['staff_id']) ?></td><td><?= $name($a['substitute_id']) ?></td><td><?= e(vertretung_date((string) $a['start_date'])) ?> – <?= e(vertretung_date((string) $a['end_date'])) ?></td><td><?= e($labels[$a['status']] ?? $a['status']) ?></td><td><?= (int) $a['moved'] ?> Tickets</td></tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"slug": "vertretung",
|
||||
"name": "Urlaub und Vertretung",
|
||||
"version": "1.0.0",
|
||||
"description": "Mitarbeiter tragen Abwesenheiten mit Vertretung ein. Zum Beginn werden ihre offenen Tickets an die Vertretung verteilt, neue Tickets gehen automatisch dorthin, und am Ende bekommt der Mitarbeiter seine noch offenen Tickets zurück. Baut auf dem Urlaubsstatus im Profil und auf der automatischen Zuweisung der Abteilungen auf.",
|
||||
"author": "DeskHub",
|
||||
"requires": "1.0.0",
|
||||
"permissions": ["hooks.tickets", "hooks.cron", "ui.menu", "db.migrations"]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
// Urlaub und Vertretung: Zeiträume für Abwesenheiten, Umverteilung und Rückgabe von Tickets.
|
||||
require_once __DIR__ . '/lib.php';
|
||||
|
||||
plugin_add_hook('cron.run', function () {
|
||||
vertretung_tick(get_db());
|
||||
});
|
||||
// Ticket ist (von Regel, Hand oder API) einer abwesenden Person zugewiesen → an die Vertretung weitergeben
|
||||
foreach (['ticket.created', 'ticket.assigned'] as $hook) {
|
||||
plugin_add_hook($hook, function (array $payload) {
|
||||
if (!empty($payload['ticketId'])) vertretung_redirect(get_db(), (int) $payload['ticketId']);
|
||||
});
|
||||
}
|
||||
|
||||
// Jeder Mitarbeiter pflegt seine eigene Abwesenheit, Administratoren alle
|
||||
plugin_register_menu('Abwesenheit', 'vertretung');
|
||||
Reference in New Issue
Block a user