0) { $stmt = $pdo->prepare('SELECT u.*, o.name AS org_name FROM users u LEFT JOIN organizations o ON o.id = u.organization_id WHERE u.id = ?'); $stmt->execute([$userId]); $customer = $stmt->fetch(); } elseif ($guestEmail !== '') { // Gast-Anfrager (kein Konto, nur über Tickets bekannt) – keine Zeile in "users", // daher hier aus den Ticket-Daten zusammengesetzt statt per user_id nachgeschlagen. $stmt = $pdo->prepare( "SELECT requester_email AS email, MAX(requester_name) AS name, MIN(created_at) AS created_at, NULL AS phone, NULL AS org_name FROM tickets WHERE requester_email = ? AND user_id IS NULL GROUP BY requester_email" ); $stmt->execute([$guestEmail]); $customer = $stmt->fetch(); $isGuest = (bool) $customer; } else { $customer = false; } if (!$customer) { flash('error', 'Kunde nicht gefunden.'); redirect('/staff/customers.php'); } $errors = []; if ($_SERVER['REQUEST_METHOD'] === 'POST') { verify_csrf(); $action = $_POST['action'] ?? ''; // Notizen und Kontaktdaten hängen an einem echten Kundenkonto (users-Tabelle) – // für Gast-Anfrager ohne Konto gibt es das nicht, siehe Hinweis oben in der Seite. if ($isGuest && in_array($action, ['add_note', 'delete_note', 'save_customer'], true)) { flash('error', 'Für Gast-Anfrager ohne Konto nicht verfügbar.'); redirect('/staff/customer_profile.php?email=' . urlencode($guestEmail)); } if ($action === 'gdpr_export' || $action === 'gdpr_erase') { if ($staff['role'] !== 'admin' || !gdpr_tools_enabled()) { flash('error', 'Die Datenschutz-Werkzeuge sind nicht verfügbar (nur Administratoren, in den Einstellungen einzuschalten).'); redirect($isGuest ? '/staff/customer_profile.php?email=' . urlencode($guestEmail) : '/staff/customer_profile.php?id=' . $userId); } $ref = $isGuest ? ['email' => $guestEmail] : ['user_id' => $userId]; if ($action === 'gdpr_export') { $data = gdpr_customer_export($pdo, $ref); audit_log($staff['id'], 'Kundendaten exportiert (' . ($isGuest ? 'Gast ' . $guestEmail : 'Kunde #' . $userId) . ')'); header('Content-Type: application/json; charset=utf-8'); header('Content-Disposition: attachment; filename="kundendaten-' . ($isGuest ? 'gast' : $userId) . '-' . date('Ymd') . '.json"'); header('X-Content-Type-Options: nosniff'); echo json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE); exit; } if (trim((string) ($_POST['confirm_email'] ?? '')) !== (string) $customer['email']) { flash('error', 'Zur Bestätigung muss die E-Mail-Adresse des Kunden genau eingegeben werden.'); redirect($isGuest ? '/staff/customer_profile.php?email=' . urlencode($guestEmail) : '/staff/customer_profile.php?id=' . $userId); } $res = gdpr_customer_erase($pdo, $ref, !empty($_POST['force'])); if ($res['error']) { flash('error', $res['error']); redirect($isGuest ? '/staff/customer_profile.php?email=' . urlencode($guestEmail) : '/staff/customer_profile.php?id=' . $userId); } audit_log($staff['id'], 'Kundendaten gelöscht (' . ($isGuest ? 'Gast' : 'Kunde #' . $userId) . '): ' . $res['tickets'] . ' Ticket(s) anonymisiert, ' . $res['messages'] . ' Nachricht(en) und ' . $res['files'] . ' Datei(en) entfernt'); flash('success', 'Kundendaten gelöscht: ' . $res['tickets'] . ' Ticket(s) anonymisiert, ' . $res['messages'] . ' Nachricht(en) und ' . $res['files'] . ' Datei(en) entfernt.'); redirect('/staff/customers.php'); } if ($action === 'add_note') { $note = trim($_POST['note'] ?? ''); if ($note !== '') { $pdo->prepare('INSERT INTO customer_notes (user_id, staff_id, note, created_at) VALUES (?,?,?,?)') ->execute([$userId, $staff['id'], $note, db_now()]); flash('success', 'Notiz gespeichert.'); } redirect('/staff/customer_profile.php?id=' . $userId); } if ($action === 'delete_note') { $noteId = (int) ($_POST['note_id'] ?? 0); $pdo->prepare('DELETE FROM customer_notes WHERE id=? AND user_id=?')->execute([$noteId, $userId]); redirect('/staff/customer_profile.php?id=' . $userId); } if ($action === 'save_customer') { $name = trim($_POST['name'] ?? ''); $phone = trim($_POST['phone'] ?? ''); if ($name !== '') { $pdo->prepare('UPDATE users SET name=?, phone=? WHERE id=?')->execute([$name, $phone ?: null, $userId]); flash('success', 'Kundendaten gespeichert.'); } redirect('/staff/customer_profile.php?id=' . $userId); } } // Alle Tickets dieses Kunden (Mitarbeiter sehen nur Tickets ihrer Abteilungen) [$deptSql, $deptParams] = dept_filter_sql(staff_department_ids($staff), 't.department_id'); $ticketStmt = $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 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 WHERE (t.requester_email = ? OR t.user_id = ?) {$deptSql} ORDER BY t.created_at DESC LIMIT 100" ); $ticketStmt->execute([$customer['email'], $userId, ...$deptParams]); $tickets = $ticketStmt->fetchAll(); // Notizen $noteStmt = $pdo->prepare( "SELECT cn.*, COALESCE(s.name, 'Gelöschter Mitarbeiter') AS staff_name FROM customer_notes cn LEFT JOIN staff s ON s.id = cn.staff_id WHERE cn.user_id = ? ORDER BY cn.created_at DESC" ); $noteStmt->execute([$userId]); $notes = $noteStmt->fetchAll(); // Statistiken $totalTickets = count($tickets); $openTickets = count(array_filter($tickets, fn($t) => !in_array($t['status'], ['resolved','closed']))); $closedTickets = count(array_filter($tickets, fn($t) => in_array($t['status'], ['resolved','closed']))); $pageTitle = 'Kundenprofil: ' . $customer['name']; $activePage = 'customers'; require __DIR__ . '/_header.php'; ?>
| Ticket | Betreff | Status | Priorität | Mitarbeiter | Erstellt |
|---|---|---|---|---|---|
| #= e($t['ticket_number']) ?> | = e($t['subject']) ?> | = e($label) ?> | = e($t['priority_name']) ?> | = e($t['staff_name'] ?? '–') ?> | = format_dt($t['created_at']) ?> |
Noch keine Notizen.
Ohne Konto nicht bearbeitbar.