Przejdź do treści

Reklamacje i skargi (Complaint)

Ostatnia aktualizacja dokumentacji: 26 lutego 2026 Stan synchronizacji z kodem: ✅ Zsynchronizowany


1. Opis ogólny

Domena Reklamacji zarządza zgłoszeniami problemów od klientów — od złożenia reklamacji, przez wymianę wiadomości, aż po rozwiązanie sprawy. To jak cyfrowa książka skarg i wniosków z pełnym workflow, historią korespondencji i możliwością dołączania zdjęć czy dokumentów.

Klienci mogą składać reklamacje bez logowania — wystarczy kod dostępu z rezerwacji. System automatycznie generuje unikalny numer zgłoszenia i kod dostępu, umożliwiając śledzenie statusu sprawy i wymianę wiadomości z obsługą.


2. Architektura domeny

2.1 Modele danych

Complaint (Reklamacja)

  • Ścieżka: app/Domain/Complaint/Model/Complaint.php
  • Tabela: complaints
  • Klucz: UUID (string, HasUuids)
  • Interfejsy: HasMedia (Spatie MediaLibrary)
  • Traity: HasUuids, InteractsWithMedia
  • Observery: StatusLogObserver, ComplaintObserver
  • Hidden fields: access_code (ukryty w serializacji, ale zwracany w Resource)

Relacje:

Metoda Typ Model docelowy Opis
organization() BelongsTo Organization Organizacja powiązana z reklamacją
reservation() BelongsTo Reservation Rezerwacja, której dotyczy reklamacja
messages() HasMany ComplaintMessage Wątek wiadomości
supervisor() BelongsTo User (via supervisor_id) Opiekun sprawy
solver() BelongsTo User (via resolved_by) Osoba, która rozwiązała sprawę
notes() MorphMany Note Wewnętrzne notatki (polimorficzne)
media MediaLibrary Media Załączniki (kolekcja attachments)

Kolekcje mediów:

Kolekcja attachments akceptuje: image/jpeg, image/png, image/gif, image/webp, video/mp4, video/quicktime, video/x-msvideo, application/pdf, application/msword, application/vnd.openxmlformats-officedocument.wordprocessingml.document.

ComplaintMessage (Wiadomość reklamacji)

  • Ścieżka: app/Domain/Complaint/Model/ComplaintMessage.php
  • Tabela: complaint_messages
  • Klucz: UUID (string, HasUuids)
  • Observer: ComplaintMessageObserver

Relacje:

Metoda Typ Model docelowy Opis
complaint() BelongsTo Complaint Reklamacja nadrzędna

2.2 Diagram relacji

