Upload via GUI (340 Dateien)

This commit is contained in:
2026-10-26 21:19:14 +00:00
parent a7aeec1b26
commit 2bc706cb09
6 changed files with 273 additions and 54 deletions
+39 -9
View File
@@ -10,6 +10,7 @@ $priorities = $pdo->query('SELECT * FROM priorities ORDER BY sort_order')->fetc
$agents = $pdo->query('SELECT id, name FROM staff WHERE is_active = 1 ORDER BY name')->fetchAll();
$bulkErrors = [];
$resolutionReasons = ticket_resolution_reasons();
// ---------------------------------------------------------------
// Gespeicherte Ansichten: speichern / löschen
@@ -60,6 +61,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['bulk_action'] ?? '') !== '
if (count($allowedTickets) < count($ticketIds)) {
$bulkErrors[] = 'Einige ausgewählte Tickets gehören zu Abteilungen, auf die Sie keinen Zugriff haben, und wurden übersprungen.';
}
$bulkResolutionCode = (string) ($_POST['bulk_resolution_code'] ?? '');
$bulkResolutionNote = trim(mb_substr((string) ($_POST['bulk_resolution_note'] ?? ''), 0, 2000));
if ($bulkAction === 'status' && in_array((string) ($_POST['bulk_status'] ?? ''), ['resolved', 'closed'], true) && !isset($resolutionReasons[$bulkResolutionCode])) {
$bulkErrors[] = 'Bitte für das Schließen einen Abschlussgrund auswählen.';
$allowedTickets = [];
}
$now = db_now();
// Abteilung wechseln: Ziel prüfen (muss existieren und für den Mitarbeiter erreichbar sein), Rechte je Ticket später
@@ -88,6 +95,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['bulk_action'] ?? '') !== '
$prioStmt->execute([$t['priority_id']]);
$prioRow = $prioStmt->fetch();
$sla = $prioRow ? calc_sla_dates($prioRow, $t['created_at'], (int) $moveTarget['id'], !empty($t['help_topic_id']) ? (int) $t['help_topic_id'] : null) : null;
if ($sla) {
$priorPauses = ticket_sla_completed_pause_seconds($pdo, (int) $t['id']);
foreach (['first_response_due_at', 'resolution_due_at'] as $deadline) if (!empty($sla[$deadline]) && $priorPauses > 0) $sla[$deadline] = ticket_sla_shift_deadline($sla[$deadline], $priorPauses);
if (!empty($sla['first_response_due_at'])) ticket_sla_history_add($pdo, (int) $t['id'], 'adjusted', 'first_response', 'Neue Frist nach Abteilungswechsel: ' . $sla['first_response_due_at'], (int) $staff['id']);
if (!empty($sla['resolution_due_at'])) ticket_sla_history_add($pdo, (int) $t['id'], 'adjusted', 'resolution', 'Neue Frist nach Abteilungswechsel: ' . $sla['resolution_due_at'], (int) $staff['id']);
}
$pdo->prepare('UPDATE tickets SET department_id=?, first_response_due_at=?, resolution_due_at=?, updated_at=? WHERE id=?')
->execute([(int) $moveTarget['id'], $sla['first_response_due_at'] ?? $t['first_response_due_at'], $sla['resolution_due_at'] ?? $t['resolution_due_at'], $now, $t['id']]);
$oldDeptName = '';
@@ -103,14 +116,25 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['bulk_action'] ?? '') !== '
log_activity($pdo, $t['id'], $staff['id'], 'Sammelaktion: Mitarbeiter zugewiesen');
} elseif ($bulkAction === 'priority') {
$newPrio = (int) $_POST['bulk_priority_id'];
$pdo->prepare('UPDATE tickets SET priority_id=?, updated_at=? WHERE id=?')->execute([$newPrio, $now, $t['id']]);
$prioStmt = $pdo->prepare('SELECT * FROM priorities WHERE id = ?');
$prioStmt->execute([$newPrio]); $newPriority = $prioStmt->fetch();
$sla = $newPriority ? calc_sla_dates($newPriority, $t['created_at'], (int) $t['department_id'], !empty($t['help_topic_id']) ? (int) $t['help_topic_id'] : null) : null;
if ($sla) {
$priorPauses = ticket_sla_completed_pause_seconds($pdo, (int) $t['id']);
foreach (['first_response_due_at', 'resolution_due_at'] as $deadline) if (!empty($sla[$deadline]) && $priorPauses > 0) $sla[$deadline] = ticket_sla_shift_deadline($sla[$deadline], $priorPauses);
ticket_sla_history_add($pdo, (int) $t['id'], 'adjusted', 'first_response', 'Neue Frist nach Prioritaetswechsel: ' . $sla['first_response_due_at'], (int) $staff['id']);
ticket_sla_history_add($pdo, (int) $t['id'], 'adjusted', 'resolution', 'Neue Frist nach Prioritaetswechsel: ' . $sla['resolution_due_at'], (int) $staff['id']);
}
$pdo->prepare('UPDATE tickets SET priority_id=?, first_response_due_at=?, resolution_due_at=?, updated_at=? WHERE id=?')
->execute([$newPrio, $sla['first_response_due_at'] ?? $t['first_response_due_at'], $sla['resolution_due_at'] ?? $t['resolution_due_at'], $now, $t['id']]);
log_activity($pdo, $t['id'], $staff['id'], 'Sammelaktion: Priorität geändert');
} elseif ($bulkAction === 'status') {
$newStatus = $_POST['bulk_status'];
$resolvedAt = $newStatus === 'resolved' ? $now : $t['resolved_at'];
$closedAt = $newStatus === 'closed' ? $now : $t['closed_at'];
$pdo->prepare('UPDATE tickets SET status=?, resolved_at=?, closed_at=?, updated_at=? WHERE id=?')
->execute([$newStatus, $resolvedAt, $closedAt, $now, $t['id']]);
$pdo->prepare('UPDATE tickets SET status=?, resolved_at=?, closed_at=?, resolution_code=?, resolution_note=?, updated_at=? WHERE id=?')
->execute([$newStatus, $resolvedAt, $closedAt, in_array($newStatus, ['resolved', 'closed'], true) ? $bulkResolutionCode : $t['resolution_code'], in_array($newStatus, ['resolved', 'closed'], true) ? ($bulkResolutionNote ?: $t['resolution_note']) : $t['resolution_note'], $now, $t['id']]);
ticket_sla_status_transition($pdo, (int) $t['id'], (string) $t['status'], (string) $newStatus, (int) $staff['id']);
log_activity($pdo, $t['id'], $staff['id'], 'Sammelaktion: Status geändert');
} elseif ($bulkAction === 'delete') {
delete_ticket($pdo, $t, $staff['id']);
@@ -118,6 +142,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['bulk_action'] ?? '') !== '
$email = $t['requester_email'];
$domain = $email ? substr($email, strpos($email,'@')+1) : '';
if ($email) {
$priorStmt = $pdo->prepare("SELECT id,status FROM tickets WHERE requester_email=? AND status NOT IN ('closed')");
$priorStmt->execute([$email]); $priorTickets = $priorStmt->fetchAll();
$chk = $pdo->prepare('SELECT id FROM banlist WHERE value=?');
$chk->execute([$email]);
if (!$chk->fetchColumn()) {
@@ -125,8 +151,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['bulk_action'] ?? '') !== '
->execute(['email', $email, 'Bulk-Sperre', $staff['id'], $now]);
}
// Alle Tickets dieses Absenders schließen
$pdo->prepare("UPDATE tickets SET status='closed',closed_at=?,updated_at=? WHERE requester_email=? AND status NOT IN ('closed')")
$pdo->prepare("UPDATE tickets SET status='closed',resolution_code='spam',resolution_note='Absender im Rahmen einer Sammelaktion gesperrt',closed_at=?,updated_at=? WHERE requester_email=? AND status NOT IN ('closed')")
->execute([$now, $now, $email]);
ticket_sla_transition_rows($pdo, $priorTickets, 'closed', (int) $staff['id']);
}
}
}
@@ -178,7 +205,7 @@ if ($activeQuick === 'me') {
$where[] = 't.assigned_staff_id IS NULL';
$where[] = "t.status NOT IN ('resolved','closed')";
} elseif ($activeQuick === 'overdue') {
$where[] = 't.resolution_due_at < ?'; $params[] = db_now();
$where[] = '(t.is_overdue = 1 OR (t.sla_paused_at IS NULL AND t.resolution_due_at < ?))'; $params[] = db_now();
$where[] = "t.status NOT IN ('resolved','closed')";
} elseif ($activeQuick === 'highprio') {
$where[] = 't.priority_id IN (SELECT id FROM (SELECT id FROM priorities ORDER BY sort_order DESC LIMIT 2) AS top_priorities)';
@@ -203,13 +230,13 @@ if ($filterSla !== '') {
$now = db_now();
$riskThreshold = date('Y-m-d H:i:s', time() + 4 * 3600);
if ($filterSla === 'overdue') {
$where[] = "t.resolution_due_at IS NOT NULL AND t.resolution_due_at < ? AND t.status NOT IN ('resolved','closed')";
$where[] = "t.resolution_due_at IS NOT NULL AND (t.is_overdue = 1 OR (t.sla_paused_at IS NULL AND t.resolution_due_at < ?)) AND t.status NOT IN ('resolved','closed')";
$params[] = $now;
} elseif ($filterSla === 'at_risk') {
$where[] = "t.resolution_due_at IS NOT NULL AND t.resolution_due_at >= ? AND t.resolution_due_at < ? AND t.status NOT IN ('resolved','closed')";
$where[] = "t.sla_paused_at IS NULL AND t.resolution_due_at IS NOT NULL AND t.resolution_due_at >= ? AND t.resolution_due_at < ? AND t.status NOT IN ('resolved','closed')";
array_push($params, $now, $riskThreshold);
} elseif ($filterSla === 'on_time') {
$where[] = "(t.resolution_due_at IS NULL OR t.resolution_due_at >= ?) AND t.status NOT IN ('resolved','closed')";
$where[] = "t.sla_paused_at IS NULL AND (t.resolution_due_at IS NULL OR t.resolution_due_at >= ?) AND t.status NOT IN ('resolved','closed')";
$params[] = $riskThreshold;
}
}
@@ -307,7 +334,7 @@ $_stMe = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE assigned_staff_id=? A
$_stMe->execute([$staff['id']]);
$countMe = (int)$_stMe->fetchColumn();
$countUnassigned= (int)$pdo->query("SELECT COUNT(*) FROM tickets WHERE assigned_staff_id IS NULL AND status NOT IN ('resolved','closed')")->fetchColumn();
$_stOd = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE resolution_due_at < ? AND status NOT IN ('resolved','closed')");
$_stOd = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE (is_overdue = 1 OR (sla_paused_at IS NULL AND resolution_due_at < ?)) AND status NOT IN ('resolved','closed')");
$_stOd->execute([db_now()]);
$countOverdue = (int)$_stOd->fetchColumn();
$countHighPrio = (int)$pdo->query("SELECT COUNT(*) FROM tickets WHERE status NOT IN ('resolved','closed') AND priority_id IN (SELECT id FROM (SELECT id FROM priorities ORDER BY sort_order DESC LIMIT 2) AS top_priorities)")->fetchColumn();
@@ -602,6 +629,9 @@ document.getElementById('qf-panel')?.addEventListener('click', function(e){ e.st
<option value="<?= $val ?>"><?= $lbl ?></option>
<?php endforeach; ?>
</select>
<select name="bulk_resolution_code" aria-label="Abschlussgrund für Tickets">
<option value="">Abschlussgrund …</option><?php foreach ($resolutionReasons as $code => $label): ?><option value="<?= e($code) ?>"><?= e($label) ?></option><?php endforeach; ?>
</select>
<button type="button" class="btn secondary" style="white-space:nowrap" onclick="submitBulk('status')">Status setzen</button>
</div>