Upload via GUI (340 Dateien)
This commit is contained in:
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user