Files
deskhub/DeskHub-Webseite/api/staff/v1/tickets.php
T
2026-10-26 21:19:25 +00:00

119 lines
4.9 KiB
PHP

<?php
declare(strict_types=1);
require_once __DIR__ . '/common.php';
api_require_method('GET');
$staff = api_require_staff();
$pdo = get_db();
$where = [];
$params = [];
$departmentIds = staff_department_ids($staff);
[$departmentFilter, $departmentParams] = dept_filter_sql($departmentIds, 't.department_id');
if ($departmentFilter !== '') {
$where[] = substr($departmentFilter, 5);
array_push($params, ...$departmentParams);
}
// Wiedervorlage: ausgeblendet, außer mit ?snoozed=1 (dann nur diese)
$where[] = !empty($_GET['snoozed']) ? (ticket_snooze_available() ? "t.snoozed_until > '" . db_now() . "'" : '1=0') : ticket_not_snoozed_sql('t');
// Beobachtet: nur Tickets, die der angemeldete Mitarbeiter beobachtet (?following=1)
if (!empty($_GET['following'])) {
$where[] = 't.id IN (SELECT ticket_id FROM ticket_followers WHERE staff_id = ?)';
$params[] = (int) $staff['id'];
}
$statuses = array_values(array_filter(
array_map('trim', explode(',', (string) ($_GET['status'] ?? 'open,pending,answered,on_hold'))),
fn(string $status) => in_array($status, MOBILE_API_STATUSES, true)
));
if ($statuses) {
$where[] = 't.status IN (' . implode(',', array_fill(0, count($statuses), '?')) . ')';
array_push($params, ...$statuses);
}
$department = filter_var($_GET['departmentId'] ?? null, FILTER_VALIDATE_INT);
if ($department !== false && $department !== null) {
if ($departmentIds !== null && !in_array((int) $department, $departmentIds, true)) {
api_error(403, 'DEPARTMENT_FORBIDDEN', 'Auf diese Abteilung besteht kein Zugriff.');
}
$where[] = 't.department_id = ?';
$params[] = (int) $department;
}
$assigned = trim((string) ($_GET['assigned'] ?? ''));
if ($assigned === 'me') {
$where[] = 't.assigned_staff_id = ?';
$params[] = $staff['id'];
} elseif ($assigned === 'unassigned') {
$where[] = 't.assigned_staff_id IS NULL';
} elseif ($assigned !== '' && ctype_digit($assigned)) {
$where[] = 't.assigned_staff_id = ?';
$params[] = (int) $assigned;
}
$priority = filter_var($_GET['priorityId'] ?? null, FILTER_VALIDATE_INT);
if ($priority !== false && $priority !== null) {
$where[] = 't.priority_id = ?';
$params[] = (int) $priority;
}
$tag = filter_var($_GET['tagId'] ?? null, FILTER_VALIDATE_INT);
if ($tag !== false && $tag !== null) {
$where[] = 't.id IN (SELECT ticket_id FROM ticket_tags WHERE tag_id = ?)';
$params[] = (int) $tag;
}
// SLA-Status: siehe staff/tickets.php für dieselbe Logik (Web und App teilen die
// 4-Stunden-Risiko-Schwelle, damit beide Oberflächen dasselbe "bald fällig" meinen).
$sla = trim((string) ($_GET['sla'] ?? ''));
if ($sla !== '') {
$now = db_now();
$riskThreshold = date('Y-m-d H:i:s', time() + 4 * 3600);
if ($sla === 'overdue') {
$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 ($sla === 'at_risk') {
$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 ($sla === 'on_time') {
$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;
}
}
$search = mb_substr(trim((string) ($_GET['q'] ?? '')), 0, 100);
if ($search !== '') {
$like = '%' . $search . '%';
$where[] = '(t.subject LIKE ? OR t.ticket_number LIKE ? OR t.requester_email LIKE ?
OR t.requester_name LIKE ? OR t.id IN (SELECT ticket_id FROM ticket_threads WHERE message LIKE ?))';
array_push($params, $like, $like, $like, $like, $like);
}
$page = max(1, (int) ($_GET['page'] ?? 1));
$perPage = max(10, min(100, (int) ($_GET['perPage'] ?? 30)));
$offset = ($page - 1) * $perPage;
$from = ' FROM tickets t
JOIN priorities p ON p.id = t.priority_id
JOIN departments d ON d.id = t.department_id
LEFT JOIN staff s ON s.id = t.assigned_staff_id';
$whereSql = $where ? ' WHERE ' . implode(' AND ', $where) : '';
$countStmt = $pdo->prepare('SELECT COUNT(*)' . $from . $whereSql);
$countStmt->execute($params);
$total = (int) $countStmt->fetchColumn();
$stmt = $pdo->prepare(
'SELECT t.*, p.name AS priority_name, p.color AS priority_color,
d.name AS department_name, s.name AS staff_name'
. $from . $whereSql
. ' ORDER BY t.updated_at DESC, t.id DESC LIMIT ' . $perPage . ' OFFSET ' . $offset
);
$stmt->execute($params);
api_json([
'items' => array_map('api_ticket_summary', $stmt->fetchAll()),
'page' => $page,
'perPage' => $perPage,
'total' => $total,
'hasMore' => ($offset + $perPage) < $total,
]);