261 lines
33 KiB
PHP
261 lines
33 KiB
PHP
<?php
|
|
/**
|
|
* tools/manual/seed_manual.php
|
|
*
|
|
* Ergänzt die Demo-Installation von tools/demo-video/seed_demo.php um viele weitere erfundene Daten (Abteilungen, Themen, Vorlagen, Regeln,
|
|
* Berichte, Kunden, Tickets, Plugins …), damit die Bilder im Handbuch jede Seite gefüllt zeigen. Alles ist erfunden: Namen, Firmen,
|
|
* Adressen (example.org, demo.example), keine echten Daten.
|
|
* php tools/manual/seed_manual.php <Demo-Ordner>
|
|
* Zugang danach: lena@demo.example / Demo-Passwort-1234 (Administratorin), kunde: erika.muster@kunde.example / Demo-Passwort-1234
|
|
*/
|
|
$tmp = rtrim(str_replace('\\', '/', $argv[1] ?? ''), '/');
|
|
if ($tmp === '' || !is_file("$tmp/database/deskhub.sqlite")) { fwrite(STDERR, "Demo-Ordner fehlt. Erst tools/demo-video/seed_demo.php ausführen.\n"); exit(1); }
|
|
date_default_timezone_set('Europe/Berlin');
|
|
$p = new PDO("sqlite:$tmp/database/deskhub.sqlite");
|
|
$p->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
|
$p->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
|
|
$ts = fn(string $rel) => date('Y-m-d H:i:s', strtotime($rel));
|
|
$warn = [];
|
|
$try = function (string $what, callable $fn) use (&$warn) { try { $fn(); } catch (Throwable $e) { $warn[] = $what . ': ' . $e->getMessage(); } };
|
|
$one = fn(string $sql, array $args = []) => (function () use ($p, $sql, $args) { $s = $p->prepare($sql); $s->execute($args); return $s->fetchColumn(); })();
|
|
$set = $p->prepare('INSERT OR REPLACE INTO settings (setting_key, setting_value) VALUES (?, ?)');
|
|
$id = fn(string $table, string $col, string $val) => (int) $one("SELECT id FROM {$table} WHERE {$col} = ?", [$val]);
|
|
$H = password_hash('Demo-Passwort-1234', PASSWORD_DEFAULT);
|
|
|
|
// ── Abteilungen, Prioritäten, SLA ───────────────────────────────────────────
|
|
$try('Abteilungen', function () use ($p, $ts) {
|
|
$p->exec("UPDATE departments SET email = 'support@demo.example', notes = 'Erste Anlaufstelle für alle Anfragen' WHERE name = 'Allgemeiner Support'");
|
|
$p->exec("UPDATE departments SET email = 'buchhaltung@demo.example', notes = 'Rechnungen, Zahlungen, Mahnungen' WHERE name = 'Buchhaltung'");
|
|
$p->exec("UPDATE departments SET email = 'technik@demo.example', notes = 'Störungen und technische Fragen' WHERE name = 'Technik'");
|
|
});
|
|
$try('SLA-Pläne', function () use ($p, $ts, $id) {
|
|
$p->exec("INSERT INTO sla_plans (name, response_hours, resolution_hours, is_active, notes, created_at, updated_at) VALUES ('Standard', 8, 48, 1, 'Für normale Anfragen', '{$ts('-60 days')}', '{$ts('-60 days')}'), ('Premium', 1, 8, 1, 'Für Kunden mit Servicevertrag', '{$ts('-60 days')}', '{$ts('-10 days')}')");
|
|
$p->exec("UPDATE departments SET sla_plan_id = " . $id('sla_plans', 'name', 'Standard') . " WHERE name = 'Allgemeiner Support'");
|
|
});
|
|
$dept = fn(string $n) => $id('departments', 'name', $n);
|
|
$prio = fn(string $n) => $id('priorities', 'name', $n);
|
|
$staffId = fn(string $n) => $id('staff', 'name', $n);
|
|
$lena = $staffId('Lena Hartmann');
|
|
$tim = $staffId('Tim Roth');
|
|
|
|
// ── Hilfethemen, Felder, Listen ─────────────────────────────────────────────
|
|
$try('Hilfethemen', function () use ($p, $ts, $dept, $prio) {
|
|
$ins = $p->prepare('INSERT INTO help_topics (name, department_id, priority_id, is_active, sort_order, is_public, notes, created_at, updated_at) VALUES (?,?,?,1,?,1,?,?,?)');
|
|
foreach ([['Rechnung und Zahlung', 'Buchhaltung', 'Hoch', 1, 'Fragen zu Rechnungen und Zahlungen'], ['Technische Störung', 'Technik', 'Dringend', 2, 'Etwas funktioniert nicht'], ['Allgemeine Frage', 'Allgemeiner Support', 'Normal', 3, 'Alles andere'], ['Kündigung und Vertrag', 'Allgemeiner Support', 'Normal', 4, 'Verträge ändern oder beenden']] as [$n, $d, $pr, $so, $no])
|
|
$ins->execute([$n, $dept($d), $prio($pr), $so, $no, $ts('-60 days'), $ts('-60 days')]);
|
|
});
|
|
$try('Listen und Felder', function () use ($p, $ts, $id) {
|
|
$p->exec("INSERT INTO custom_lists (name, sort_order, created_at) VALUES ('Betriebssystem', 1, '{$ts('-50 days')}')");
|
|
$lid = (int) $p->lastInsertId();
|
|
foreach (['Windows', 'macOS', 'Linux', 'Android', 'iOS'] as $i => $v) $p->prepare('INSERT INTO custom_list_items (list_id, value, sort_order) VALUES (?,?,?)')->execute([$lid, $v, $i]);
|
|
$ht = $id('help_topics', 'name', 'Rechnung und Zahlung');
|
|
$ht2 = $id('help_topics', 'name', 'Technische Störung');
|
|
$p->prepare("INSERT INTO custom_fields (label, field_type, is_required, is_active, sort_order, created_at, help_topic_id, help_text) VALUES ('Rechnungsnummer', 'text', 1, 1, 1, ?, ?, 'Zu finden oben auf der Rechnung')")->execute([$ts('-50 days'), $ht]);
|
|
$p->prepare("INSERT INTO custom_fields (label, field_type, list_id, is_required, is_active, sort_order, created_at, help_topic_id) VALUES ('Betriebssystem', 'list', ?, 0, 1, 2, ?, ?)")->execute([$lid, $ts('-50 days'), $ht2]);
|
|
});
|
|
|
|
// ── Vorlagen, Teams, Rollen ─────────────────────────────────────────────────
|
|
$try('Vorlagen', function () use ($p, $ts, $dept) {
|
|
$ins = $p->prepare('INSERT INTO canned_responses (title, body, department_id, created_at) VALUES (?,?,?,?)');
|
|
foreach ([
|
|
['Danke für deine Nachricht', '<p>Hallo {{kundenname}},</p><p>vielen Dank für deine Nachricht. Wir kümmern uns darum und melden uns schnellstmöglich.</p><p>{{signatur}}</p>', null],
|
|
['Rechnung erneut senden', '<p>Hallo {{kundenname}},</p><p>wir haben dir die Rechnung noch einmal als PDF angehängt. Sie steht außerdem in deinem Kundenbereich unter „Rechnungen“.</p>', 'Buchhaltung'],
|
|
['Rückfrage zur Störung', '<p>Hallo {{kundenname}},</p><p>damit wir die Störung nachstellen können: Welches Gerät und welche Version nutzt du? Hilft ein Neustart?</p>', 'Technik'],
|
|
['Ticket gelöst', '<p>Hallo {{kundenname}},</p><p>wir haben dein Anliegen gelöst. Falls noch etwas offen ist, antworte einfach auf diese Nachricht.</p>', null],
|
|
['Kündigung bestätigt', '<p>Hallo {{kundenname}},</p><p>wir bestätigen deine Kündigung zum Ende der Laufzeit. Schade, dass du gehst!</p>', 'Allgemeiner Support'],
|
|
] as [$t, $b, $d]) $ins->execute([$t, $b, $d ? $dept($d) : null, $ts('-40 days')]);
|
|
});
|
|
$try('Rollen', function () use ($p, $ts) {
|
|
$p->prepare('INSERT INTO roles (name, permissions, created_at) VALUES (?,?,?)')->execute(['Teamleitung', json_encode(['reply_ticket' => true, 'close_ticket' => true, 'post_note' => true, 'assign_ticket' => true, 'transfer_ticket' => true, 'manage_canned' => true, 'view_reports' => true, 'manage_teams' => true]), $ts('-45 days')]);
|
|
$p->prepare('INSERT INTO roles (name, permissions, created_at) VALUES (?,?,?)')->execute(['Nur lesen', json_encode(['view_reports' => true]), $ts('-45 days')]);
|
|
});
|
|
$try('Teams', function () use ($p, $ts, $staffId) {
|
|
$p->prepare('INSERT INTO teams (name, lead_id, notes, is_active, created_at) VALUES (?,?,?,1,?)')->execute(['Support-Team', $staffId('Lena Hartmann'), 'Tagesgeschäft und Kundenanfragen', $ts('-45 days')]);
|
|
$t1 = (int) $p->lastInsertId();
|
|
$p->prepare('INSERT INTO teams (name, lead_id, notes, is_active, created_at) VALUES (?,?,?,1,?)')->execute(['Technik-Team', $staffId('Mira Schulz'), 'Störungen und Entwicklung', $ts('-45 days')]);
|
|
$t2 = (int) $p->lastInsertId();
|
|
foreach ([[$t1, 'Lena Hartmann'], [$t1, 'Tim Roth'], [$t2, 'Mira Schulz'], [$t2, 'Tim Roth']] as [$t, $n]) $p->prepare('INSERT INTO team_members (team_id, staff_id) VALUES (?,?)')->execute([$t, $staffId($n)]);
|
|
});
|
|
|
|
// ── Kunden, Organisationen ──────────────────────────────────────────────────
|
|
$try('Kunden', function () use ($p, $ts, $H, $lena) {
|
|
$p->exec("INSERT INTO organizations (name, notes, created_at) VALUES ('Beispiel Bau GmbH', 'Rahmenvertrag, Premium-Support', '{$ts('-80 days')}'), ('Musterhaus AG', 'Pilotkunde', '{$ts('-70 days')}')");
|
|
$o1 = (int) $p->query("SELECT id FROM organizations WHERE name = 'Beispiel Bau GmbH'")->fetchColumn();
|
|
$o2 = (int) $p->query("SELECT id FROM organizations WHERE name = 'Musterhaus AG'")->fetchColumn();
|
|
$ins = $p->prepare('INSERT INTO users (name, email, password_hash, phone, organization_id, created_at) VALUES (?,?,?,?,?,?)');
|
|
foreach ([['Erika Muster', 'erika.muster@kunde.example', '0123 456789', $o1, '-90 days'], ['Jonas Weber', 'jonas.weber@kunde.example', null, $o1, '-75 days'], ['Sophie Klein', 'sophie.klein@kunde.example', null, $o2, '-60 days'], ['Markus Lehmann', 'markus.lehmann@kunde.example', '0171 0000000', null, '-30 days'], ['Lea Wagner', 'lea.wagner@kunde.example', null, null, '-20 days'], ['Dirk Neumann', 'dirk.neumann@kunde.example', null, $o2, '-10 days']] as [$n, $e, $ph, $o, $c])
|
|
$ins->execute([$n, $e, $H, $ph, $o, $ts($c)]);
|
|
$uid = (int) $p->query("SELECT id FROM users WHERE email = 'erika.muster@kunde.example'")->fetchColumn();
|
|
$p->prepare('INSERT INTO customer_notes (user_id, staff_id, note, created_at) VALUES (?,?,?,?)')->execute([$uid, $lena, 'Wünscht Rückrufe nur vormittags. Ansprechpartnerin für die Buchhaltung.', $ts('-20 days')]);
|
|
});
|
|
|
|
// ── Weitere Tickets ─────────────────────────────────────────────────────────
|
|
$try('Tickets', function () use ($p, $ts, $dept, $prio, $staffId) {
|
|
$tk = $p->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, assigned_staff_id, created_at, updated_at, resolution_due_at, first_response_due_at, first_responded_at, resolved_at, closed_at, is_overdue, user_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
|
|
$th = $p->prepare('INSERT INTO ticket_threads (ticket_id, author_type, staff_id, message, is_internal_note, created_at) VALUES (?,?,?,?,?,?)');
|
|
$uid = fn(string $e) => (int) $p->query("SELECT id FROM users WHERE email = " . $p->quote($e))->fetchColumn() ?: null;
|
|
foreach ([
|
|
['260107', 'Zahlung doppelt abgebucht', 'Erika Muster', 'Buchhaltung', 'Hoch', 'open', 'email', 'Lena Hartmann', '-4 hours', '+4 hours', null, [['customer', null, "Hallo,\nmeine Rechnung 4711 wurde zweimal abgebucht. Bitte um Erstattung.", '-4 hours']]],
|
|
['260108', 'Webseite lädt sehr langsam', 'Jonas Weber', 'Technik', 'Dringend', 'open', 'web', 'Mira Schulz', '-9 hours', '-1 hours', null, [['customer', null, 'Seit heute Morgen braucht jede Seite über 10 Sekunden.', '-9 hours'], ['staff', 'Mira Schulz', 'Wir prüfen das gerade. Danke für den Hinweis!', '-8 hours']]],
|
|
['260109', 'Wie ändere ich meine Lieferadresse?', 'Sophie Klein', 'Allgemeiner Support', 'Normal', 'pending', 'chat', 'Tim Roth', '-1 days', '+30 hours', null, [['customer', null, 'Ich ziehe um. Wo ändere ich die Adresse?', '-1 days'], ['staff', 'Tim Roth', 'Im Kundenbereich unter „Profil“. Klappt das bei dir?', '-23 hours']]],
|
|
['260110', 'Angebot für 50 Lizenzen', 'Lea Wagner', 'Allgemeiner Support', 'Normal', 'open', 'phone', null, '-3 hours', '+45 hours', null, [['customer', null, 'Telefonische Anfrage: Angebot für 50 Lizenzen, Rückruf gewünscht.', '-3 hours']]],
|
|
['260111', 'Rechnung fehlt (Monat Februar)', 'Dirk Neumann', 'Buchhaltung', 'Normal', 'on_hold', 'api', 'Lena Hartmann', '-2 days', '+3 days', null, [['customer', null, 'Die Februar-Rechnung fehlt im Kundenbereich.', '-2 days'], ['staff', 'Lena Hartmann', 'Wir klären das mit dem Rechnungsprogramm und melden uns.', '-47 hours']]],
|
|
['259905', 'Zugang gesperrt nach Passwortwechsel', 'Markus Lehmann', 'Allgemeiner Support', 'Hoch', 'resolved', 'web', 'Tim Roth', '-8 days', null, '-7 days', [['customer', null, 'Nach dem Passwortwechsel komme ich nicht mehr rein.', '-8 days'], ['staff', 'Tim Roth', 'Ich habe den Zugang entsperrt. Bitte versuche es jetzt noch einmal.', '-7 days']]],
|
|
['259906', 'Frage zum Datenschutz', 'Erika Muster', 'Allgemeiner Support', 'Niedrig', 'closed', 'email', 'Lena Hartmann', '-30 days', null, '-29 days', [['customer', null, 'Wo kann ich meine Daten einsehen?', '-30 days'], ['staff', 'Lena Hartmann', 'Unter „Profil → Datenschutz“. Gerne senden wir dir auch einen Auszug.', '-29 days']]],
|
|
['259907', 'Rechnungsadresse falsch', 'Jonas Weber', 'Buchhaltung', 'Normal', 'closed', 'web', 'Lena Hartmann', '-35 days', null, '-33 days', [['customer', null, 'Auf der Rechnung steht eine alte Adresse.', '-35 days'], ['staff', 'Lena Hartmann', 'Korrigiert, die neue Rechnung ist unterwegs.', '-33 days']]],
|
|
] as [$no, $subj, $name, $dn, $pn, $status, $src, $assignee, $created, $due, $resolved, $threads]) {
|
|
$last = $threads[count($threads) - 1][3];
|
|
$email = strtolower(str_replace(' ', '.', $name)) . '@kunde.example';
|
|
$tk->execute([$no, $subj, $name, $email, $dept($dn), $prio($pn), $status, $src, $assignee ? $staffId($assignee) : null, $ts($created), $ts($last),
|
|
$due ? $ts($due) : null, $ts($created . ' +1 hour'), $assignee ? $ts($created . ' +40 minutes') : null, $resolved ? $ts($resolved) : null, $status === 'closed' ? $ts($resolved ?? $last) : null,
|
|
($due && strtotime($ts($due)) < time() && !in_array($status, ['resolved', 'closed'], true)) ? 1 : 0, $uid($email)]);
|
|
$tid = (int) $p->lastInsertId();
|
|
foreach ($threads as [$who, $sn, $msg, $when]) $th->execute([$tid, $who, $sn ? $staffId($sn) : null, $msg, 0, $ts($when)]);
|
|
}
|
|
});
|
|
|
|
// ── Ticket-Zubehör: Schlagwörter, Zeiten, Verknüpfungen, Notizen, Anhang, Bewertung ─
|
|
$tid = fn(string $no) => (int) $one('SELECT id FROM tickets WHERE ticket_number = ?', [$no]);
|
|
$try('Schlagwörter', function () use ($p, $tid) {
|
|
$p->exec("INSERT INTO tags (name, color) VALUES ('Zahlung', '#0ea5e9'), ('Störung', '#ef4444'), ('Vertrag', '#8b5cf6'), ('VIP', '#f59e0b')");
|
|
foreach ([['260101', 'Zahlung'], ['260107', 'Zahlung'], ['260107', 'VIP'], ['260108', 'Störung'], ['260105', 'Störung'], ['260104', 'Vertrag'], ['260102', 'VIP']] as [$no, $tag])
|
|
$p->prepare('INSERT INTO ticket_tags (ticket_id, tag_id) VALUES (?, (SELECT id FROM tags WHERE name = ?))')->execute([$tid($no), $tag]);
|
|
});
|
|
$try('Zeiten und Notizen', function () use ($p, $ts, $tid, $staffId) {
|
|
foreach ([['260108', 'Mira Schulz', 45, 'Logs geprüft, Datenbank langsam'], ['260108', 'Mira Schulz', 30, 'Index neu aufgebaut'], ['260107', 'Lena Hartmann', 15, 'Zahlungseingang geprüft'], ['260105', 'Mira Schulz', 60, 'Absturz nachgestellt']] as [$no, $n, $m, $note])
|
|
$p->prepare('INSERT INTO ticket_time_entries (ticket_id, staff_id, minutes, note, logged_at) VALUES (?,?,?,?,?)')->execute([$tid($no), $staffId($n), $m, $note, $ts('-3 hours')]);
|
|
foreach ([['260107', 'Lena Hartmann', 'Buchung bei der Bank prüfen: Erstattung nötig, wenn tatsächlich doppelt abgebucht.', '-3 hours'], ['260108', 'Mira Schulz', 'Ursache vermutlich fehlender Index auf der Bestelltabelle.', '-7 hours']] as [$no, $n, $note, $w])
|
|
$p->prepare('INSERT INTO ticket_threads (ticket_id, author_type, staff_id, message, is_internal_note, created_at) VALUES (?,?,?,?,1,?)')->execute([$tid($no), 'staff', $staffId($n), $note, $ts($w)]);
|
|
});
|
|
$try('Verknüpfungen und Mitleser', function () use ($p, $ts, $tid, $lena) {
|
|
$p->prepare('INSERT INTO ticket_links (ticket_id, linked_ticket_id, link_type, created_by, created_at) VALUES (?,?,?,?,?)')->execute([$tid('260107'), $tid('260101'), 'related', $lena, $ts('-2 hours')]);
|
|
$p->prepare('INSERT INTO ticket_collaborators (ticket_id, email, name, added_by, created_at) VALUES (?,?,?,?,?)')->execute([$tid('260101'), 'buchhaltung@kunde.example', 'Buchhaltung Muster GmbH', $lena, $ts('-2 hours')]);
|
|
$p->prepare('INSERT INTO problems (title, description, root_cause, workaround, status, created_by, updated_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)')->execute(['Webseite langsam am Morgen', 'Viele Kunden melden lange Ladezeiten.', 'Fehlender Datenbank-Index', 'Seite neu laden hilft kurzzeitig', 'workaround', $lena, $lena, $ts('-8 hours'), $ts('-2 hours')]);
|
|
$pid = (int) $p->lastInsertId();
|
|
foreach (['260108', '260102'] as $no) $p->prepare('INSERT INTO problem_tickets (problem_id, ticket_id, linked_by, linked_at) VALUES (?,?,?,?)')->execute([$pid, $tid($no), $lena, $ts('-2 hours')]);
|
|
});
|
|
$try('Geplante Antwort', function () use ($p, $ts, $tid, $staffId) {
|
|
$p->prepare("INSERT INTO scheduled_replies (ticket_id, staff_id, message, set_status, send_at, state, created_at) VALUES (?,?,?,?,?,'pending',?)")->execute([$tid('260103'), $staffId('Tim Roth'), '<p>Hallo Frau Klein, kurze Nachfrage: Konnten Sie die Adresse inzwischen ändern?</p>', 'pending', $ts('tomorrow 09:00'), $ts('-1 hour')]);
|
|
});
|
|
$try('Bewertungen', function () use ($p, $ts, $tid, $staffId) {
|
|
$i = 0;
|
|
foreach ([['259901', 'Tim Roth', 5, 'Super schnelle Hilfe, danke!'], ['259902', 'Lena Hartmann', 4, null], ['259903', 'Tim Roth', 5, 'Ersatz war sofort da.'], ['259904', 'Lena Hartmann', 3, 'Hat etwas gedauert.'], ['259905', 'Tim Roth', 5, null], ['259906', 'Lena Hartmann', 4, 'Freundlich und hilfreich.']] as [$no, $n, $r, $c]) {
|
|
$p->prepare('INSERT INTO ticket_csat (ticket_id, token, staff_id, rating, comment, sent_at, answered_at) VALUES (?,?,?,?,?,?,?)')->execute([$tid($no), sprintf('%040x', ++$i * 7919), $staffId($n), $r, $c, $ts('-' . (3 + $i) . ' days'), $ts('-' . (3 + $i) . ' days +2 hours')]);
|
|
}
|
|
});
|
|
$try('Anhang', function () use ($p, $ts, $tmp, $tid) {
|
|
$png = base64_decode('iVBORw0KGgoAAAANSUhEUgAAAGQAAAA8CAIAAAAv+yGaAAAAZ0lEQVR4nO3PQQ0AIBAEoFt+zWoRDmZgUkCSM0I5vUPzbBQKpyC6V2gv0N6hvUN7h/YO7R3aO7R3aO/Q3qG9Q3uH9g7tHdo7tHdo79DeIb1Deof0DukdsvrBR/YDBwAAAABJRU5ErkJggg==');
|
|
@file_put_contents("$tmp/uploads/beispiel-fehlermeldung.png", $png);
|
|
$thread = (int) $p->query("SELECT id FROM ticket_threads WHERE ticket_id = {$tid('260105')} AND author_type = 'customer' ORDER BY id LIMIT 1")->fetchColumn();
|
|
$p->prepare('INSERT INTO ticket_attachments (thread_id, original_name, stored_name, filesize, created_at) VALUES (?,?,?,?,?)')->execute([$thread, 'Fehlermeldung.png', 'beispiel-fehlermeldung.png', strlen($png), $ts('-5 hours')]);
|
|
$p->prepare('INSERT INTO ticket_attachments (thread_id, original_name, stored_name, filesize, created_at) VALUES (?,?,?,?,?)')->execute([$thread, 'Protokoll.txt', 'beispiel-protokoll.txt', 2048, $ts('-5 hours')]);
|
|
@file_put_contents("$tmp/uploads/beispiel-protokoll.txt", str_repeat("Beispielprotokoll\n", 100));
|
|
});
|
|
|
|
// ── Aufgaben, Ansichten, Berichte ───────────────────────────────────────────
|
|
$try('Aufgaben', function () use ($p, $ts, $staffId, $tid) {
|
|
$ins = $p->prepare('INSERT INTO tasks (ticket_id, title, description, assigned_to, status, due_at, created_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)');
|
|
foreach ([[$tid('260108'), 'Index auf Bestelltabelle prüfen', 'Mit dem Hosting klären', 'Mira Schulz', 'open', '+1 day'], [null, 'Textbausteine überarbeiten', 'Neue Vorlagen für Rückfragen', 'Tim Roth', 'open', '+3 days'], [$tid('260107'), 'Erstattung prüfen', null, 'Lena Hartmann', 'open', 'today 16:00'], [null, 'Monatsbericht vorbereiten', null, 'Lena Hartmann', 'done', '-2 days']] as [$t, $title, $d, $n, $st, $due])
|
|
$ins->execute([$t, $title, $d, $staffId($n), $st, $ts($due), $staffId('Lena Hartmann'), $ts('-3 days'), $ts('-1 day')]);
|
|
});
|
|
$try('Ansichten', function () use ($p, $ts, $lena, $dept) {
|
|
$ins = $p->prepare('INSERT INTO saved_queues (staff_id, name, filters, sort_order, created_at, is_shared, updated_at) VALUES (?,?,?,?,?,?,?)');
|
|
foreach ([['Meine überfälligen Tickets', ['assigned' => 'me', 'sla' => 'overdue'], 0], ['Unzugewiesen', ['assigned' => 'none', 'tab' => 'open'], 1], ['Nur Technik', ['tab' => 'open', 'departmentId' => (string) $dept('Technik')], 1]] as $i => [$n, $f, $sh])
|
|
$ins->execute([$lena, $n, json_encode($f), $i, $ts('-10 days'), $sh, $ts('-10 days')]);
|
|
});
|
|
$try('Berichte', function () use ($p, $ts, $lena) {
|
|
$cfg = fn($dim, $m) => json_encode(['dimension' => $dim, 'metrics' => $m, 'from' => date('Y-m-d', strtotime('-30 days')), 'to' => date('Y-m-d'), 'filters' => [], 'sort' => 'label', 'desc' => false]);
|
|
$p->prepare('INSERT INTO saved_reports (staff_id, name, config, is_shared, created_at, updated_at) VALUES (?,?,?,?,?,?)')->execute([$lena, 'Monatsbericht je Abteilung', $cfg('department', ['total', 'open', 'resolved']), 1, $ts('-12 days'), $ts('-12 days')]);
|
|
$rid = (int) $p->lastInsertId();
|
|
$p->prepare('INSERT INTO saved_reports (staff_id, name, config, is_shared, created_at, updated_at) VALUES (?,?,?,?,?,?)')->execute([$lena, 'Tickets je Mitarbeiter', $cfg('agent', ['total', 'resolved', 'avg_first_response']), 0, $ts('-8 days'), $ts('-8 days')]);
|
|
$p->prepare("INSERT INTO report_schedules (report_id, staff_id, recipients, frequency, weekday, monthday, send_time, period, enabled, next_run_at, created_at, only_if_data, skip_unchanged) VALUES (?,?,?,?,?,?,?,?,1,?,?,1,1)")->execute([$rid, $lena, 'leitung@demo.example', 'weekly', 1, 1, '08:00', 'last_week', $ts('next monday 08:00'), $ts('-6 days')]);
|
|
});
|
|
|
|
// ── Integrationen, E-Mail, Zeiten, Regeln ───────────────────────────────────
|
|
$try('API und Webhooks', function () use ($p, $ts, $lena) {
|
|
$p->prepare('INSERT INTO api_keys (name, key_hash, key_prefix, can_create_tickets, can_read_tickets, ip_whitelist, is_active, last_used_at, created_by, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)')->execute(['Kontaktformular Website', hash('sha256', 'demo-1'), 'dh_3f9a', 1, 1, null, 1, $ts('-2 hours'), $lena, $ts('-30 days')]);
|
|
$p->prepare('INSERT INTO api_keys (name, key_hash, key_prefix, can_create_tickets, can_read_tickets, ip_whitelist, is_active, last_used_at, created_by, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)')->execute(['Minecraft-Server', hash('sha256', 'demo-2'), 'dh_77c1', 1, 0, '203.0.113.10', 1, $ts('-1 day'), $lena, $ts('-20 days')]);
|
|
$p->prepare('INSERT INTO webhooks (name, url, secret_enc, events, is_active, last_status, last_delivery_at, fail_count, created_at) VALUES (?,?,?,?,?,?,?,?,?)')->execute(['Chat-Benachrichtigung', 'https://hooks.example.org/deskhub', 'demo', 'ticket.created,ticket.replied', 1, 200, $ts('-1 hour'), 0, $ts('-15 days')]);
|
|
});
|
|
$try('E-Mail', function () use ($p, $ts, $dept, $prio) {
|
|
$ins = $p->prepare('INSERT INTO email_addresses (email, label, department_id, priority_id, is_default, is_active, notes, created_at) VALUES (?,?,?,?,?,1,?,?)');
|
|
$ins->execute(['support@demo.example', 'Support', $dept('Allgemeiner Support'), $prio('Normal'), 1, 'Standardadresse', $ts('-60 days')]);
|
|
$ins->execute(['buchhaltung@demo.example', 'Buchhaltung', $dept('Buchhaltung'), $prio('Hoch'), 0, null, $ts('-60 days')]);
|
|
$ins->execute(['technik@demo.example', 'Technik', $dept('Technik'), $prio('Dringend'), 0, null, $ts('-60 days')]);
|
|
$p->prepare('INSERT INTO imap_accounts (label, host, port, encryption, username, password_enc, folder, after_fetch, department_id, priority_id, is_active, last_run_at, last_success_at, created_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)')
|
|
->execute(['Support-Postfach', 'imap.demo.example', 993, 'ssl', 'support@demo.example', 'demo', 'INBOX', 'mark_read', $dept('Allgemeiner Support'), $prio('Normal'), 1, $ts('-4 minutes'), $ts('-4 minutes'), $ts('-60 days')]);
|
|
$p->prepare("INSERT INTO mail_queue (to_email, subject, body, status, attempts, next_attempt_at, last_error, created_at) VALUES (?,?,?,?,?,?,?,?)")->execute(['erika.muster@kunde.example', 'Ihre Anfrage #260107', '<p>Text</p>', 'waiting', 2, $ts('+20 minutes'), 'Verbindung zum Mailserver fehlgeschlagen', $ts('-30 minutes')]);
|
|
});
|
|
$try('Zeiten', function () use ($p, $ts) {
|
|
$p->exec('DELETE FROM business_hours');
|
|
foreach (range(0, 6) as $w) $p->prepare('INSERT INTO business_hours (weekday, is_open, open_time, close_time) VALUES (?,?,?,?)')->execute([$w, in_array($w, [1, 2, 3, 4, 5], true) ? 1 : 0, '08:00:00', '17:00:00']);
|
|
foreach ([['2026-12-25', '1. Weihnachtstag'], ['2026-12-26', '2. Weihnachtstag'], ['2027-01-01', 'Neujahr']] as [$d, $n]) $p->prepare('INSERT INTO business_holidays (holiday_date, name) VALUES (?,?)')->execute([$d, $n]);
|
|
});
|
|
$try('Regeln', function () use ($p, $ts, $dept, $prio, $staffId, $lena) {
|
|
$p->prepare('INSERT INTO ticket_rules (name, match_type, is_active, sort_order, created_at, notes) VALUES (?,?,?,?,?,?)')->execute(['Rechnungs-Anfragen an die Buchhaltung', 'any', 1, 1, $ts('-40 days'), 'Betreff enthält Rechnung oder Zahlung']);
|
|
$r = (int) $p->lastInsertId();
|
|
foreach (['Rechnung', 'Zahlung'] as $w) $p->prepare('INSERT INTO ticket_rule_conditions (rule_id, field, operator, value) VALUES (?,?,?,?)')->execute([$r, 'subject', 'contains', $w]);
|
|
$p->prepare('INSERT INTO ticket_rule_actions (rule_id, action_type, action_value) VALUES (?,?,?)')->execute([$r, 'set_department', (string) $dept('Buchhaltung')]);
|
|
$p->prepare('INSERT INTO ticket_rule_actions (rule_id, action_type, action_value) VALUES (?,?,?)')->execute([$r, 'set_priority', (string) $prio('Hoch')]);
|
|
$p->prepare("INSERT INTO escalation_rules (name, is_active, trigger_type, trigger_hours, condition_status, action_set_priority_id, action_notify_admin, action_notify_staff, sort_order, created_at) VALUES (?,?,?,?,?,?,?,?,?,?)")->execute(['Unbeantwortet nach 8 Stunden: Priorität erhöhen', 1, 'no_reply', 8, 'open', $prio('Dringend'), 1, 1, 1, $ts('-40 days')]);
|
|
$p->prepare("INSERT INTO escalation_rules (name, is_active, trigger_type, trigger_hours, action_notify_admin, sort_order, created_at) VALUES (?,?,?,?,?,?,?)")->execute(['Überfällig: Admin informieren', 1, 'overdue', 0, 1, 2, $ts('-40 days')]);
|
|
$p->prepare('INSERT INTO recurring_tickets (subject, message, department_id, priority_id, assigned_staff_id, interval_days, next_run, is_active, created_by, created_at) VALUES (?,?,?,?,?,?,?,1,?,?)')->execute(['Monatliche Checkliste Backups', 'Backups prüfen, Speicherplatz kontrollieren, Updates einspielen.', $dept('Technik'), $prio('Normal'), $staffId('Mira Schulz'), 30, $ts('+12 days'), $lena, $ts('-18 days')]);
|
|
});
|
|
$try('Seiten', function () use ($p, $ts) {
|
|
$ins = $p->prepare('INSERT INTO site_pages (slug, title, content, show_in_nav, is_active, sort_order, created_at) VALUES (?,?,?,?,1,?,?)');
|
|
$ins->execute(['impressum', 'Impressum', '<p><b>Muster Support GmbH</b><br>Beispielstraße 1<br>12345 Beispielstadt</p><p>Alle Angaben sind erfunden.</p>', 1, 1, $ts('-60 days')]);
|
|
$ins->execute(['datenschutz', 'Datenschutz', '<p>Hier steht in einer echten Installation eure Datenschutzerklärung.</p>', 1, 2, $ts('-60 days')]);
|
|
$ins->execute(['agb', 'Allgemeine Geschäftsbedingungen', '<p>Beispieltext.</p>', 0, 3, $ts('-60 days')]);
|
|
});
|
|
$try('Statusseite', function () use ($p, $ts, $lena) {
|
|
$n = (int) $p->query('SELECT COUNT(*) FROM status_components')->fetchColumn();
|
|
if ($n === 0) {
|
|
foreach ([['Website', 'operational'], ['Kundenportal', 'operational'], ['E-Mail-Versand', 'degraded'], ['Telefon-Hotline', 'operational']] as $i => [$nm, $st])
|
|
$p->prepare('INSERT INTO status_components (name, status, sort_order, created_at, is_enabled) VALUES (?,?,?,?,1)')->execute([$nm, $st, $i, $ts('-60 days')]);
|
|
}
|
|
$cid = (int) $p->query("SELECT id FROM status_components ORDER BY id LIMIT 1 OFFSET 2")->fetchColumn() ?: null;
|
|
$p->prepare('INSERT INTO status_incidents (title, body, status, impact, component_id, created_by, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?)')->execute(['Verzögerter E-Mail-Versand', 'Mails kommen mit Verspätung an. Wir arbeiten an der Ursache.', 'monitoring', 'minor', $cid, $lena, $ts('-3 hours'), $ts('-1 hour')]);
|
|
$iid = (int) $p->lastInsertId();
|
|
$p->prepare('INSERT INTO status_incident_updates (incident_id, status, body, created_by, created_at) VALUES (?,?,?,?,?)')->execute([$iid, 'investigating', 'Wir haben das Problem erkannt.', $lena, $ts('-3 hours')]);
|
|
$p->prepare('INSERT INTO status_incident_updates (incident_id, status, body, created_by, created_at) VALUES (?,?,?,?,?)')->execute([$iid, 'monitoring', 'Ein Neustart des Mailservers hat geholfen. Wir beobachten.', $lena, $ts('-1 hour')]);
|
|
});
|
|
$try('Mitarbeiter-Zubehör', function () use ($p, $ts, $staffId, $lena, $tim) {
|
|
foreach ([1, 2, 3, 4, 5] as $w) $p->prepare('INSERT INTO staff_shifts (staff_id, weekday, start_time, end_time, is_active) VALUES (?,?,?,?,1)')->execute([$staffId('Tim Roth'), $w, '08:00', '16:00']);
|
|
$p->prepare('INSERT INTO staff_chat_messages (from_staff_id, to_staff_id, message, is_read, created_at) VALUES (?,?,?,?,?)')->execute([$staffId('Tim Roth'), $staffId('Lena Hartmann'), 'Kannst du kurz auf 260105 schauen? Android 14.', 1, $ts('-2 hours')]);
|
|
$p->prepare('INSERT INTO staff_chat_messages (from_staff_id, to_staff_id, message, is_read, created_at) VALUES (?,?,?,?,?)')->execute([$staffId('Lena Hartmann'), $staffId('Tim Roth'), 'Mache ich, danke dir!', 1, $ts('-110 minutes')]);
|
|
$p->prepare('INSERT INTO banlist (match_type, value, reason, created_by, created_at) VALUES (?,?,?,?,?)')->execute(['email', 'spam@bad-example.org', 'Wiederholt Werbung', $lena, $ts('-14 days')]);
|
|
$p->prepare('INSERT INTO banlist (match_type, value, reason, created_by, created_at) VALUES (?,?,?,?,?)')->execute(['domain', 'wegwerf-mail.example', 'Wegwerf-Adressen', $lena, $ts('-14 days')]);
|
|
foreach ([['Anmeldung', 'Mitarbeiter angemeldet: Tim Roth', '-1 hours'], ['Einstellungen', 'Systemeinstellungen geändert', '-1 days'], ['Mitarbeiter', 'Mitarbeiter angelegt: Mira Schulz', '-20 days'], ['Backup', 'Backup erstellt (automatisch)', '-2 days'], ['E-Mail', 'Postfach „Support-Postfach“ abgerufen: 3 neue Mails', '-4 minutes']] as [$k, $a, $w])
|
|
$p->prepare('INSERT INTO admin_audit_log (staff_id, action, created_at) VALUES (?,?,?)')->execute([$lena, $a, $ts($w)]);
|
|
$ev = $p->prepare('INSERT INTO login_events (staff_id, email, event, channel, ip, place, device, detail, created_at) VALUES (?,?,?,?,?,?,?,?,?)');
|
|
$ev->execute([$lena, 'lena@demo.example', 'success', 'web', '203.0.113.5', 'Netz 203.0.x.x', 'Chrome auf Windows', 'Angemeldet', $ts('-1 hour')]);
|
|
$ev->execute([$tim, 'tim@demo.example', 'new_device', 'app', '203.0.113.9', 'Netz 203.0.x.x', 'DeskHub-App', 'Neues Gerät', $ts('-1 day')]);
|
|
$ev->execute([$tim, 'tim@demo.example', 'failed', 'web', '198.51.100.7', 'Netz 198.51.x.x', 'Firefox auf Linux', 'Falsches Passwort', $ts('-2 days')]);
|
|
});
|
|
$try('Einstellungen', function () use ($set) {
|
|
foreach (['cron_fetch_emails_last_run' => date('Y-m-d H:i:s', strtotime('+30 days')), 'csat_enabled' => '1', 'csat_delay_minutes' => '30', 'business_hours_enabled' => '1', 'status_page_enabled' => '1', 'auto_assign_on_open' => '1', 'kb_enabled' => '1', 'live_chat_enabled' => '1', 'display_name' => 'Muster Support', 'wait_reminder_enabled' => '1', 'wait_reminder_hours' => '4'] as $k => $v) $set->execute([$k, $v]);
|
|
});
|
|
|
|
// Lenas Woche: ein paar gelöste Tickets mit Bewertungen, damit „Meine Woche“ gefüllt aussieht; eine Erwähnung
|
|
$try('Meine Woche', function () use ($p, $ts, $dept, $prio, $lena, $tim) {
|
|
$tk = $p->prepare('INSERT INTO tickets (ticket_number, subject, requester_name, requester_email, department_id, priority_id, status, source, assigned_staff_id, created_at, updated_at, resolution_due_at, first_response_due_at, first_responded_at, resolved_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
|
|
$th = $p->prepare('INSERT INTO ticket_threads (ticket_id, author_type, staff_id, message, is_internal_note, created_at) VALUES (?,?,?,?,0,?)');
|
|
$monday = strtotime('monday this week 09:00');
|
|
foreach ([['260021', 'Rechnung nachgereicht', 0, 3, 5, 'Buchhaltung'], ['260022', 'Adresse geändert', 0, 2, 5, 'Allgemeiner Support'], ['260023', 'Zugang eingerichtet', 1, 5, 4, 'Allgemeiner Support'], ['260024', 'Passwort zurückgesetzt', 1, 1, 5, 'Allgemeiner Support'], ['260025', 'Frage zur Lieferung', 2, 4, 3, 'Allgemeiner Support']] as [$no, $subj, $dayOff, $hours, $rating, $dn]) {
|
|
$created = date('Y-m-d H:i:s', $monday + $dayOff * 86400);
|
|
$resolved = date('Y-m-d H:i:s', $monday + $dayOff * 86400 + $hours * 3600);
|
|
if (strtotime($resolved) > time()) { $created = date('Y-m-d H:i:s', time() - 7200 - $hours * 3600); $resolved = date('Y-m-d H:i:s', time() - 3600); }
|
|
$tk->execute([$no, $subj, 'Kunde ' . $no, 'k' . $no . '@kunde.example', $dept($dn), $prio('Normal'), 'resolved', 'web', $lena, $created, $resolved, date('Y-m-d H:i:s', strtotime($created) + 86400), date('Y-m-d H:i:s', strtotime($created) + 3600), date('Y-m-d H:i:s', strtotime($created) + 1500 + $hours * 300), $resolved]);
|
|
$tid = (int) $p->lastInsertId();
|
|
$th->execute([$tid, 'customer', null, 'Anfrage: ' . $subj, $created]);
|
|
$th->execute([$tid, 'staff', $lena, 'Erledigt, danke für die Geduld.', date('Y-m-d H:i:s', strtotime($created) + 1500 + $hours * 300)]);
|
|
$p->prepare('INSERT INTO ticket_csat (ticket_id, token, staff_id, rating, comment, sent_at, answered_at) VALUES (?,?,?,?,?,?,?)')->execute([$tid, sprintf('%040x', crc32($no) * 31), $lena, $rating, null, $resolved, $resolved]);
|
|
}
|
|
$t = (int) $p->query("SELECT id FROM tickets WHERE ticket_number = '260108'")->fetchColumn();
|
|
$thr = (int) $p->query("SELECT id FROM ticket_threads WHERE ticket_id = {$t} AND is_internal_note = 1 ORDER BY id LIMIT 1")->fetchColumn();
|
|
$p->prepare('INSERT INTO staff_mentions (ticket_id, thread_id, mentioned_staff_id, mentioned_by_staff_id, is_read, created_at) VALUES (?,?,?,?,0,?)')->execute([$t, $thr, $lena, $tim, $ts('-1 hour')]);
|
|
});
|
|
|
|
echo "Handbuch-Daten ergänzt.\n";
|
|
foreach ($warn as $w) echo "WARNUNG $w\n";
|