'Fehler behoben', 'guidance' => 'Anleitung oder Beratung', 'duplicate' => 'Doppelt / zusammengeführt', 'not_reproducible' => 'Nicht reproduzierbar', 'no_action' => 'Keine Maßnahme erforderlich', 'spam' => 'Spam oder Sperre', 'automated' => 'Automatisch geschlossen', 'other' => 'Sonstiges', ]; } function ticket_problem_statuses(): array { return [ 'investigating' => 'Wird untersucht', 'workaround' => 'Workaround verfügbar', 'monitoring' => 'Wird beobachtet', 'resolved' => 'Behoben', ]; } function ticket_problem_number(int $id): string { return 'PRB-' . str_pad((string) $id, 6, '0', STR_PAD_LEFT); } function ticket_problems(PDO $pdo, bool $activeOnly = false): array { $where = $activeOnly ? "WHERE p.status <> 'resolved'" : ''; return $pdo->query("SELECT p.*, s.name AS creator_name, (SELECT COUNT(*) FROM problem_tickets pt WHERE pt.problem_id = p.id) AS ticket_count FROM problems p LEFT JOIN staff s ON s.id = p.created_by $where ORDER BY CASE WHEN p.status = 'resolved' THEN 1 ELSE 0 END, p.updated_at DESC, p.id DESC LIMIT 300")->fetchAll(); } function ticket_problem_get(PDO $pdo, int $id): ?array { $stmt = $pdo->prepare('SELECT p.*, s.name AS creator_name FROM problems p LEFT JOIN staff s ON s.id = p.created_by WHERE p.id = ?'); $stmt->execute([$id]); return $stmt->fetch() ?: null; } function ticket_problem_create(PDO $pdo, array $input, int $staffId): int { $title = trim(mb_substr((string) ($input['title'] ?? ''), 0, 200)); if ($title === '') throw new InvalidArgumentException('Bitte einen Titel für das Problem angeben.'); $status = (string) ($input['status'] ?? 'investigating'); if (!isset(ticket_problem_statuses()[$status])) $status = 'investigating'; $resolution = trim((string) ($input['resolution'] ?? '')); if ($status === 'resolved' && $resolution === '') throw new InvalidArgumentException('Bitte dokumentieren Sie die dauerhafte Lösung, bevor Sie das Problem als behoben markieren.'); $now = db_now(); $stmt = $pdo->prepare('INSERT INTO problems (title, description, root_cause, workaround, resolution, status, created_by, updated_by, created_at, updated_at, resolved_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)'); $stmt->execute([ $title, trim((string) ($input['description'] ?? '')) ?: null, trim((string) ($input['root_cause'] ?? '')) ?: null, trim((string) ($input['workaround'] ?? '')) ?: null, $resolution ?: null, $status, $staffId, $staffId, $now, $now, $status === 'resolved' ? $now : null, ]); return (int) $pdo->lastInsertId(); } function ticket_problem_update(PDO $pdo, int $id, array $input, int $staffId): bool { $title = trim(mb_substr((string) ($input['title'] ?? ''), 0, 200)); $status = (string) ($input['status'] ?? 'investigating'); if ($title === '') throw new InvalidArgumentException('Bitte einen Titel für das Problem angeben.'); if (!isset(ticket_problem_statuses()[$status])) throw new InvalidArgumentException('Der Problemstatus ist ungültig.'); $resolution = trim((string) ($input['resolution'] ?? '')); if ($status === 'resolved' && $resolution === '') throw new InvalidArgumentException('Bitte dokumentieren Sie die dauerhafte Lösung, bevor Sie das Problem als behoben markieren.'); $current = ticket_problem_get($pdo, $id); if (!$current) return false; $resolvedAt = $status === 'resolved' ? ($current['resolved_at'] ?: db_now()) : null; $stmt = $pdo->prepare('UPDATE problems SET title = ?, description = ?, root_cause = ?, workaround = ?, resolution = ?, status = ?, updated_by = ?, updated_at = ?, resolved_at = ? WHERE id = ?'); $stmt->execute([ $title, trim((string) ($input['description'] ?? '')) ?: null, trim((string) ($input['root_cause'] ?? '')) ?: null, trim((string) ($input['workaround'] ?? '')) ?: null, $resolution ?: null, $status, $staffId, db_now(), $resolvedAt, $id, ]); return true; } function ticket_problem_tickets(PDO $pdo, int $problemId): array { $stmt = $pdo->prepare('SELECT t.id, t.ticket_number, t.subject, t.status, t.requester_name, t.created_at, s.name AS linked_by_name FROM problem_tickets pt JOIN tickets t ON t.id = pt.ticket_id LEFT JOIN staff s ON s.id = pt.linked_by WHERE pt.problem_id = ? ORDER BY pt.linked_at DESC, t.id DESC'); $stmt->execute([$problemId]); return $stmt->fetchAll(); } function ticket_problem_link(PDO $pdo, int $problemId, int $ticketId, int $staffId): bool { if (!ticket_problem_get($pdo, $problemId)) return false; $ticket = $pdo->prepare('SELECT id FROM tickets WHERE id = ?'); $ticket->execute([$ticketId]); if (!$ticket->fetchColumn()) return false; $insertIgnore = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME) === 'sqlite' ? 'INSERT OR IGNORE' : 'INSERT IGNORE'; $stmt = $pdo->prepare("$insertIgnore INTO problem_tickets (problem_id, ticket_id, linked_by, linked_at) VALUES (?,?,?,?)"); $stmt->execute([$problemId, $ticketId, $staffId, db_now()]); return true; } /** Löst die Verknüpfung. Liefert false, wenn es sie nicht gab. */ function ticket_problem_unlink(PDO $pdo, int $problemId, int $ticketId): bool { $stmt = $pdo->prepare('DELETE FROM problem_tickets WHERE problem_id = ? AND ticket_id = ?'); $stmt->execute([$problemId, $ticketId]); return $stmt->rowCount() > 0; } function ticket_problems_for_ticket(PDO $pdo, int $ticketId): array { $stmt = $pdo->prepare('SELECT p.* FROM problems p JOIN problem_tickets pt ON pt.problem_id = p.id WHERE pt.ticket_id = ? ORDER BY p.updated_at DESC, p.id DESC'); $stmt->execute([$ticketId]); return $stmt->fetchAll(); } function ticket_record_resolution(PDO $pdo, int $ticketId, string $reason, string $note = ''): bool { if (!isset(ticket_resolution_reasons()[$reason])) return false; $pdo->prepare('UPDATE tickets SET resolution_code = ?, resolution_note = ? WHERE id = ?') ->execute([$reason, trim(mb_substr($note, 0, 2000)) ?: null, $ticketId]); return true; }