diff --git a/DeskHub-Webseite/staff/banlist.php b/DeskHub-Webseite/staff/banlist.php index 8f65b3ff..eb52f210 100644 --- a/DeskHub-Webseite/staff/banlist.php +++ b/DeskHub-Webseite/staff/banlist.php @@ -24,8 +24,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ->execute([$matchType, $value, $reason ?: null, $staff['id'], db_now()]); // Tickets schließen $col = $matchType === 'email' ? 'requester_email' : "" . sql_email_domain("requester_email") . ""; - $pdo->prepare("UPDATE tickets SET status='closed',closed_at=?,updated_at=? WHERE $col=? AND status NOT IN ('closed')") + $priorStmt = $pdo->prepare("SELECT id,status FROM tickets WHERE $col=? AND status NOT IN ('closed')"); + $priorStmt->execute([$value]); $priorTickets = $priorStmt->fetchAll(); + $pdo->prepare("UPDATE tickets SET status='closed',resolution_code='spam',resolution_note='Im Rahmen einer Sperre geschlossen',closed_at=?,updated_at=? WHERE $col=? AND status NOT IN ('closed')") ->execute([db_now(), db_now(), $value]); + ticket_sla_transition_rows($pdo, $priorTickets, 'closed', (int) $staff['id']); audit_log($staff['id'], "Sperrliste: $value hinzugefügt ($matchType)"); } flash('success', "$value gesperrt."); @@ -50,8 +53,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ->execute([$type, $val, $reason ?: null, $staff['id'], db_now()]); // Tickets schließen $col = $type === 'email' ? 'requester_email' : "" . sql_email_domain("requester_email") . ""; - $pdo->prepare("UPDATE tickets SET status='closed',closed_at=?,updated_at=? WHERE $col=? AND status NOT IN ('closed')") + $priorStmt = $pdo->prepare("SELECT id,status FROM tickets WHERE $col=? AND status NOT IN ('closed')"); + $priorStmt->execute([$val]); $priorTickets = $priorStmt->fetchAll(); + $pdo->prepare("UPDATE tickets SET status='closed',resolution_code='spam',resolution_note='Im Rahmen einer Sperre geschlossen',closed_at=?,updated_at=? WHERE $col=? AND status NOT IN ('closed')") ->execute([db_now(), db_now(), $val]); + ticket_sla_transition_rows($pdo, $priorTickets, 'closed', (int) $staff['id']); $added++; } audit_log($staff['id'], "Sperrliste Massen-Import: $added hinzugefügt, $skipped übersprungen"); diff --git a/DeskHub-Webseite/staff/dashboard_counts.php b/DeskHub-Webseite/staff/dashboard_counts.php index 4916f117..d7e91530 100644 --- a/DeskHub-Webseite/staff/dashboard_counts.php +++ b/DeskHub-Webseite/staff/dashboard_counts.php @@ -17,7 +17,7 @@ $mine->execute(array_merge([$staff['id']], $deptParams)); $mine = (int)$mine->fe $unassigned = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE assigned_staff_id IS NULL AND status NOT IN ('resolved','closed')" . $deptFilter); $unassigned->execute($deptParams); $unassigned = (int)$unassigned->fetchColumn(); -$overdue = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE status NOT IN ('resolved','closed') AND resolution_due_at IS NOT NULL AND resolution_due_at < ?" . $deptFilter); +$overdue = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE status NOT IN ('resolved','closed') AND (is_overdue = 1 OR (sla_paused_at IS NULL AND resolution_due_at IS NOT NULL AND resolution_due_at < ?))" . $deptFilter); $overdue->execute(array_merge([$now], $deptParams)); $overdue = (int)$overdue->fetchColumn(); $unanswered = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE status = 'open'" . $deptFilter); diff --git a/DeskHub-Webseite/staff/kb_manage.php b/DeskHub-Webseite/staff/kb_manage.php index 6df93635..2a501bba 100644 --- a/DeskHub-Webseite/staff/kb_manage.php +++ b/DeskHub-Webseite/staff/kb_manage.php @@ -16,6 +16,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } else { $slug = slugify($name); $pdo->prepare('INSERT INTO kb_categories (name, slug, sort_order) VALUES (?,?,0)')->execute([$name, $slug]); + if (function_exists('dhcm_cache_clear')) dhcm_cache_clear(); audit_log($admin['id'], "KB-Kategorie erstellt: {$name}"); flash('success', 'Kategorie erstellt.'); redirect('/staff/kb_manage.php'); @@ -33,6 +34,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $nameStmt->execute([$id]); $catName = $nameStmt->fetchColumn(); $pdo->prepare('DELETE FROM kb_categories WHERE id = ?')->execute([$id]); + if (function_exists('dhcm_cache_clear')) dhcm_cache_clear(); audit_log($admin['id'], "KB-Kategorie gelöscht: {$catName}"); flash('success', 'Kategorie gelöscht.'); } @@ -69,6 +71,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { audit_log($admin['id'], "KB-Artikel aktualisiert: {$title}"); flash('success', 'Artikel aktualisiert.'); } + if (function_exists('dhcm_cache_clear')) dhcm_cache_clear(); redirect('/staff/kb_manage.php'); } } @@ -79,6 +82,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $titleStmt->execute([$id]); $artTitle = $titleStmt->fetchColumn(); $pdo->prepare('DELETE FROM kb_articles WHERE id = ?')->execute([$id]); + if (function_exists('dhcm_cache_clear')) dhcm_cache_clear(); audit_log($admin['id'], "KB-Artikel gelöscht: {$artTitle}"); flash('success', 'Artikel gelöscht.'); redirect('/staff/kb_manage.php'); diff --git a/DeskHub-Webseite/staff/live_chat.php b/DeskHub-Webseite/staff/live_chat.php index 685a4673..2bc10d5f 100644 --- a/DeskHub-Webseite/staff/live_chat.php +++ b/DeskHub-Webseite/staff/live_chat.php @@ -164,8 +164,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && ($_POST['action'] ?? '') === 'end') $now = db_now(); $pdo->prepare( - "UPDATE tickets SET status='closed', closed_at=COALESCE(closed_at, ?), updated_at=?, locked_by=NULL, locked_at=NULL WHERE id=?" + "UPDATE tickets SET status='closed', resolution_code='other', resolution_note='Live-Chat beendet', closed_at=COALESCE(closed_at, ?), updated_at=?, locked_by=NULL, locked_at=NULL WHERE id=?" )->execute([$now, $now, $ticket['id']]); + ticket_sla_status_transition($pdo, (int) $ticket['id'], (string) $ticket['status'], 'closed', (int) $staff['id']); log_activity($pdo, $ticket['id'], $staff['id'], 'Live-Chat beendet'); finalize_work_session($pdo, (int) $ticket['id'], (int) $staff['id']); @@ -231,6 +232,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if ($autoAssign) $args[] = $staff['id']; $args[] = $ticket['id']; $stmt->execute($args); + ticket_sla_status_transition($pdo, (int) $ticket['id'], (string) $ticket['status'], $newStatus, (int) $staff['id']); if ($autoAssign) log_activity($pdo, $ticket['id'], $staff['id'], 'Automatisch zugewiesen (Live-Chat)'); save_mentions($pdo, $ticket['id'], $threadId, $staff['id'], $message, $ticket); diff --git a/DeskHub-Webseite/staff/live_tickets.php b/DeskHub-Webseite/staff/live_tickets.php index 461408a6..4b61791c 100644 --- a/DeskHub-Webseite/staff/live_tickets.php +++ b/DeskHub-Webseite/staff/live_tickets.php @@ -39,7 +39,7 @@ $counts = [ 'open' => $count($open), 'mine' => $count("t.assigned_staff_id = ? AND {$open}", [(int) $staff['id']]), 'unassigned' => $count("t.assigned_staff_id IS NULL AND {$open}"), - 'overdue' => $count("{$open} AND t.resolution_due_at IS NOT NULL AND t.resolution_due_at < ?", [db_now()]), + 'overdue' => $count("{$open} AND (t.is_overdue = 1 OR (t.sla_paused_at IS NULL AND t.resolution_due_at IS NOT NULL AND t.resolution_due_at < ?))", [db_now()]), 'unanswered' => $count("t.status = 'open'"), ]; diff --git a/DeskHub-Webseite/staff/problems.php b/DeskHub-Webseite/staff/problems.php new file mode 100644 index 00000000..a7cd5d61 --- /dev/null +++ b/DeskHub-Webseite/staff/problems.php @@ -0,0 +1,142 @@ +beginTransaction(); + $problemId = ticket_problem_create($pdo, $_POST, (int) $staff['id']); + $ticketNumber = trim((string) ($_POST['ticket_number'] ?? '')); + if ($ticketNumber !== '') { + $ticketStmt = $pdo->prepare('SELECT id FROM tickets WHERE ticket_number = ?'); + $ticketStmt->execute([$ticketNumber]); + $ticketId = (int) $ticketStmt->fetchColumn(); + if (!$ticketId) throw new InvalidArgumentException('Das angegebene Ticket wurde nicht gefunden.'); + ticket_problem_link($pdo, $problemId, $ticketId, (int) $staff['id']); + log_activity($pdo, $ticketId, (int) $staff['id'], 'Mit ' . ticket_problem_number($problemId) . ' verknüpft'); + } + $pdo->commit(); + audit_log($staff['id'], 'Problem ' . ticket_problem_number($problemId) . ' erstellt'); + flash('success', ticket_problem_number($problemId) . ' wurde angelegt.'); + redirect('/staff/problems.php?tab=list&id=' . $problemId); + } elseif ($action === 'problem_update') { + if (!ticket_problem_update($pdo, $problemId, $_POST, (int) $staff['id'])) throw new InvalidArgumentException('Problem nicht gefunden.'); + audit_log($staff['id'], 'Problem ' . ticket_problem_number($problemId) . ' aktualisiert'); + flash('success', 'Problem wurde gespeichert.'); + } elseif ($action === 'ticket_link') { + $ticketNumber = trim((string) ($_POST['ticket_number'] ?? '')); + $ticketStmt = $pdo->prepare('SELECT id FROM tickets WHERE ticket_number = ?'); + $ticketStmt->execute([$ticketNumber]); + $ticketId = (int) $ticketStmt->fetchColumn(); + if (!$ticketId) throw new InvalidArgumentException('Das angegebene Ticket wurde nicht gefunden.'); + if (!ticket_problem_link($pdo, $problemId, $ticketId, (int) $staff['id'])) throw new InvalidArgumentException('Ticket oder Problem wurde nicht gefunden.'); + log_activity($pdo, $ticketId, (int) $staff['id'], 'Mit ' . ticket_problem_number($problemId) . ' verknüpft'); + audit_log($staff['id'], 'Ticket mit ' . ticket_problem_number($problemId) . ' verknüpft'); + flash('success', 'Ticket wurde mit dem Problem verknüpft.'); + } elseif ($action === 'ticket_unlink') { + $ticketId = (int) ($_POST['ticket_id'] ?? 0); + ticket_problem_unlink($pdo, $problemId, $ticketId); + log_activity($pdo, $ticketId, (int) $staff['id'], 'Verknüpfung mit ' . ticket_problem_number($problemId) . ' entfernt'); + audit_log($staff['id'], 'Ticket von ' . ticket_problem_number($problemId) . ' getrennt'); + flash('success', 'Ticket-Verknüpfung wurde entfernt.'); + } + } catch (Throwable $e) { + if ($pdo->inTransaction()) $pdo->rollBack(); + $errors[] = $e instanceof InvalidArgumentException ? $e->getMessage() : 'Änderung konnte nicht gespeichert werden.'; + } + if (!$errors) redirect('/staff/problems.php' . ($problemId ? '?id=' . $problemId : '')); +} + +$problems = ticket_problems($pdo); +$problem = $problemId ? ticket_problem_get($pdo, $problemId) : null; +if ($problemId && !$problem) $errors[] = 'Problem nicht gefunden.'; +$linkedTickets = $problem ? ticket_problem_tickets($pdo, $problemId) : []; +$problemStatuses = ticket_problem_statuses(); +$activePage = 'problems'; +$pageTitle = 'Problemmanagement'; +require __DIR__ . '/_header.php'; +?> +

