prepare('SELECT COUNT(*) FROM staff WHERE id = ? AND is_active = 1'); $agentStmt->execute([$assignedId]); if (!(bool) $agentStmt->fetchColumn()) { api_error(422, 'INVALID_ASSIGNEE', 'Der ausgewählte Mitarbeiter ist nicht verfügbar.'); } } $currentId = $ticket['assigned_staff_id'] === null ? null : (int) $ticket['assigned_staff_id']; if ($assignedId !== $currentId) { api_require_permission($staff, 'assign_ticket', (int) $ticket['department_id']); $updates[] = 'assigned_staff_id = ?'; $params[] = $assignedId; } } // Wiedervorlage ("snoozeUntil": Datum/Uhrzeit oder null zum Aufheben) und Beobachten ("following": true/false) if (array_key_exists('snoozeUntil', $body)) { if ($body['snoozeUntil'] === null || $body['snoozeUntil'] === '') { ticket_snooze_clear($pdo, (int) $ticket['id'], (int) $staff['id'], 'Wiedervorlage aufgehoben (Mobile App)'); } else { $until = date('Y-m-d H:i:s', (int) strtotime((string) $body['snoozeUntil'])); $untilTs = strtotime($until); if ($untilTs <= time() + 30 || $untilTs > time() + 366 * 86400) { api_error(422, 'INVALID_SNOOZE', 'Die Wiedervorlage muss in der Zukunft liegen (höchstens 1 Jahr).'); } ticket_snooze_set($pdo, (int) $ticket['id'], (int) $staff['id'], $until); } } if (array_key_exists('following', $body)) { if (!empty($body['following'])) ticket_follow($pdo, (int) $ticket['id'], (int) $staff['id']); else ticket_unfollow($pdo, (int) $ticket['id'], (int) $staff['id']); } if (!$updates) { api_json(['ticket' => api_ticket_summary(api_find_ticket($pdo, $staff, $number))]); } $updates[] = 'updated_at = ?'; $params[] = $now; $params[] = $ticket['id']; $pdo->prepare('UPDATE tickets SET ' . implode(', ', $updates) . ' WHERE id = ?')->execute($params); if (array_key_exists('status', $body) && $body['status'] !== $ticket['status']) { [$label] = status_label((string) $body['status']); log_activity($pdo, (int) $ticket['id'], (int) $staff['id'], 'Status geändert: ' . $label . ' (Mobile App)'); } if (array_key_exists('assignedStaffId', $body)) { log_activity($pdo, (int) $ticket['id'], (int) $staff['id'], 'Zuweisung geändert (Mobile App)'); if ($assignedId !== null && $assignedId !== $currentId) fcm_notify_assignment($ticket, $assignedId, (int) $staff['id']); // Push auf die Android-App des neuen Bearbeiters } if ($chatJustClosed && $ticket['requester_email']) { try { $transcriptStmt = $pdo->prepare( 'SELECT tt.author_type AS authorType, tt.message, tt.created_at AS createdAt, s.name AS staff_name FROM ticket_threads tt LEFT JOIN staff s ON s.id = tt.staff_id WHERE tt.ticket_id = ? AND tt.is_internal_note = 0 ORDER BY tt.created_at ASC, tt.id ASC' ); $transcriptStmt->execute([$ticket['id']]); $transcriptMessages = array_map(fn(array $m) => [ 'authorType' => $m['authorType'], 'authorName' => $m['staff_name'], 'message' => $m['message'], 'createdAt' => $m['createdAt'], ], $transcriptStmt->fetchAll()); notify_customer_chat_transcript($ticket, $transcriptMessages); } catch (Throwable $e) { error_log('ticket_update (Mobile App) transcript mail: ' . $e->getMessage()); } } $updated = api_find_ticket($pdo, $staff, $number); if ($updated['status'] !== $ticket['status']) webhook_dispatch($pdo, 'ticket.status_changed', (int) $ticket['id'], ['previousStatus' => $ticket['status']]); if ((int) ($updated['assigned_staff_id'] ?? 0) !== (int) ($ticket['assigned_staff_id'] ?? 0)) webhook_dispatch($pdo, 'ticket.assigned', (int) $ticket['id']); api_json(['ticket' => api_ticket_summary($updated)]);