erDiagram
    Complaint ||--o{ ComplaintMessage : "messages()"
    Complaint }o--|| Organization : "organization()"
    Complaint }o--o| Reservation : "reservation()"
    Complaint }o--o| User : "supervisor()"
    Complaint }o--o| User : "solver()"
    Complaint ||--o{ Note : "notes() [morph]"
    Complaint ||--o{ Media : "media [attachments]"

    Complaint {
        uuid id PK
        uuid uuid
        string access_code
        uuid organization_id FK
        uuid reservation_id FK
        string first_name
        string last_name
        string email
        string mobile_phone
        enum type
        text message
        enum status
        enum priority
        uuid supervisor_id FK
        uuid resolved_by FK
        datetime resolved_at
        datetime created_at
        datetime updated_at
    }

    ComplaintMessage {
        uuid id PK
        uuid complaint_id FK
        enum author_type
        string author_name
        string author_email
        text message
        datetime created_at
        datetime updated_at
    }

2.3 Struktura tabel w bazie danych

Migracja: database/migrations/0001_01_01_000020_create_complaints.php

Tabela complaints

Kolumna Typ Nullable Default Indeks Opis
id uuid Nie PK Identyfikator (klucz główny)
uuid uuid Nie INDEX Publiczny identyfikator UUID
access_code string Nie INDEX Kod dostępu (10 znaków, wielkie litery)
organization_id uuid Tak null INDEX, FK → organizations.id (CASCADE) Organizacja
reservation_id uuid Tak null INDEX, FK → reservations.id (CASCADE) Rezerwacja
resolved_by uuid Tak null INDEX, FK → users.id (CASCADE) Kto rozwiązał
first_name string Nie Imię zgłaszającego
last_name string Nie Nazwisko zgłaszającego
email string Nie Email zgłaszającego
mobile_phone string Nie Telefon zgłaszającego
type enum Nie Typ: issue, refund, suggestion, other
message text Nie Treść zgłoszenia
status enum Nie Status: new, in_progress, resolved, closed
priority enum Nie Priorytet: low, medium, high
supervisor_id uuid Tak null INDEX, FK → users.id (SET NULL) Opiekun sprawy
resolved_at datetime Tak null Data rozwiązania
created_at timestamp Tak Data utworzenia
updated_at timestamp Tak Data aktualizacji

Tabela complaint_messages

Kolumna Typ Nullable Default Indeks Opis
id uuid Nie PK Identyfikator (klucz główny)
complaint_id uuid Nie FK → complaints.id (CASCADE), COMPOSITE INDEX Reklamacja nadrzędna
author_type enum Nie COMPOSITE INDEX Typ autora: resident, staff, system
author_name string Tak null Nazwa autora
author_email string Tak null Email autora
message text Nie Treść wiadomości
created_at timestamp Tak COMPOSITE INDEX Data utworzenia
updated_at timestamp Tak Data aktualizacji

Indeksy złożone: (complaint_id, created_at), (author_type, created_at)


3. Endpointy API

3.1 Endpointy uwierzytelnione (auth)

Middleware bazowe: auth:api, user_is_active, throttle:api, to_user_access, user_accepted_agreements.

Middleware roli ADMIN|SUPERVISOR|EMPLOYEE na: index, show, update, changeStatus, assignSupervisor.

GET /api/complaints

  • Opis: Lista reklamacji z filtrami i paginacją
  • Autoryzacja: Rola ADMIN, SUPERVISOR lub EMPLOYEE
  • Query Parameters:
    • Paginacja (standardowa z TraitSupport)
    • filter[id] — dokładne dopasowanie UUID
    • filter[type] — typ reklamacji (issue, refund, suggestion, other)
    • filter[status] — status (new, in_progress, resolved, closed)
    • filter[priority] — priorytet (low, medium, high)
    • filter[supervisor.name] — częściowe dopasowanie nazwy opiekuna
    • filter[solver.name] — częściowe dopasowanie nazwy osoby rozwiązującej
  • Response (sukces): Paginowana kolekcja ComplaintResource z relacjami: reservation, organization, supervisor, solver

GET /api/complaints/{complaintId}

  • Opis: Szczegóły pojedynczej reklamacji
  • Autoryzacja: Rola ADMIN, SUPERVISOR lub EMPLOYEE
  • Parametry URL: complaintId (UUID)
  • Response (sukces): ComplaintResource z relacjami
  • Response (błędy): 404 — reklamacja nie znaleziona (NotFoundModelException)

POST /api/complaints

  • Opis: Tworzenie nowej reklamacji (dostępne dla wszystkich zalogowanych, bez wymogu roli)
  • Body (Request): StoreComplaintControllerRequest — patrz sekcja 4.3
  • Response (sukces): 200"The Complaint has been successfully added"

PATCH /api/complaints/{complaintId}

  • Opis: Aktualizacja reklamacji
  • Autoryzacja: Rola ADMIN|SUPERVISOR|EMPLOYEE + Policy update (musi należeć do organizacji)
  • Parametry URL: complaintId (UUID)
  • Body (Request): UpdateComplaintControllerRequest — patrz sekcja 4.3
  • Response (sukces): 200"The Complaint has been successfully updated"

PATCH /api/complaints/{complaintId}/change-status

  • Opis: Zmiana statusu reklamacji
  • Autoryzacja: Rola ADMIN|SUPERVISOR|EMPLOYEE + Policy update
  • Parametry URL: complaintId (UUID)
  • Body (Request): ChangeStatusComplaintControllerRequest
{
  "status": "in_progress"
}
  • Response (sukces): 200 — standardowa odpowiedź z changeStatusXController
  • Reguły biznesowe: Używa generycznej metody changeStatusXController z TraitControllerSupport

PATCH /api/complaints/{complaintId}/assign-supervisor

  • Opis: Przypisanie opiekuna do reklamacji
  • Autoryzacja: Rola ADMIN|SUPERVISOR|EMPLOYEE + Policy update
  • Parametry URL: complaintId (UUID)
  • Body (Request): AssignSupervisorComplaintControllerRequest
{
  "supervisor_id": "uuid-of-user"
}
  • Response (sukces): 200"Supervisor has been successfully assigned to the complaint"
  • Reguły biznesowe: Opiekun musi być pracownikiem organizacji powiązanej z reklamacją (VerifySupervisorIsInOrganizationRule)

GET /api/complaint-messages

  • Opis: Lista wiadomości dla danej reklamacji
  • Autoryzacja: Brak wymogu roli (weryfikacja przez complaint_id + complaint_uuid + complaint_access_code)
  • Query Parameters: Paginacja + complaint_id (required), complaint_uuid (required), complaint_access_code (required)
  • Response (sukces): Paginowana kolekcja ComplaintMessageResource

POST /api/complaint-messages

  • Opis: Dodanie wiadomości do reklamacji
  • Autoryzacja: Brak wymogu roli
  • Body (Request): StoreComplaintMessageControllerRequest — patrz sekcja 4.3
  • Response (sukces): 200"The Complaint message has been successfully added"

GET /api/complaint-attachments

  • Opis: Lista załączników reklamacji
  • Autoryzacja: Brak wymogu roli (weryfikacja przez complaint_id + complaint_uuid + complaint_access_code)
  • Response (sukces): Kolekcja ComplaintAttachmentResource

POST /api/complaint-attachments

  • Opis: Dodanie załączników do reklamacji
  • Autoryzacja: Brak wymogu roli (weryfikacja przez dane reklamacji)
  • Body (Request): StoreComplaintAttachmentControllerRequest — patrz sekcja 4.3
  • Response (sukces): 201 — kolekcja ComplaintAttachmentResource

DELETE /api/complaint-attachments/{attachmentId}

  • Opis: Usunięcie załącznika
  • Autoryzacja: Brak wymogu roli (weryfikacja przez dane reklamacji)
  • Parametry URL: attachmentId (UUID)
  • Body (Request): DeleteComplaintAttachmentControllerRequest (wymaga complaint_id, complaint_uuid, complaint_access_code)
  • Response (sukces): 200"Attachment deleted successfully"

3.2 Endpointy publiczne (noAuth)

Middleware bazowe: require_app_client, global_database_transaction, app_mode, check_site_enable, set_locale, security_headers, throttle:api.

Metoda Endpoint Opis Throttle
POST /api/no-auth/complaints Złożenie reklamacji public-store
GET /api/no-auth/complaints/{complaintId}/find Podgląd reklamacji po UUID+kod public-find
GET /api/no-auth/complaint-messages Lista wiadomości
POST /api/no-auth/complaint-messages Dodanie wiadomości public-store
GET /api/no-auth/complaint-attachments Lista załączników
POST /api/no-auth/complaint-attachments Dodanie załączników public-store
DELETE /api/no-auth/complaint-attachments/{attachmentId} Usunięcie załącznika public-store

Route names (noAuth): no-auth-complaints.store, no-auth-complaints.find, no-auth-complaint-messages.index, no-auth-complaint-messages.store, no-auth-complaint-attachments.index, no-auth-complaint-attachments.store, no-auth-complaint-attachments.delete.

Dostęp do wiadomości i załączników

Endpointy wiadomości i załączników wymagają podania complaint_id, complaint_uuid i complaint_access_code — weryfikowanych przez VerifyComplaintCorrectDataRule.


4. Logika biznesowa

4.1 Główne procesy

Proces tworzenia reklamacji

sequenceDiagram
    participant K as Klient
    participant S as System
    participant A as Administrator
    participant E as Email

    K->>S: Składa reklamację (po rezerwacji)
    S->>S: Generuje UUID i kod dostępu
    S->>S: Kopiuje dane z rezerwacji (imię, email, telefon)
    S->>E: Wysyła potwierdzenie z kodem
    E-->>K: Email z kodem i linkiem
    S->>A: Nowa reklamacja w panelu
    A->>S: Przypisuje opiekuna
    S->>E: Powiadomienie dla opiekuna
    A->>S: Odpowiada klientowi
    S->>E: Email do klienta
    E-->>K: Odpowiedź od obsługi
  1. Klient po rezerwacji składa reklamację podając reservation_id, reservation_uuid i reservation_access_code
  2. CreatingComplaintObserver generuje unikalny uuid (UUID v4) i access_code (10 znaków, wielkie litery via Str::random(10))
  3. Dane kontaktowe (first_name, last_name, email, mobile_phone) są automatycznie kopiowane z rezerwacji, jeśli nie zostały podane
  4. organization_id jest automatycznie ustawiany z rezerwacji
  5. Domyślny status: new
  6. CreatedComplaintObserver wysyła ComplaintCreatedNotification do klienta (email)
  7. Jeśli przypisano opiekuna — ComplaintNewSupervisorNotification do opiekuna

Proces wymiany wiadomości

flowchart TD
    A[Nowa wiadomość] --> B{Kto pisze?}
    B -->|Zalogowany użytkownik| C[author_type = staff]
    C --> D[Ustaw author_name z user.name]
    B -->|Niezalogowany klient| E[author_type = resident]
    E --> F[Ustaw author_name z complaint.first_name + last_name]
    D --> G{Czy poprzednia wiadomość<br>od tego samego autora?}
    F --> G
    G -->|Tak| H[Nie wysyłaj powiadomienia]
    G -->|Nie| I{Kto jest odbiorcą?}
    I -->|Klient pisał| J[Email do opiekuna]
    I -->|Opiekun pisał| K[Email do klienta]

Logika powiadomień (SendComplaintMessageCreatedNotification):

  1. Sprawdza czy poprzednia wiadomość w wątku (last by created_at) ma inny author_email niż bieżąca
  2. Jeśli ten sam email — powiadomienie nie jest wysyłane (zapobiega podwójnym powiadomieniom)
  3. Jeśli autor to resident → email do supervisor.email
  4. Jeśli autor to staff → email do complaint.email (klient)

Proces śledzenia statusu (dostęp publiczny)

  1. Klient otwiera link z emaila
  2. Endpoint GET /api/no-auth/complaints/{complaintId}/find wymaga uuid i access_code
  3. FindComplaintUUIDAccessCode szuka reklamacji po uuid, access_code i id
  4. ExceptionFindComplaintUUIDAccessCode sprawdza rate limiting (checkAccessLockout, rateLimitAccessAttempt)
  5. Przy niepowodzeniu — rzuca FindComplaintUUIDAccessCodeException (409)

4.2 Reguły biznesowe

  1. Rezerwacja musi być potwierdzona — status rezerwacji musi być confirmed (ReservationStillToComplaintRule)
  2. Rezerwacja musi być zakończonaend_at musi być w przeszłości
  3. Termin złożenia — maksymalnie MAX_DAY_TO_ADD_COMPLAINT dni od zakończenia rezerwacji (konfigurowalny)
  4. Weryfikacja rezerwacji — wymagane reservation_id, reservation_uuid i reservation_access_code (VerifyReservationCorrectDataRule)
  5. Opiekun musi być pracownikiem organizacji — walidacja VerifySupervisorIsInOrganizationRule sprawdza existOrganizationEmployee()
  6. Niezalogowani nie mogą przypisać opiekunaCreatingComplaintObserver ustawia supervisor_id = null jeśli !auth()->check()
  7. Automatyczne kopiowanie danych z rezerwacjiorganization_id zawsze, first_name, last_name, email, mobile_phone tylko jeśli puste
  8. Automatyczne zarządzanie rozwiązaniemUpdatingComplaintObserver: status resolved → ustawia resolved_at + resolved_by; inny status → czyści te pola
  9. Powiadomienia o istotnych zmianachUpdatedComplaintObserver wysyła ComplaintUpdatedNotification tylko gdy zmienione pola to nie supervisor_id i nie updated_at
  10. Rate limiting dostępu publicznegoExceptionFindComplaintUUIDAccessCode używa checkAccessLockout i rateLimitAccessAttempt dla resource type RATE_LIMIT_RESOURCE_COMPLAINT
  11. Honeypot antyspamowy — przy tworzeniu przez niezalogowanego: pola website, company_website, fax_number muszą być puste (max:0)
  12. Filtr wulgaryzmów — pola author_name, author_email i message w wiadomościach walidowane regułą blasp_check

4.3 Walidacje

StoreComplaintControllerRequest (Tworzenie reklamacji)

Pole Reguły Uwagi
reservation_id required\|uuid\|exists:reservations,id + ReservationStillToComplaintRule Weryfikuje termin i status rezerwacji
reservation_uuid required\|uuid\|max:255 + VerifyReservationCorrectDataRule
reservation_access_code required\|string\|min:1\|max:20 + VerifyReservationCorrectDataRule
first_name required_without:reservation_id\|string\|max:255 Wymagane gdy brak reservation_id
last_name required_without:reservation_id\|string\|max:255
email required_without:reservation_id\|email\|max:255
mobile_phone nullable\|string\|max:32
type required\|string\|in:issue,refund,suggestion,other
message required\|string\|max:5000
priority required\|string\|in:low,medium,high
files sometimes\|array\|min:1\|max:10 Opcjonalnie
files.* sometimes\|file\|max:30480\|mimetypes:... ~29.8 MB na plik
supervisor_id sometimes\|uuid\|exists:users,id + VerifySupervisorIsInOrganizationRule Tylko dla zalogowanych
website sometimes\|max:0 Honeypot — tylko dla niezalogowanych
company_website sometimes\|max:0 Honeypot
fax_number sometimes\|max:0 Honeypot

UpdateComplaintControllerRequest (Aktualizacja reklamacji)

Pole Reguły
type required\|string\|in:issue,refund,suggestion,other
priority required\|string\|in:low,medium,high
first_name sometimes\|string\|max:255
last_name sometimes\|string\|max:255
email sometimes\|email\|max:255
mobile_phone nullable\|string\|max:32
supervisor_id sometimes\|nullable\|uuid\|exists:users,id + VerifySupervisorIsInOrganizationRule

ChangeStatusComplaintControllerRequest (Zmiana statusu)

Pole Reguły
status required\|string\|in:new,in_progress,resolved,closed

AssignSupervisorComplaintControllerRequest (Przypisanie opiekuna)

Pole Reguły
supervisor_id sometimes\|nullable\|uuid\|exists:users,id + VerifySupervisorIsInOrganizationRule

FindComplaintControllerRequest (Podgląd reklamacji)

Pole Reguły
uuid required\|uuid\|max:255
access_code required\|string\|min:1\|max:20

StoreComplaintMessageControllerRequest (Dodanie wiadomości)

Pole Reguły Uwagi
complaint_id required\|uuid\|max:255\|exists:complaints,id
complaint_uuid required\|uuid\|max:255\|exists:complaints,uuid
complaint_access_code required\|string\|min:1\|max:20\|exists:complaints,access_code
author_name RequiredWhenNotAuthenticatedRule\|string\|max:255\|blasp_check Wymagane gdy niezalogowany
author_email RequiredWhenNotAuthenticatedRule\|email\|max:255\|blasp_check Wymagane gdy niezalogowany
message required\|string\|min:3\|max:5000\|blasp_check Filtr wulgaryzmów

IndexComplaintMessageControllerRequest (Lista wiadomości)

Pole Reguły
(paginacja) Standardowe reguły paginacji
complaint_id required\|uuid\|max:255\|exists:complaints,id + VerifyComplaintCorrectDataRule
complaint_uuid required\|uuid\|max:255\|exists:complaints,uuid + VerifyComplaintCorrectDataRule
complaint_access_code required\|string\|min:1\|max:20\|exists:complaints,access_code + VerifyComplaintCorrectDataRule

StoreComplaintAttachmentControllerRequest (Dodanie załączników)

Pole Reguły
complaint_id required\|uuid\|max:255\|exists:complaints,id + VerifyComplaintCorrectDataRule
complaint_uuid required\|uuid\|max:255\|exists:complaints,uuid + VerifyComplaintCorrectDataRule
complaint_access_code required\|string\|min:1\|max:20\|exists:complaints,access_code + VerifyComplaintCorrectDataRule
files required\|array\|min:1\|max:10
files.* required\|file\|max:20480\|mimetypes:...

IndexComplaintAttachmentControllerRequest (Lista załączników)

Pole Reguły
complaint_id required\|uuid\|max:255\|exists:complaints,id + VerifyComplaintCorrectDataRule
complaint_uuid required\|uuid\|max:255\|exists:complaints,uuid + VerifyComplaintCorrectDataRule
complaint_access_code required\|string\|min:1\|max:20\|exists:complaints,access_code + VerifyComplaintCorrectDataRule

DeleteComplaintAttachmentControllerRequest (Usunięcie załącznika)

Pole Reguły
complaint_id required\|uuid\|max:255\|exists:complaints,id + VerifyComplaintCorrectDataRule
complaint_uuid required\|uuid\|max:255\|exists:complaints,uuid + VerifyComplaintCorrectDataRule
complaint_access_code required\|string\|min:1\|max:20\|exists:complaints,access_code + VerifyComplaintCorrectDataRule

5. Autoryzacja i uprawnienia

Middleware roli

Kontroler ComplaintController stosuje middleware role:ADMIN|SUPERVISOR|EMPLOYEE na metodach: index, show, update, changeStatus, assignSupervisor.

Metoda store jest dostępna bez roli — zarówno dla zalogowanych jak i niezalogowanych użytkowników.

Kontrolery ComplaintMessageController i ComplaintAttachmentController nie mają middleware roli — dostęp jest kontrolowany przez weryfikację complaint_id + complaint_uuid + complaint_access_code.

Laravel Policies

ComplaintPolicy (app/Domain/Complaint/Policy/ComplaintPolicy.php)

Metoda Logika Opis
view belongsToOrganization($user, $complaint->organization_id) Użytkownik musi należeć do organizacji reklamacji
create return false Tworzenie zawsze dozwolone (zarządzane przez middleware/route)
update belongsToOrganization($user, $complaint->organization_id) Użytkownik musi należeć do organizacji reklamacji

ComplaintMessagePolicy (app/Domain/Complaint/Policy/ComplaintMessagePolicy.php)

Metoda Logika Opis
view belongsToOrganization($user, $complaint->organization_id) Przez complaint nadrzędny
create return false Zawsze false
update return false Zawsze false

Matryca uprawnień

Rola Podgląd listy Szczegóły Tworzenie Edycja Zmiana statusu Przypisanie opiekuna Wiadomości Załączniki
Administrator
Supervisor
Employee
Klient (z kodem) (swoje)

6. Eventy i efekty uboczne

ComplaintObserver

Zdarzenie Klasa akcji Efekt
creating CreatingComplaintObserver Generuje uuid i access_code; kopiuje dane z rezerwacji; ustawia domyślny status new; ustawia resolved_at/resolved_by jeśli status = resolved; czyści supervisor_id jeśli niezalogowany
created CreatedComplaintObserver Wysyła ComplaintCreatedNotification do klienta; opcjonalnie ComplaintNewSupervisorNotification do opiekuna
updating UpdatingComplaintObserver Przy zmianie statusu na resolved → ustawia resolved_at i resolved_by; przy innym statusie → czyści te pola. Pomija jeśli status nie został zmieniony (isDirty)
updated UpdatedComplaintObserver Wysyła ComplaintUpdatedNotification do klienta (jeśli istotne zmiany, tj. nie supervisor_id/updated_at); ComplaintNewSupervisorNotification przy zmianie opiekuna

Dodatkowy observer

Model Complaint ma również StatusLogObserver z domeny StatusLog — loguje wszystkie zmiany statusów do dziennika.

ComplaintMessageObserver

Zdarzenie Klasa akcji Efekt
creating CreatingComplaintMessageObserver Automatycznie ustawia author_type, author_name, author_email na podstawie stanu uwierzytelnienia (zalogowany → staff z danych usera; niezalogowany → resident z danych reklamacji)
created CreatedComplaintMessageObserver Wywołuje sendComplaintMessageCreatedNotification — wysyła powiadomienie email z inteligentnym filtrowaniem (nie wysyła jeśli poprzednia wiadomość od tego samego autora)

7. Powiadomienia

Wszystkie powiadomienia są wysyłane asynchronicznie przez SimpleSendNotificationJob (kolejka) kanałem mail.

Powiadomienie Klasa Kiedy Odbiorca Treść
Reklamacja utworzona ComplaintCreatedNotification Po złożeniu reklamacji Klient (complaint.email) Numer UUID, kod dostępu, data, status, wiadomość, link do panelu
Nowy opiekun ComplaintNewSupervisorNotification Po przypisaniu/zmianie opiekuna Opiekun (supervisor.email) Numer UUID, kod dostępu, data, status, wiadomość, link do panelu admin
Reklamacja zaktualizowana ComplaintUpdatedNotification Po istotnej zmianie (nie supervisor_id, nie updated_at) Klient (complaint.email) Numer UUID, kod dostępu, data, aktualny status, link
Nowa wiadomość ComplaintMessageCreatedNotification Po dodaniu wiadomości (warunkowo) Opiekun lub klient (zależnie od autora) Numer UUID, kod dostępu, aktualny status, treść oryginalnego zgłoszenia, treść nowej wiadomości, link

Inteligentne powiadomienia wiadomości

System nie wysyła powiadomienia, jeśli ostatnia wiadomość w wątku pochodzi od tego samego autora (adres email). Zapobiega to podwójnym powiadomieniom, gdy ta sama osoba pisze kilka wiadomości pod rząd.

URL w powiadomieniach

  • Klient: {frontend_url}/reklamacje/{id}?uuid={uuid}&access_code=
  • Staff/supervisor: {frontend_url}/panel/reklamacje/{id}?uuid={uuid}&access_code=
  • Wiadomości: URL zależy od author_type — staff dostaje URL panelu, resident URL publiczny

8. Powiązania z innymi domenami

Domena Powiązanie Typ relacji Opis
Rezerwacje (Reservation) Reklamacja dotyczy konkretnej rezerwacji BelongsTo Dane kontaktowe kopiowane z rezerwacji
Organizacje (Organization) Reklamacja przypisana do organizacji BelongsTo organization_id ustawiany automatycznie z rezerwacji
Użytkownicy (User) Opiekun sprawy BelongsTo (supervisor_id) Musi być pracownikiem organizacji
Użytkownicy (User) Osoba rozwiązująca BelongsTo (resolved_by) Ustawiany automatycznie przy statusie resolved
Notatki (Note) Wewnętrzne notatki MorphMany (notable) Polimorficzne notatki do reklamacji
Dziennik statusów (StatusLog) Logowanie zmian statusów StatusLogObserver Automatyczne logowanie każdej zmiany statusu
Media (Media) Załączniki plików Spatie MediaLibrary (attachments) Obrazy, dokumenty, video
Powiadomienia (Notification) Emaile o zmianach SimpleSendNotificationJob 4 typy powiadomień
Konfiguracja (Configuration) Parametr terminu reklamacji TraitConfiguration MAX_DAY_TO_ADD_COMPLAINT

9. Konfiguracja

Parametr Źródło Opis
MAX_DAY_TO_ADD_COMPLAINT Domena Configuration (getConfigurationBySlug) Maksymalna liczba dni od zakończenia rezerwacji, w ciągu których można złożyć reklamację
app.frontend_url config('app.frontend_url') Bazowy URL frontendu — używany do generowania linków w powiadomieniach
RATE_LIMIT_RESOURCE_COMPLAINT SupportEnumService Typ zasobu do rate limitingu przy publicznym dostępie

10. Znane ograniczenia i TODO

Ograniczenie Opis
ComplaintPolicy::create() zwraca false Tworzenie obsługiwane poza Policy (przez routing)
access_code w $hidden Kod jest ukryty w serializacji modelu, ale jawnie zwracany w ComplaintResource
Brak weryfikacji tożsamości klienta Endpoint find wymaga UUID + access_code, ale nie weryfikuje czy osoba to faktyczny klient
Brak limitu wiadomości Nie ma ograniczenia na liczbę wiadomości w wątku
Brak soft delete Reklamacje i wiadomości nie mają miękkiego usuwania

Struktura domeny

app/Domain/Complaint/
├── Action/
│   ├── Controller/
│   │   ├── AssignSupervisorComplaintController.php  # Przypisanie opiekuna
│   │   ├── DeleteComplaintAttachmentController.php  # Usuwanie załącznika
│   │   ├── FindComplaintController.php              # Znajdź reklamację (publiczny)
│   │   ├── IndexComplaintAttachmentController.php   # Lista załączników
│   │   ├── IndexComplaintController.php             # Lista reklamacji
│   │   ├── IndexComplaintMessageController.php      # Lista wiadomości
│   │   ├── ShowComplaintController.php              # Szczegóły reklamacji
│   │   ├── StoreComplaintAttachmentController.php   # Dodawanie załączników
│   │   ├── StoreComplaintController.php             # Tworzenie reklamacji
│   │   ├── StoreComplaintMessageController.php      # Dodawanie wiadomości
│   │   └── UpdateComplaintController.php            # Aktualizacja reklamacji
│   └── Other/
│       ├── CreateComplaint.php                      # Tworzenie + załączniki
│       ├── CreateComplaintAttachments.php            # Upload plików (Spatie Media)
│       ├── CreateComplaintMessage.php                # Tworzenie wiadomości
│       ├── CreatedComplaintMessageObserver.php       # Powiadomienie po wiadomości
│       ├── CreatedComplaintObserver.php              # Powiadomienie po utworzeniu
│       ├── CreatingComplaintMessageObserver.php      # Auto-uzupełnianie autora
│       ├── CreatingComplaintObserver.php             # UUID, kod, dane z rezerwacji
│       ├── ExceptionFindComplaintUUIDAccessCode.php  # Rate limiting dostępu
│       ├── FindComplaintUUIDAccessCode.php           # Wyszukiwanie po UUID+kod
│       ├── SendComplaintMessageCreatedNotification.php # Logika powiadomień wiadomości
│       ├── UpdateComplaint.php                      # Aktualizacja danych
│       ├── UpdatedComplaintObserver.php             # Powiadomienia po aktualizacji
│       └── UpdatingComplaintObserver.php            # resolved_at/resolved_by
├── Controller/
│   ├── ComplaintAttachmentController.php            # Kontroler załączników
│   ├── ComplaintController.php                      # Główny kontroler
│   └── ComplaintMessageController.php               # Kontroler wiadomości
├── Model/
│   ├── Complaint.php                                # Model reklamacji
│   └── ComplaintMessage.php                         # Model wiadomości
├── Notification/
│   ├── ComplaintCreatedNotification.php              # Email: reklamacja utworzona
│   ├── ComplaintMessageCreatedNotification.php       # Email: nowa wiadomość
│   ├── ComplaintNewSupervisorNotification.php        # Email: nowy opiekun
│   └── ComplaintUpdatedNotification.php              # Email: aktualizacja
├── Observer/
│   ├── ComplaintMessageObserver.php                  # Observer wiadomości (creating, created)
│   └── ComplaintObserver.php                         # Observer reklamacji (creating, created, updating, updated)
├── Policy/
│   ├── ComplaintMessagePolicy.php                    # Polityka wiadomości
│   └── ComplaintPolicy.php                           # Polityka reklamacji
├── Request/
│   ├── AssignSupervisorComplaintControllerRequest.php
│   ├── ChangeStatusComplaintControllerRequest.php
│   ├── DeleteComplaintAttachmentControllerRequest.php
│   ├── FindComplaintControllerRequest.php
│   ├── IndexComplaintAttachmentControllerRequest.php
│   ├── IndexComplaintControllerRequest.php
│   ├── IndexComplaintMessageControllerRequest.php
│   ├── Rule/
│   │   ├── RequiredWhenNotAuthenticatedRule.php       # Pole wymagane gdy niezalogowany
│   │   ├── ReservationStillToComplaintRule.php        # Warunki złożenia reklamacji
│   │   ├── VerifyComplaintCorrectDataRule.php         # Weryfikacja UUID+kod
│   │   └── VerifySupervisorIsInOrganizationRule.php   # Opiekun z organizacji
│   ├── StoreComplaintAttachmentControllerRequest.php
│   ├── StoreComplaintControllerRequest.php
│   ├── StoreComplaintMessageControllerRequest.php
│   └── UpdateComplaintControllerRequest.php
├── Resource/
│   ├── ComplaintAttachmentResource.php               # JSON załącznika
│   ├── ComplaintMessageResource.php                  # JSON wiadomości
│   └── ComplaintResource.php                         # JSON reklamacji
└── Service/
    ├── ComplaintEnumService.php                       # Statusy, typy, priorytety, typy autorów
    ├── TraitComplaint.php                            # Metody domenowe
    └── TraitControllerComplaint.php                  # Metody kontrolera

Enumy (ComplaintEnumService)

Statusy reklamacji

Enum Wartość Etykieta
COMPLAINT_STATUS_NEW new Nowa
COMPLAINT_STATUS_IN_PROGRESS in_progress W trakcie
COMPLAINT_STATUS_RESOLVED resolved Rozwiązana
COMPLAINT_STATUS_CLOSED closed Zamknięta

Typy reklamacji

Enum Wartość Etykieta
COMPLAINT_TYPE_ISSUE issue Zgłoszenie problemu
COMPLAINT_TYPE_REFUND refund Reklamacja / Zwrot
COMPLAINT_TYPE_SUGGESTION suggestion Sugestia / propozycja
COMPLAINT_TYPE_OTHER other Inne

Priorytety

Enum Wartość Etykieta
COMPLAINT_PRIORITY_LOW low Niski
COMPLAINT_PRIORITY_MEDIUM medium Średni
COMPLAINT_PRIORITY_HIGH high Wysoki

Typy autorów wiadomości

Enum Wartość Etykieta
COMPLAINT_AUTHOR_TYPE_STAFF staff Pracownik
COMPLAINT_AUTHOR_TYPE_RESIDENT resident Mieszkaniec
COMPLAINT_AUTHOR_TYPE_SYSTEM system System

Workflow reklamacji

stateDiagram-v2
    [*] --> new: Złożenie reklamacji
    new --> in_progress: Podjęcie sprawy
    in_progress --> resolved: Rozwiązanie problemu
    resolved --> closed: Zamknięcie sprawy
    in_progress --> new: Ponowne otwarcie
    resolved --> in_progress: Ponowne otwarcie
Status Kod Opis Akcje obserwera
Nowa new Nowa reklamacja, oczekuje na podjęcie Domyślny status przy tworzeniu
W trakcie in_progress W trakcie rozpatrywania Czyści resolved_at i resolved_by
Rozwiązana resolved Problem rozwiązany Ustawia resolved_at i resolved_by
Zamknięta closed Sprawa zamknięta Czyści resolved_at i resolved_by

Odpowiedź API (Resource)

ComplaintResource

Automatycznie ładowane relacje: reservation, organization, supervisor, solver.

Pola niedostępne dla niezalogowanych: can.

Pole Typ Opis
id string (UUID) Identyfikator reklamacji
uuid string (UUID) Publiczny identyfikator
access_code string Kod dostępu (10 znaków)
organization_id string\|null ID organizacji
reservation_id string\|null ID rezerwacji
first_name string Imię zgłaszającego
last_name string Nazwisko zgłaszającego
email string Email zgłaszającego
mobile_phone string Telefon zgłaszającego
type string Kod typu reklamacji
type_lang string Przetłumaczona nazwa typu
message string Treść zgłoszenia
status string Kod statusu
status_lang string Przetłumaczona nazwa statusu
priority string Kod priorytetu
priority_lang string Przetłumaczona nazwa priorytetu
can_be_edit boolean Czy reklamacja może być edytowana
resolved_by string\|null ID użytkownika który rozwiązał
resolved_at string\|null Data rozwiązania (Y-m-d H:i:s)
supervisor_id string\|null ID opiekuna
created_at string\|null Data utworzenia (Y-m-d H:i:s)
updated_at string\|null Data aktualizacji (Y-m-d H:i:s)
can object Uprawnienia (tylko dla zalogowanych)

ComplaintMessageResource

Pola niedostępne dla niezalogowanych: can.

Pole Typ Opis
id string (UUID) Identyfikator wiadomości
complaint_id string\|null ID reklamacji
author_type string Typ autora (staff, resident, system)
author_type_lang string Przetłumaczona nazwa typu autora
author_name string Nazwa autora
author_email string Email autora
message string Treść wiadomości
created_at string\|null Data utworzenia (Y-m-d H:i:s)
updated_at string\|null Data aktualizacji (Y-m-d H:i:s)
can object Uprawnienia (tylko dla zalogowanych)

ComplaintAttachmentResource

Pole Typ Opis
id string ID załącznika
uuid string UUID media
model_type string Typ modelu nadrzędnego
model_id string ID modelu nadrzędnego
collection_name string Nazwa kolekcji (attachments)
name string Nazwa pliku
file_name string Nazwa pliku na dysku
mime_type string Typ MIME
disk string Dysk przechowywania
size integer Rozmiar w bajtach
size_human string Rozmiar czytelny (np. 2.5 MB)
url string URL do pobrania
type string Typ mediów (image, video, document)
custom_properties object Właściwości niestandardowe
created_at string\|null Data utworzenia (Y-m-d H:i:s)
updated_at string\|null Data aktualizacji (Y-m-d H:i:s)