Problemmanagement

+

Bündeln Sie Tickets mit derselben Ursache. Ursache, Workaround und dauerhafte Lösung werden zentral dokumentiert.

+
+ + +
+

Neues Problem erfassen

+

Titel und erste Ticketnummer eintragen. Ursache und dauerhafte Lösung können danach ergänzt werden.

+
+ + + + + + +
+
+ +
+ + +
+ +

Problem auswählen

Wählen Sie links ein Problem aus oder legen Sie ein neues an.

+ +
+
+

+ Erstellt +
+
+ + + + + +
+
+
+ +
+

Verknüpfte Tickets ()

+
+

Noch keine Tickets mit diesem Problem verknüpft.

+ +
+
# ·
·
+
+
+ +
+ +
+
+ + + diff --git a/DeskHub-Webseite/staff/reports.php b/DeskHub-Webseite/staff/reports.php index db8e621c..f0cdb616 100644 --- a/DeskHub-Webseite/staff/reports.php +++ b/DeskHub-Webseite/staff/reports.php @@ -337,7 +337,7 @@ $stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE assigned_staff_id IS N $stmt->execute($deptParamsT); $dashUnassigned = (int) $stmt->fetchColumn(); -$stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE status NOT IN ('resolved','closed') AND resolution_due_at IS NOT NULL AND resolution_due_at < ?{$deptFilterT}"); +$stmt = $pdo->prepare("SELECT COUNT(*) FROM tickets WHERE status NOT IN ('resolved','closed') AND (is_overdue = 1 OR (sla_paused_at IS NULL AND resolution_due_at IS NOT NULL AND resolution_due_at < ?)){$deptFilterT}"); $stmt->execute(array_merge([$now], $deptParamsT)); $dashOverdue = (int) $stmt->fetchColumn();