- Real-time updates (Laravel Reverb): live operator queue, live ticket chat/detail updates for operator and client, periodic fallback refresh with a visible countdown as a backstop for dropped websocket connections. - SLA automation rules (Admin > Automatyzacja SLA): act on a ticket after N minutes of customer silence (change priority/status/team/assignee), evaluated every 15 minutes, reusing TicketService's own setters so automated changes get the same history/notification/broadcast a manual change would. - New notification: every operator on a matching team gets notified when a new ticket lands in one of their subcategories. - BookStack knowledge-base sidebar now also shown on the client's own ticket view (previously operator-only); suggestions everywhere now load in after first paint instead of blocking it. - Client ticket view: shows assigned operator + team; page widened to match the operator's. - Notification bell shows unread only; read notifications disappear instead of just dimming. - Stats dashboard: sectioned layout, new breakdowns (by subcategory, CSAT by team/operator, top clients, client x subcategory cross-tab). - Mobile: nav dropdowns (theme/notifications/profile) now expand full width instead of overflowing off-screen below 640px. - Fixed two bugs that silently disabled all real-time updates (missing CSRF header on Echo's private-channel auth; a script-load-order race that could miss the livewire:init event) and the mariadb healthcheck (world-writable credentials file on this stack's NFS mount). - Assorted test-suite fixes (roles virtual attribute needs the roles table seeded; a few missing seeds/wrong assertions found along the way). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
456 lines
27 KiB
PHP
456 lines
27 KiB
PHP
<?php
|
|
|
|
namespace Database\Seeders;
|
|
|
|
use App\Models\Category;
|
|
use App\Models\CustomField;
|
|
use App\Models\EmailTemplate;
|
|
use App\Models\NotificationSetting;
|
|
use App\Models\Priority;
|
|
use App\Models\ReplyQuickAction;
|
|
use App\Models\ResponseTemplate;
|
|
use App\Models\Role;
|
|
use App\Models\SlaRule;
|
|
use App\Models\Status;
|
|
use App\Models\Subcategory;
|
|
use App\Models\Team;
|
|
use App\Models\User;
|
|
use App\Models\UserField;
|
|
use App\Support\Settings;
|
|
use Illuminate\Database\Seeder;
|
|
|
|
class DatabaseSeeder extends Seeder
|
|
{
|
|
/** @var array<string, Subcategory> */
|
|
protected array $subs = [];
|
|
|
|
/** @var array<string, CustomField> */
|
|
protected array $fields = [];
|
|
|
|
/**
|
|
* Seeds the application with the same reference data currently
|
|
* configured in production (categories, custom fields, statuses,
|
|
* priorities/SLA, teams, templates, quick actions, settings) — no ticket
|
|
* data, and only a single local admin account instead of real users.
|
|
*/
|
|
public function run(): void
|
|
{
|
|
$this->seedRoles();
|
|
$this->seedStatusesAndPriorities();
|
|
$this->seedCategories();
|
|
$this->seedCustomFields();
|
|
$this->seedTeams();
|
|
$this->seedUserFields();
|
|
$this->seedReplyQuickActions();
|
|
$this->seedResponseTemplates();
|
|
$this->seedEmailTemplatesAndNotifications();
|
|
$this->seedSettings();
|
|
$this->seedAdminUser();
|
|
}
|
|
|
|
protected function seedRoles(): void
|
|
{
|
|
foreach ([
|
|
['key' => 'client', 'label' => 'Klient'],
|
|
['key' => 'operator', 'label' => 'Operator'],
|
|
['key' => 'admin', 'label' => 'Administrator'],
|
|
] as $role) {
|
|
Role::query()->firstOrCreate(['key' => $role['key']], $role);
|
|
}
|
|
}
|
|
|
|
protected function seedStatusesAndPriorities(): void
|
|
{
|
|
foreach ([
|
|
['key' => 'new', 'label' => 'Nowe', 'color' => '#e90a0f', 'stage' => 'new', 'locked' => true, 'sort_order' => 1],
|
|
['key' => 'open', 'label' => 'Otwarty', 'color' => '#b5abfc', 'stage' => 'open', 'locked' => true, 'sort_order' => 2],
|
|
['key' => 'in_progress', 'label' => 'W trakcie', 'color' => '#2fd744', 'stage' => 'open', 'locked' => false, 'sort_order' => 3],
|
|
['key' => 'waiting_customer', 'label' => 'Oczekuje na klienta', 'color' => '#82ccd0', 'stage' => 'open', 'locked' => false, 'sort_order' => 4],
|
|
['key' => 'waiting_operator', 'label' => 'Oczekuje na operatora', 'color' => '#b5abfc', 'stage' => 'open', 'locked' => false, 'sort_order' => 5],
|
|
['key' => 'on_hold', 'label' => 'Wstrzymany', 'color' => '#b5abfc', 'stage' => 'open', 'locked' => false, 'sort_order' => 6],
|
|
['key' => 'closed', 'label' => 'Zamknięte', 'color' => '#75798c', 'stage' => 'closed', 'locked' => true, 'sort_order' => 7],
|
|
] as $status) {
|
|
Status::query()->create($status);
|
|
}
|
|
|
|
foreach ([
|
|
['key' => 'critical', 'label' => 'Krytyczny', 'color' => '#e60000', 'sort_order' => 1],
|
|
['key' => 'high', 'label' => 'Wysoki', 'color' => '#0008f0', 'sort_order' => 2],
|
|
['key' => 'medium', 'label' => 'Średni', 'color' => '#fd08f4', 'sort_order' => 3],
|
|
['key' => 'low', 'label' => 'Niski', 'color' => '#1be748', 'sort_order' => 4],
|
|
['key' => 'none', 'label' => 'Brak', 'color' => '#9184d9', 'sort_order' => 5],
|
|
] as $priority) {
|
|
Priority::query()->create($priority);
|
|
}
|
|
|
|
foreach ([
|
|
['critical', 30, 240],
|
|
['high', 60, 480],
|
|
['medium', 240, 1440],
|
|
['low', 480, 4320],
|
|
['none', 0, 0],
|
|
] as [$priority, $response, $resolution]) {
|
|
SlaRule::query()->create([
|
|
'priority_key' => $priority,
|
|
'response_mins' => $response,
|
|
'resolution_mins' => $resolution,
|
|
]);
|
|
}
|
|
}
|
|
|
|
protected function seedCategories(): void
|
|
{
|
|
$data = [
|
|
'IT-Pomoc' => [
|
|
'description' => 'Pomoc techniczna z sprzętem lub oprogramowaniem.',
|
|
'subs' => [
|
|
'aktualizacje-systemu' => ['Aktualizacje systemu', 'Aktualizacja systemu lub konkretnej aplikacji.'],
|
|
'backup' => ['Backup i odzyskiwanie danych', 'Tworzenie kopii zapasowych i przywracanie utraconych danych.'],
|
|
'drukarki' => ['Drukarki i skanery', 'Problemy z drukarkami, skanerami i urządzeniami wielofunkcyjnymi.'],
|
|
'incydent' => ['Incydent bezpieczeństwa', 'Zgłaszanie podejrzanych zdarzeń i naruszeń bezpieczeństwa IT.'],
|
|
'it-inne' => ['Inne', 'Pozostałe sprawy związane z IT, niepasujące do innych kategorii.'],
|
|
'internet' => ['Internet', 'Problemy z dostępem do internetu i połączeniem sieciowym.'],
|
|
'konta' => ['Konta i dostępy', 'Zakładanie, modyfikacja i blokowanie kont oraz uprawnień w systemach.'],
|
|
'oprogramowanie' => ['Oprogramowanie', 'Instalacja, aktualizacja i błędy aplikacji oraz oprogramowania.'],
|
|
'poczta' => ['Poczta', 'Problemy ze skrzynką pocztową, wysyłką i odbiorem wiadomości e-mail.'],
|
|
'pulpit-zdalny' => ['Pulpit zdalny', 'Dostęp i problemy z pulpitem zdalnym oraz zdalnym połączeniem do komputera.'],
|
|
'reset-hasla' => ['Reset hasła', 'Resetowanie zapomnianych lub zablokowanych haseł do systemów.'],
|
|
'siec-wewnetrzna' => ['Sieć wewnętrzna', 'Problemy z siecią lokalną i infrastrukturą sieciową w firmie.'],
|
|
'sprzet' => ['Sprzęt komputerowy', 'Awarie i wsparcie dla komputerów, laptopów i akcesoriów.'],
|
|
'telefon' => ['Telefon', 'Problemy z telefonią stacjonarną i komórkową.'],
|
|
'vpn' => ['VPN', 'Problemy z połączeniem VPN i dostępem zdalnym do zasobów firmy.'],
|
|
],
|
|
],
|
|
'Zamówienia' => [
|
|
'description' => 'Zamawianie sprzętu, oprogramowania, licencji i materiałów biurowych.',
|
|
'subs' => [
|
|
'zam-sprzet' => ['Zamówienie sprzętu IT', 'Wnioski o zakup nowego sprzętu komputerowego i akcesoriów.'],
|
|
'zam-oprogramowanie' => ['Zamówienie oprogramowania/licencji', 'Wnioski o zakup oprogramowania i licencji.'],
|
|
'zam-materialy' => ['Zamówienie materiałów biurowych', 'Zamawianie artykułów i materiałów biurowych.'],
|
|
'zam-usluga' => ['Zamówienie usługi zewnętrznej', 'Zlecanie usług realizowanych przez firmy zewnętrzne.'],
|
|
],
|
|
],
|
|
'Administracja' => [
|
|
'description' => 'Sprawy administracyjne, kadrowe i biurowe.',
|
|
'subs' => [
|
|
'wnioski-dokumenty' => ['Wnioski i dokumenty', 'Składanie wniosków i obieg dokumentów administracyjnych.'],
|
|
'sprawy-kadrowe' => ['Sprawy kadrowe', 'Sprawy związane z zatrudnieniem, urlopami i dokumentacją pracowniczą.'],
|
|
'obsluga-biura' => ['Obsługa biura', 'Sprawy związane z bieżącą obsługą i funkcjonowaniem biura.'],
|
|
'dostep-budynek' => ['Dostęp do budynku / karty', 'Wydawanie i zarządzanie kartami dostępu do budynku.'],
|
|
],
|
|
],
|
|
'Delegacje' => [
|
|
'description' => 'Wnioski i rozliczenia związane z podróżami służbowymi.',
|
|
'subs' => [
|
|
'del-krajowa' => ['Wniosek o delegację krajową', 'Zgłaszanie wyjazdów służbowych na terenie kraju.'],
|
|
'del-zagraniczna' => ['Wniosek o delegację zagraniczną', 'Zgłaszanie wyjazdów służbowych za granicę.'],
|
|
'del-rozliczenie' => ['Rozliczenie kosztów delegacji', 'Rozliczanie kosztów poniesionych podczas delegacji.'],
|
|
'del-zaliczka' => ['Zaliczka na delegację', 'Wnioski o zaliczkę na poczet wydatków związanych z delegacją.'],
|
|
],
|
|
],
|
|
];
|
|
|
|
foreach ($data as $name => $cat) {
|
|
$category = Category::query()->create(['name' => $name, 'description' => $cat['description']]);
|
|
|
|
foreach ($cat['subs'] as $slug => [$subName, $description]) {
|
|
$this->subs[$slug] = $category->subcategories()->create([
|
|
'name' => $subName,
|
|
'description' => $description,
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function seedCustomFields(): void
|
|
{
|
|
$fields = [
|
|
'teamviewer' => ['Teamviewer-ID', 'text', null, ['aktualizacje-systemu', 'drukarki', 'oprogramowanie', 'pulpit-zdalny', 'siec-wewnetrzna', 'sprzet', 'vpn']],
|
|
'nazwa-systemu' => ['Nazwa systemu/aplikacji', 'text', null, ['aktualizacje-systemu', 'konta']],
|
|
'lokalizacja-biura' => ['Lokalizacja / numer biura', 'text', null, ['internet', 'siec-wewnetrzna']],
|
|
'lokalizacja-danych' => ['Lokalizacja danych do odzyskania', 'text', null, ['backup']],
|
|
'data-utraty' => ['Data utraty danych', 'date', null, ['backup']],
|
|
'model-urzadzenia' => ['Model urządzenia', 'text', null, ['drukarki']],
|
|
'typ-incydentu' => ['Typ incydentu', 'select', ['Phishing', 'Złośliwe oprogramowanie', 'Wyciek danych', 'Nieautoryzowany dostęp', 'Inne'], ['incydent']],
|
|
'dane-wrazliwe' => ['Dane wrażliwe ujawnione', 'checkbox', null, ['incydent']],
|
|
'opis-sprawy' => ['Opis sprawy', 'textarea', null, ['it-inne']],
|
|
'rodzaj-problemu-net' => ['Rodzaj problemu', 'select', ['Brak połączenia', 'Wolne łącze', 'Przerywane połączenie', 'Inne'], ['internet']],
|
|
'rodzaj-dostepu' => ['Rodzaj dostępu', 'select', ['Nowe konto', 'Zmiana uprawnień', 'Zablokowanie konta', 'Inne'], ['konta']],
|
|
'nazwa-aplikacji' => ['Nazwa aplikacji', 'text', null, ['oprogramowanie']],
|
|
'adres-email' => ['Adres e-mail', 'text', null, ['poczta']],
|
|
'rodzaj-problemu-mail' => ['Rodzaj problemu', 'select', ['Nie odbiera', 'Nie wysyła', 'Spam/Phishing', 'Skrzynka pełna', 'Inne'], ['poczta']],
|
|
'adres-stacji-zdalnej' => ['Adres/nazwa stacji zdalnej', 'text', null, ['pulpit-zdalny']],
|
|
'nazwa-systemu-konta' => ['Nazwa systemu/konta', 'text', null, ['reset-hasla']],
|
|
'numer-inwentarzowy' => ['Numer inwentarzowy sprzętu', 'text', null, ['sprzet']],
|
|
'numer-telefonu' => ['Numer telefonu służbowego', 'text', null, ['telefon']],
|
|
'model-telefonu' => ['Model telefonu', 'text', null, ['telefon']],
|
|
'lokalizacja-pracy-zdalnej' => ['Lokalizacja pracy zdalnej', 'text', null, ['vpn']],
|
|
'kwota' => ['Szacowana kwota (PLN)', 'number', null, ['zam-sprzet', 'zam-oprogramowanie', 'zam-materialy', 'zam-usluga']],
|
|
'centrum-kosztow' => ['Numer centrum kosztów', 'text', null, ['zam-sprzet', 'zam-oprogramowanie', 'zam-materialy', 'zam-usluga']],
|
|
'zgoda-przelozonego' => ['Zgoda przełożonego uzyskana', 'checkbox', null, ['zam-sprzet', 'zam-oprogramowanie', 'zam-materialy', 'zam-usluga']],
|
|
'rodzaj-wniosku' => ['Rodzaj wniosku', 'text', null, ['wnioski-dokumenty', 'sprawy-kadrowe', 'obsluga-biura', 'dostep-budynek']],
|
|
'termin-realizacji' => ['Termin realizacji', 'date', null, ['wnioski-dokumenty', 'sprawy-kadrowe', 'obsluga-biura', 'dostep-budynek']],
|
|
'miejsce-delegacji' => ['Miejsce delegacji', 'text', null, ['del-krajowa', 'del-zagraniczna', 'del-rozliczenie', 'del-zaliczka']],
|
|
'powod-delegacji' => ['Powód delegacji', 'text', null, ['del-krajowa', 'del-zagraniczna', 'del-rozliczenie', 'del-zaliczka']],
|
|
'data-poczatku-delegacji' => ['Data początku delegacji', 'date', null, ['del-krajowa', 'del-zagraniczna', 'del-rozliczenie', 'del-zaliczka']],
|
|
'data-konca-delegacji' => ['Data końca delegacji', 'date', null, ['del-krajowa', 'del-zagraniczna', 'del-rozliczenie', 'del-zaliczka']],
|
|
'srodki-transportu' => ['Środki transportu', 'select', ['Samochód', 'Pociąg', 'Samolot', 'Autobus', 'Inne'], ['del-krajowa', 'del-zagraniczna', 'del-rozliczenie', 'del-zaliczka']],
|
|
'hotel' => ['Hotel', 'select', [
|
|
'Brak', 'Dowolny', 'Inny wskazany', 'Antares', 'Countryard (Gdynia)', 'Countryard by Mariot Szczecin',
|
|
'Dom Muzyka', 'Focus Szczecin', 'Moxy Szczecin City', 'My Story', 'Qubus Hotel Gdańsk',
|
|
'Radisson Blu Hotel Szczecin', 'Różany Gaj', 'Vulcan', 'Millenium (Świnoujście)', 'Grand Focus (Szczecin)',
|
|
], ['del-krajowa', 'del-zagraniczna', 'del-rozliczenie', 'del-zaliczka']],
|
|
];
|
|
|
|
foreach ($fields as $slug => [$label, $type, $options, $subSlugs]) {
|
|
$field = CustomField::query()->create([
|
|
'label' => $label,
|
|
'type' => $type,
|
|
'required' => false,
|
|
'options' => $options,
|
|
'sort_order' => count($this->fields),
|
|
]);
|
|
|
|
$this->fields[$slug] = $field;
|
|
|
|
foreach ($subSlugs as $position => $subSlug) {
|
|
$field->subcategories()->attach($this->subs[$subSlug]->id, ['position' => $position]);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function seedTeams(): void
|
|
{
|
|
$teams = [
|
|
'IT' => array_keys(array_filter($this->subs, fn ($_, $slug) => in_array($slug, [
|
|
'aktualizacje-systemu', 'backup', 'drukarki', 'incydent', 'it-inne', 'internet', 'konta',
|
|
'oprogramowanie', 'poczta', 'pulpit-zdalny', 'reset-hasla', 'siec-wewnetrzna', 'sprzet', 'telefon', 'vpn',
|
|
], true), ARRAY_FILTER_USE_BOTH)),
|
|
'Administracja' => ['wnioski-dokumenty', 'sprawy-kadrowe', 'obsluga-biura', 'dostep-budynek', 'del-krajowa', 'del-zagraniczna', 'del-rozliczenie', 'del-zaliczka'],
|
|
'Zakupy' => ['zam-sprzet', 'zam-oprogramowanie', 'zam-materialy', 'zam-usluga'],
|
|
];
|
|
|
|
foreach ($teams as $name => $subSlugs) {
|
|
$team = Team::query()->create(['name' => $name]);
|
|
$team->subcategories()->attach(array_map(fn ($slug) => $this->subs[$slug]->id, $subSlugs));
|
|
}
|
|
}
|
|
|
|
protected function seedUserFields(): void
|
|
{
|
|
foreach ([
|
|
['label' => 'Stanowisko', 'ldap_attribute' => 'title', 'sort_order' => 2],
|
|
['label' => 'Dział', 'ldap_attribute' => 'department', 'sort_order' => 3],
|
|
['label' => 'Firma', 'ldap_attribute' => 'company', 'sort_order' => 4],
|
|
['label' => 'Mob', 'ldap_attribute' => 'mobile', 'sort_order' => 5],
|
|
['label' => 'Stacjonarny', 'ldap_attribute' => 'telephone', 'sort_order' => 6],
|
|
['label' => 'Biuro', 'ldap_attribute' => 'phisical_delivery_office_name', 'sort_order' => 7],
|
|
] as $field) {
|
|
UserField::query()->create([
|
|
'label' => $field['label'],
|
|
'type' => 'text',
|
|
'ldap_attribute' => $field['ldap_attribute'],
|
|
'sort_order' => $field['sort_order'],
|
|
]);
|
|
}
|
|
}
|
|
|
|
protected function seedReplyQuickActions(): void
|
|
{
|
|
foreach ([
|
|
['label' => 'Wyślij i „Oczekuje na klienta”', 'status_key' => 'waiting_customer', 'sort_order' => 1],
|
|
// Used to point at the now-removed "resolved" status, folded into
|
|
// "closed" by the status restructure — same target as "Wyślij i
|
|
// zamknij" today, kept as a separate quick action for continuity
|
|
// with the old hardcoded menu (see ReplyQuickActionsTest).
|
|
['label' => 'Wyślij i oznacz jako rozwiązane', 'status_key' => 'closed', 'sort_order' => 2],
|
|
['label' => 'Wyślij i zamknij', 'status_key' => 'closed', 'sort_order' => 3],
|
|
] as $action) {
|
|
ReplyQuickAction::query()->create($action);
|
|
}
|
|
}
|
|
|
|
protected function seedResponseTemplates(): void
|
|
{
|
|
foreach ([
|
|
['label' => 'Prośba o więcej informacji', 'body' => 'Dziękujemy za zgłoszenie. Czy mógłby Pan/Pani podać więcej szczegółów oraz zrzut ekranu problemu?'],
|
|
['label' => 'Restart usuwa problem', 'body' => 'Prosimy o zrestartowanie urządzenia/aplikacji i sprawdzenie, czy problem nadal występuje.'],
|
|
['label' => 'Zgłoszenie rozwiązane', 'body' => 'Zgłoszenie zostało rozwiązane. Dziękujemy za cierpliwość — w razie dalszych pytań prosimy o kontakt.'],
|
|
] as $template) {
|
|
ResponseTemplate::query()->create($template);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Email template bodies are rendered as raw HTML (see
|
|
* Settings::renderEmailLayout() / TicketNotification), so every body
|
|
* here is proper markup — paragraphs instead of bare "\n\n", and the
|
|
* {link} placeholder wrapped in a real <a> tag instead of being dropped
|
|
* in as plain text.
|
|
*/
|
|
protected function seedEmailTemplatesAndNotifications(): void
|
|
{
|
|
$footer = '<p>Pozdrawiamy,<br>Zespół Wsparcia</p>';
|
|
$link = '<p>Podgląd zgłoszenia: <a href="{link}" rel="noopener noreferrer" target="_blank">Kliknij tu</a></p>';
|
|
$csatLink = '<p><a href="{ocena}" rel="noopener noreferrer" target="_blank">Oceń naszą obsługę</a></p>';
|
|
|
|
$templates = [
|
|
'tpl-new' => [
|
|
'name' => 'Nowe zgłoszenie przyjęte', 'trigger_label' => 'Zgłoszenie utworzone',
|
|
'subject' => 'Otrzymaliśmy Twoje zgłoszenie #{numer}',
|
|
'body' => '<p>Cześć {imie},</p><p>Otrzymaliśmy Twoje zgłoszenie „{temat}”. Nasz zespół zajmie się nim najszybciej jak to możliwe.</p>'.$link.$footer,
|
|
],
|
|
'tpl-status' => [
|
|
'name' => 'Zmiana statusu', 'trigger_label' => 'Status zgłoszenia zmieniony',
|
|
'subject' => 'Aktualizacja zgłoszenia #{numer}',
|
|
'body' => '<p>Cześć {imie},</p><p>Status Twojego zgłoszenia „{temat}” zmienił się na: {status}.</p>'.$link.$footer,
|
|
],
|
|
'tpl-category' => [
|
|
'name' => 'Zmiana kategorii', 'trigger_label' => 'Kategoria zgłoszenia zmieniona',
|
|
'subject' => 'Zmieniono kategorię zgłoszenia #{numer}',
|
|
'body' => '<p>Cześć {imie},</p><p>Kategoria Twojego zgłoszenia „{temat}” została zmieniona na: {kategoria}.</p>'.$link.$footer,
|
|
],
|
|
'tpl-assignee' => [
|
|
'name' => 'Zmiana przypisanego operatora', 'trigger_label' => 'Przypisany operator zmieniony',
|
|
'subject' => 'Zmieniono osobę obsługującą zgłoszenie #{numer}',
|
|
'body' => '<p>Cześć {imie},</p><p>Twoim zgłoszeniem „{temat}” zajmie się teraz: {operator}.</p>'.$link.$footer,
|
|
],
|
|
'tpl-priority' => [
|
|
'name' => 'Zmiana priorytetu', 'trigger_label' => 'Priorytet zgłoszenia zmieniony',
|
|
'subject' => 'Zmieniono priorytet zgłoszenia #{numer}',
|
|
'body' => '<p>Cześć {imie},</p><p>Priorytet Twojego zgłoszenia „{temat}” zmienił się na: {priorytet}.</p>'.$link.$footer,
|
|
],
|
|
'tpl-team' => [
|
|
'name' => 'Zmiana zespołu', 'trigger_label' => 'Zespół obsługujący zmieniony',
|
|
'subject' => 'Zmieniono zespół obsługujący zgłoszenie #{numer}',
|
|
'body' => '<p>Cześć {imie},</p><p>Twoim zgłoszeniem „{temat}” zajmuje się teraz zespół: {zespol}.</p>'.$link.$footer,
|
|
],
|
|
'tpl-closed' => [
|
|
'name' => 'Zgłoszenie zamknięte', 'trigger_label' => 'Status = Zamknięte',
|
|
'subject' => 'Zgłoszenie #{numer} zostało zamknięte',
|
|
'body' => '<p>Cześć {imie},</p><p>Twoje zgłoszenie „{temat}” zostało zamknięte. Jeśli temat nie został rozwiązany, odpowiedz na tego maila lub zgłoś sprawę ponownie.</p>'.$link.$csatLink.$footer,
|
|
],
|
|
'tpl-reply' => [
|
|
'name' => 'Nowa odpowiedź operatora', 'trigger_label' => 'Operator odpowiedział',
|
|
'subject' => 'Nowa odpowiedź w zgłoszeniu #{numer}',
|
|
'body' => '<p>Cześć {imie},</p><p>Otrzymałeś/aś nową odpowiedź w zgłoszeniu „{temat}”.</p>'.$link.$footer,
|
|
],
|
|
'tpl-sla-breach' => [
|
|
'name' => 'Przekroczenie SLA', 'trigger_label' => 'SLA przekroczone — operator',
|
|
'subject' => 'Przekroczono SLA zgłoszenia #{numer}',
|
|
'body' => '<p>Cześć {operator},</p><p>Zgłoszenie „{temat}” (#{numer}) przekroczyło ustalony czas rozwiązania SLA.</p>'.$link.$footer,
|
|
],
|
|
'tpl-team-new-ticket' => [
|
|
'name' => 'Nowe zgłoszenie w zespole', 'trigger_label' => 'Nowe zgłoszenie w zespole — operator',
|
|
'subject' => 'Nowe zgłoszenie w Twoim zespole (#{numer})',
|
|
'body' => '<p>Cześć,</p><p>Nowe zgłoszenie „{temat}” (#{numer}, kategoria: {kategoria}) trafiło do zespołu {zespol}.</p>'.$link.$footer,
|
|
],
|
|
];
|
|
|
|
$ids = [];
|
|
|
|
foreach ($templates as $key => $tpl) {
|
|
$ids[$key] = EmailTemplate::query()->firstOrCreate(['key' => $key], [
|
|
'name' => $tpl['name'],
|
|
'trigger_label' => $tpl['trigger_label'],
|
|
'subject' => $tpl['subject'],
|
|
'body' => $tpl['body'],
|
|
])->id;
|
|
}
|
|
|
|
foreach ([
|
|
['trigger_key' => 'ticket_created', 'trigger_label' => 'Nowe zgłoszenie utworzone', 'enabled' => true, 'recipient' => 'client', 'template' => 'tpl-new'],
|
|
['trigger_key' => 'status_changed', 'trigger_label' => 'Zmiana statusu zgłoszenia', 'enabled' => true, 'recipient' => 'client', 'template' => 'tpl-status'],
|
|
['trigger_key' => 'category_changed', 'trigger_label' => 'Zmiana kategorii zgłoszenia', 'enabled' => false, 'recipient' => 'client', 'template' => 'tpl-category'],
|
|
['trigger_key' => 'assignee_changed', 'trigger_label' => 'Zmiana przypisanego operatora', 'enabled' => false, 'recipient' => 'client', 'template' => 'tpl-assignee'],
|
|
['trigger_key' => 'priority_changed', 'trigger_label' => 'Zmiana priorytetu', 'enabled' => false, 'recipient' => 'client', 'template' => 'tpl-priority'],
|
|
['trigger_key' => 'team_changed', 'trigger_label' => 'Zmiana zespołu obsługującego', 'enabled' => false, 'recipient' => 'client', 'template' => 'tpl-team'],
|
|
['trigger_key' => 'ticket_closed', 'trigger_label' => 'Zgłoszenie zamknięte', 'enabled' => true, 'recipient' => 'client', 'template' => 'tpl-closed'],
|
|
['trigger_key' => 'operator_replied', 'trigger_label' => 'Nowa odpowiedź operatora', 'enabled' => true, 'recipient' => 'client', 'template' => 'tpl-reply'],
|
|
['trigger_key' => 'sla_breached', 'trigger_label' => 'Przekroczono SLA (powiadom operatora)', 'enabled' => false, 'recipient' => 'operator', 'template' => 'tpl-sla-breach'],
|
|
['trigger_key' => 'ticket_created_team', 'trigger_label' => 'Nowe zgłoszenie w zespole (powiadom operatorów)', 'enabled' => true, 'recipient' => 'operator', 'template' => 'tpl-team-new-ticket'],
|
|
] as $setting) {
|
|
NotificationSetting::query()->firstOrCreate(['trigger_key' => $setting['trigger_key']], [
|
|
'trigger_label' => $setting['trigger_label'],
|
|
'enabled' => $setting['enabled'],
|
|
'recipient' => $setting['recipient'],
|
|
'email_template_id' => $ids[$setting['template']],
|
|
]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Carries over the real branding/behavior configuration as-is; SMTP and
|
|
* LDAP connection details are seeded with placeholder example values
|
|
* instead of production secrets/hosts — an admin fills in the real ones
|
|
* from the Admin > Konfiguracja panel.
|
|
*/
|
|
protected function seedSettings(): void
|
|
{
|
|
$settings = [
|
|
'company_name' => 'Servicedesk',
|
|
'company_logo_path' => null,
|
|
'accent_color' => '#2c55e2',
|
|
'timezone' => 'Europe/Warsaw',
|
|
'default_status' => 'new',
|
|
'auto_assign_by_category' => '1',
|
|
'allow_attachments' => '1',
|
|
'attachment_max_size_kb' => '10240',
|
|
'attachment_max_count' => '5',
|
|
'attachment_max_total_size_kb' => '20480',
|
|
'attachment_allowed_types' => 'jpg,jpeg,png,pdf,doc,docx,xls,xlsx,zip,txt',
|
|
'session_lifetime_minutes' => '120',
|
|
|
|
'login_notice_type' => 'info',
|
|
'login_notice_html' => '<p class="ql-align-center"><strong style="color: rgb(230, 0, 0);">Informacja o logowaniu</strong></p><p class="ql-align-center"><span style="color: rgb(255, 255, 255);">Aby uzyskać dostęp do serwisu, użyj swoich standardowych danych służbowych:</span></p><hr><p class="ql-align-center"><strong style="color: rgb(255, 255, 255);">Nazwa użytkownika</strong><span style="color: rgb(255, 255, 255);">: (np. jkowalski)</span></p><p class="ql-align-center"><strong style="color: rgb(255, 255, 255);">Hasło</strong><span style="color: rgb(255, 255, 255);">: Takie samo, jak do komputera</span></p>',
|
|
|
|
'email_footer' => '<p>Ta wiadomość została wygenerowana automatycznie przez system {firma} — prosimy na nią nie odpowiadać.</p>',
|
|
'email_layout_html' => '<div style="max-width:560px;margin:0 auto;padding:32px;background:#ffffff;border:1px solid #e5e5ea;border-radius:12px;font-family:Arial,Helvetica,sans-serif;color:#23252d"><div style="font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:0.05em;color:#8a8a93;margin-bottom:18px">{firma}</div><div style="font-size:14px;line-height:1.6">{tresc}</div><div style="border-top:1px solid #e5e5ea;margin:24px 0 16px"></div><div style="font-size:12px;line-height:1.5;color:#8a8a93">{stopka}</div></div>',
|
|
|
|
'restrict_user_creation_to_ldap' => '0',
|
|
'restrict_tickets_to_ldap' => '1',
|
|
|
|
// LDAP — example connection details, not production credentials.
|
|
'ldap_enabled' => '1',
|
|
'ldap_host' => 'ldap.example.com',
|
|
'ldap_port' => '389',
|
|
'ldap_base_dn' => 'dc=example,dc=com',
|
|
'ldap_bind_dn' => 'cn=admin,dc=example,dc=com',
|
|
'ldap_bind_password' => 'changeme-ldap-password',
|
|
'ldap_use_ssl' => '0',
|
|
'ldap_user_filter' => '(uid={0})',
|
|
'ldap_auto_provision_guests' => '1',
|
|
|
|
// SMTP — example connection details, not production credentials.
|
|
'mail_smtp_enabled' => '1',
|
|
'mail_smtp_host' => 'smtp.example.com',
|
|
'mail_smtp_port' => '587',
|
|
'mail_smtp_encryption' => 'tls',
|
|
'mail_smtp_username' => 'noreply@example.com',
|
|
'mail_smtp_password' => 'changeme-smtp-password',
|
|
'mail_from_address' => 'noreply@example.com',
|
|
'mail_from_name' => 'Servicedesk',
|
|
];
|
|
|
|
foreach ($settings as $key => $value) {
|
|
Settings::set($key, $value);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The only account this seeder creates: a local (non-LDAP) admin login
|
|
* for initial/emergency access. The login form's LDAP lookup falls back
|
|
* to matching by e-mail + local password (see Livewire\Auth\Login), so
|
|
* "admin" doubles as both username and e-mail here.
|
|
*/
|
|
protected function seedAdminUser(): void
|
|
{
|
|
User::query()->create([
|
|
'name' => 'Administrator',
|
|
'email' => 'admin@example.com',
|
|
'password' => 'admin',
|
|
'roles' => ['admin'],
|
|
]);
|
|
}
|
|
}
|