Klienci (Client)¶
Baza stałych klientów — osoby fizyczne i firmy z historią rezerwacji i indywidualnymi cenami.
Ostatnia aktualizacja dokumentacji: 26 lutego 2026 Stan synchronizacji z kodem: Zsynchronizowany
Co to jest?¶
Domena Klientów zarządza bazą osób i firm korzystających z systemu rezerwacji. To jak kartoteka klientów w recepcji — z pełną historią rezerwacji, preferencjami VAT i możliwością ustalenia indywidualnych cen.
Klient może być: - Osobą fizyczną — Jan Kowalski - Firmą — ABC Sp. z o.o. (z NIP i REGON)
Jakie problemy rozwiązuje?¶
Korzyści dla JST
- Historia klienta — wszystkie rezerwacje w jednym miejscu
- Indywidualne cenniki — rabaty dla stałych klientów
- Obsługa firm — prawidłowe faktury z NIP
- Statystyki finansowe — ile klient wydał, ile zwrócono
- Raporty — ranking najlepszych klientów, nowe rejestracje
Struktura domeny¶
app/Domain/Client/
├── Action/
│ ├── Attr/
│ │ ├── GetAmountDueAttr.php # Obliczanie zaległości
│ │ ├── GetTotalPendingAttr.php # Obliczanie kwot oczekujących
│ │ ├── GetTotalRefundedAttr.php # Obliczanie zwrotów
│ │ └── GetTotalSpentAttr.php # Obliczanie wydatków
│ ├── Controller/
│ │ ├── IndexClientController.php # Listowanie klientów z filtrami
│ │ ├── ShowClientController.php # Szczegóły klienta
│ │ ├── StoreClientController.php # Tworzenie klienta
│ │ └── UpdateClientController.php # Aktualizacja klienta
│ ├── Other/
│ │ ├── ApplyClientAggregatesOrm.php # Optymalizacja zapytań SQL
│ │ ├── CreateClient.php # Tworzenie klienta (Client::create)
│ │ ├── FindClientByEmail.php # Wyszukiwanie po email
│ │ ├── FindClientByNip.php # Wyszukiwanie po NIP
│ │ ├── FindOrCreateClient.php # Znajdź lub utwórz klienta
│ │ ├── GenerateClientColor.php # Losowanie koloru klienta
│ │ ├── UpdateClient.php # Aktualizacja danych klienta
│ │ └── UpdateClientIfNeeded.php # Warunkowa aktualizacja pustych pól
│ └── Scope/
│ └── ScopeSearchFunc.php # Zakres wyszukiwania tekstowego
├── Controller/
│ └── ClientController.php # Główny kontroler
├── Model/
│ └── Client.php # Model Eloquent
├── Observer/
│ └── ClientObserver.php # Observer (pusty - zarezerwowany)
├── Policy/
│ └── ClientPolicy.php # Polityka autoryzacji
├── Request/
│ ├── IndexClientControllerRequest.php # Walidacja listy
│ ├── StoreClientControllerRequest.php # Walidacja tworzenia
│ └── UpdateClientControllerRequest.php # Walidacja edycji
├── Resource/
│ └── ClientResource.php # Transformacja JSON API
└── Service/
├── ClientEnumService.php # Enum (pusty - zarezerwowany)
├── TraitClient.php # Metody domenowe
└── TraitControllerClient.php # Metody kontrolera
Kluczowe funkcjonalności¶
Dla administratora¶
| Funkcja | Opis | Status |
|---|---|---|
| Lista klientów | Przegląd wszystkich klientów z filtrami | Dostępne |
| Tworzenie klientów | Ręczne dodawanie klientów | Dostępne |
| Edycja danych | Aktualizacja kontaktu, adresu, VAT | Dostępne |
| Aktywacja/dezaktywacja | Zmiana statusu is_active klienta | Dostępne |
| Historia rezerwacji | Podgląd wszystkich rezerwacji klienta | Dostępne |
| Statystyki finansowe | Wydatki, zwroty, saldo | Dostępne |
| Indywidualne cenniki | Przypisanie rabatowych stawek | Dostępne |
| Notatki | Wewnętrzne komentarze o kliencie | Dostępne |
| Raporty | Nowe rejestracje, ranking wydatków | Dostępne |
Jak to działa?¶
Scenariusz 1: Automatyczne tworzenie klienta¶
flowchart LR
A[Mieszkaniec rezerwuje] --> B{Czy istnieje klient?}
B -->|NIP znany<br>i firma| C[Aktualizuj puste pola]
B -->|Email znany| C
B -->|Nowy| D[Utwórz klienta]
D --> E[Losuj kolor]
C --> F[Powiąż z rezerwacją]
E --> F - Mieszkaniec składa rezerwację podając dane
- System sprawdza kolejno:
- Czy istnieje klient z tym NIP (jeśli firma)?
- Czy istnieje klient z tym emailem?
- Jeśli znaleziono — aktualizuje tylko puste pola (nie nadpisuje istniejących)
- Jeśli nie znaleziono — tworzy nowego klienta z automatycznie wylosowanym kolorem
- Rezerwacja jest powiązana z klientem
Pola aktualizowane automatycznie
Przy automatycznej aktualizacji (UpdateClientIfNeeded) system uzupełnia tylko puste pola z listy: first_name, last_name, mobile_phone, company_name, nip, regon, street, street_number, postal_code, city, commune, district, voivodeship, teryt_data. Email nie jest aktualizowany — jest używany wyłącznie do identyfikacji. Dodatkowo, jeśli klient nie był firmą, a nowa rezerwacja wskazuje is_company=true, status zostanie zmieniony na firmę.
Scenariusz 2: Indywidualny cennik dla firmy¶
- Firma "ABC Sp. z o.o." regularnie wynajmuje salę
- Administrator tworzy dla niej indywidualny cennik: 80 zł/godz. (zamiast 100 zł)
- Przy każdej rezerwacji tej firmy — automatycznie stosowana jest niższa cena
- Na fakturze widoczna jest stawka 80 zł
Scenariusz 3: Sprawdzenie historii klienta¶
- Administrator otwiera kartę klienta
- Widzi wszystkie rezerwacje, płatności, zwroty
- Widzi statystyki: łącznie wydał 5000 zł, zwrócono 200 zł
- Może dodać notatkę wewnętrzną
Struktura tabeli w bazie danych¶
Tabela clients¶
| Kolumna | Typ | Nullable | Default | Opis |
|---|---|---|---|---|
id | UUID | Nie | auto | Klucz główny |
first_name | string | Nie | — | Imię klienta |
last_name | string | Nie | — | Nazwisko klienta |
email | string | Nie | — | Adres email (unikalny) |
mobile_phone | string | Tak | null | Numer telefonu |
is_company | boolean | Nie | false | Czy klient jest firmą |
company_name | string | Tak | null | Nazwa firmy |
nip | string | Tak | null | NIP firmy (unikalny) |
regon | string | Tak | null | REGON firmy |
street | string | Tak | null | Ulica |
street_number | string | Tak | null | Numer budynku/lokalu |
postal_code | string | Tak | null | Kod pocztowy |
city | string | Tak | null | Miasto |
commune | string | Tak | null | Gmina |
district | string | Tak | null | Powiat |
voivodeship | string | Tak | null | Województwo |
teryt_data | JSON | Nie | {} | Dane adresowe z systemu TERYT |
vat_rate | string(10) | Nie | '23' | Kod stawki VAT |
vat_note | string(255) | Tak | null | Notatka dotycząca VAT |
color | string | Tak | null | Kolor HEX do identyfikacji |
is_active | boolean | Nie | true | Status aktywności |
access_pin_hash | string(255) | Tak | null | Zahashowany kod PIN |
access_pin_created_at | timestamp | Tak | null | Data utworzenia PIN |
access_pin_request_id | UUID | Tak | null | FK do access_pin_requests |
access_enabled | boolean | Nie | true | Czy dostęp PIN włączony |
created_at | timestamp | Nie | auto | Data utworzenia |
updated_at | timestamp | Nie | auto | Data aktualizacji |
Klucze obce¶
| Kolumna | Referencja | On Delete |
|---|---|---|
access_pin_request_id | access_pin_requests.id | SET NULL |
Indeksy¶
| Kolumny | Nazwa | Typ |
|---|---|---|
first_name | — | INDEX |
last_name | — | INDEX |
email | — | UNIQUE |
nip | — | UNIQUE |
is_active | — | INDEX |
mobile_phone | — | INDEX |
id, first_name, last_name | clients_id_name_index | INDEX (kompozytowy) |
nip, is_active | clients_nip_active_index | INDEX (kompozytowy) |
Tabele powiązane¶
| Tabela | Kolumna FK | On Delete | Opis |
|---|---|---|---|
reservations | client_id | SET NULL | Rezerwacje klienta |
pricings | client_id | CASCADE | Indywidualne cenniki klienta |
Endpointy API¶
Wszystkie endpointy wymagają uwierzytelnienia (JWT) i roli ADMIN, SUPERVISOR lub EMPLOYEE.
| Metoda | Endpoint | Opis |
|---|---|---|
GET | /api/clients | Lista klientów z filtrami i paginacją |
GET | /api/clients/{clientId} | Szczegóły klienta |
POST | /api/clients | Tworzenie nowego klienta |
PATCH | /api/clients/{clientId} | Aktualizacja danych klienta |
PATCH | /api/clients/{clientId}/change-state | Zmiana statusu is_active |
Brak endpointów publicznych
Domena Client nie posiada endpointów publicznych (noAuth). Wszystkie operacje wymagają uwierzytelnienia.
GET /api/clients¶
Lista klientów z filtrami, paginacją i agregatami finansowymi.
Parametry zapytania:
| Parametr | Typ | Opis |
|---|---|---|
filter[search] | string | Wyszukiwanie w: first_name, last_name, email, company_name, nip |
filter[id] | UUID | Dokładne dopasowanie po ID |
filter[is_company] | boolean | Filtr: firma / osoba fizyczna |
filter[is_active] | boolean | Filtr: aktywny / nieaktywny |
page | integer | Numer strony |
per_page | integer | Ilość elementów na stronie |
Response (sukces — 200):
Paginowana kolekcja ClientResource (patrz sekcja Odpowiedź API).
GET /api/clients/{clientId}¶
Szczegóły pojedynczego klienta z agregatami finansowymi.
Parametry URL:
| Parametr | Typ | Opis |
|---|---|---|
clientId | UUID | Identyfikator klienta |
Response (sukces — 200): Pojedynczy obiekt ClientResource.
Response (błąd — 404): NotFoundModelException jeśli klient nie istnieje.
POST /api/clients¶
Tworzenie nowego klienta. Wymaga autoryzacji (ClientPolicy@create).
Body (Request):
| Pole | Typ | Wymagane | Walidacja |
|---|---|---|---|
first_name | string | Tak | required\|string\|max:255 |
last_name | string | Tak | required\|string\|max:255 |
email | string | Tak | required\|email\|max:255\|unique:clients |
is_company | boolean | Tak | required\|boolean |
company_name | string | Jeśli firma | required_if:is_company,true\|string\|max:255 |
nip | string | Jeśli firma | required_if:is_company,true\|string\|unique:clients\|ValidCorrectNIPRule |
mobile_phone | string | Nie | regex:/^[0-9]+$/\|min:7\|max:15 |
regon | string | Nie | string\|max:20\|ValidCorrectRegonRule |
street | string | Nie | string\|max:255 |
street_number | string | Nie | string\|max:255 |
postal_code | string | Nie | regex:/^[0-9]{2}-[0-9]{3}$/ |
city | string | Nie | string\|max:255 |
commune | string | Nie | string\|max:255 |
district | string | Nie | string\|max:255 |
voivodeship | string | Nie | string\|max:255 |
teryt_data | array | Nie | array |
color | string | Nie | hex_color\|not_in:#000,#000000 |
vat_rate | string | Nie | in: wartości z VatEnumService::getAllRates() |
vat_note | string | Nie | string\|max:255 |
Response (sukces — 200): "The Client has been successfully added"
PATCH /api/clients/{clientId}¶
Aktualizacja danych klienta. Wymaga autoryzacji (ClientPolicy@update).
Wszystkie pola są opcjonalne (sometimes). Email i NIP zachowują unikalność z wykluczeniem aktualnego rekordu.
Response (sukces — 200): "The Client has been successfully updated"
Response (błąd — 404): NotFoundModelException jeśli klient nie istnieje.
PATCH /api/clients/{clientId}/change-state¶
Przełączenie statusu is_active klienta. Wymaga autoryzacji (ClientPolicy@update).
Response (sukces — 200): Komunikat o zmianie statusu.
Powiązania z innymi domenami¶
| Domena | Powiązanie | Typ relacji |
|---|---|---|
| Rezerwacje (Reservation) | Klient ma wiele rezerwacji | HasMany |
| Rezerwacje (Reservation) | FindOrCreateClientForReservation — automatyczne tworzenie klienta przy rezerwacji | Cross-domain |
| Cenniki (Pricing) | Klient może mieć indywidualne cenniki | HasMany |
| Notatki (Note) | Do klienta można dodawać notatki | MorphMany (polimorficzna) |
| Dostęp (Access) | Klient może mieć kod PIN do logowania | BelongsTo (AccessPinRequest) |
| Harmonogramy (Schedule) | Filtrowanie harmonogramu po kliencie | Pośrednie |
| Raporty (Report) | clients.new_registrations — raport nowych rejestracji | Cross-domain |
| Raporty (Report) | clients.top_spenders — ranking klientów wg wydatków | Cross-domain |
Integracja z domeną Rezerwacji¶
Klasa FindOrCreateClientForReservation (w domenie Reservation) wyciąga dane klienta z walidowanych danych rezerwacji i deleguje do FindOrCreateClient:
flowchart LR
A[Dane rezerwacji] --> B[FindOrCreateClientForReservation]
B --> C{Email lub NIP podany?}
C -->|Nie| D[Zwróć null]
C -->|Tak| E[FindOrCreateClient]
E --> F[Klient] Pola wyciągane z rezerwacji: first_name, last_name, email, mobile_phone, is_company, company_name, nip, regon, street, street_number, postal_code, city, commune, district, voivodeship.
Warunek konieczny
Jeśli w danych rezerwacji brakuje zarówno email jak i nip, klient nie jest tworzony — metoda zwraca null.
Integracja z domeną Raportów¶
Domena Client jest źródłem danych dla 2 dedykowanych raportów:
clients.new_registrations — Raport nowych rejestracji klientów:
- Grupowanie po dacie (
DATE(created_at)) - Filtrowanie po okresie (
this_month,last_month,this_year,last_year,custom) - Opcjonalne filtrowanie po
organization_id - Kolumny: Data, Liczba nowych klientów
clients.top_spenders — Ranking klientów wg wydatków:
- Dane z rezerwacji o statusie
RESERVATION_STATUS_CONFIRMED - Filtrowanie po okresie i opcjonalnie po
organization_id - Konfigurowalny limit wyników (domyślnie 10, max 30)
- Kolumny: Pozycja, Nazwa klienta, Email, Telefon, Liczba rezerwacji, Liczba slotów, Suma godzin, Zapłacone, Zwroty, Przychód razem
Wartość biznesowa¶
Argumenty dla decydentów
- Wiedza o kliencie — pełna historia w jednym miejscu
- Lojalizacja — możliwość oferowania rabatów stałym klientom
- Prawidłowe faktury — NIP, REGON, dane adresowe
- Analityka — ile klient wydał, jaki ma potencjał
- Raporty — ranking najlepszych klientów, analiza nowych rejestracji
Typy klientów¶
Osoba fizyczna¶
| Pole | Wymagane | Walidacja | Opis |
|---|---|---|---|
Imię (first_name) | Tak | string, max 255 | Imię klienta |
Nazwisko (last_name) | Tak | string, max 255 | Nazwisko klienta |
Email (email) | Tak | email, max 255, unikalny | Unikalny adres email |
Telefon (mobile_phone) | Nie | regex: /^[0-9]+$/, 7-15 znaków | Numer kontaktowy (tylko cyfry) |
| Adres | Nie | — | Ulica, numer, kod pocztowy, miasto |
Dane TERYT (teryt_data) | Nie | array | Dane adresowe z systemu TERYT |
Firma¶
| Pole | Wymagane | Walidacja | Opis |
|---|---|---|---|
Nazwa firmy (company_name) | Tak (jeśli firma) | string, max 255 | Pełna nazwa firmy |
NIP (nip) | Tak (jeśli firma) | walidacja sumy kontrolnej (ValidCorrectNIPRule), unikalny | 10 cyfr |
REGON (regon) | Nie | walidacja (ValidCorrectRegonRule), max 20 | 9 lub 14 cyfr |
Email (email) | Tak | email, max 255, unikalny | Email kontaktowy |
Telefon (mobile_phone) | Nie | regex: /^[0-9]+$/, 7-15 znaków | Numer kontaktowy |
| Adres | Nie | kod pocztowy: regex: /^[0-9]{2}-[0-9]{3}$/ | Siedziba firmy |
Walidacja przy aktualizacji
Przy aktualizacji (PATCH) wszystkie pola są opcjonalne (sometimes). Email i NIP zachowują unikalność z wykluczeniem aktualnego rekordu.
Obsługa VAT¶
Każdy klient może mieć przypisaną stawkę VAT (domyślnie 23%):
| Stawka | Kod | Wartość numeryczna | Opis |
|---|---|---|---|
| 23% | 23 | 23.00 | Standardowa stawka |
| 8% | 8 | 8.00 | Obniżona |
| 5% | 5 | 5.00 | Super obniżona |
| 0% | 0 | 0.00 | Zerowa |
| Zwolniony | zw | null | Zwolnienie z VAT |
| Nie podlega | np | null | Nie podlega opodatkowaniu |
Stawki VAT są zarządzane przez VatEnumService z domeny Pricing.
| Pole | Opis |
|---|---|
vat_rate | Kod stawki VAT (domyślnie 23) |
vat_rate_label | Etykieta tekstowa (np. 23%, Zwolniony) — atrybut obliczany |
vat_rate_numeric | Wartość numeryczna stawki (np. 23.00, null) — atrybut obliczany |
vat_note | Notatka dotycząca VAT (opcjonalna, max 255 znaków) |
Dziedziczenie stawki VAT
Stawka VAT klienta jest używana przy obliczaniu cen rezerwacji, chyba że cennik ma własne nadpisanie.
Statystyki finansowe¶
System automatycznie oblicza dla każdego klienta:
| Metryka | Opis | Sposób obliczania |
|---|---|---|
total_spent | Suma opłaconych płatności | Suma amount z ReservationPayment o statusach RES_COMPLETED lub RES_PARTIALLY_REFUNDED |
total_refunded | Suma zwrotów | Suma amount z ReservationPaymentRefund o statusie RES_COMPLETED, powiązanych z płatnościami o statusach RES_COMPLETED / RES_PARTIALLY_REFUNDED |
total_balance | Saldo (wydane - zwrócone) | total_spent - total_refunded |
total_pending | Kwota oczekująca na płatność | Suma (total_price - total_paid) z rezerwacji o statusach RES_PENDING / RESERVATION_STATUS_CONFIRMED i payment_status RES_PENDING / PAYMENT_STATUS_PARTIALLY_PAID |
amount_due | Zaległości do zapłaty | Alias dla total_pending — ta sama wartość |
total_reservations | Liczba rezerwacji | reservations_count lub reservations()->count() |
is_in_use | Czy klient ma rezerwacje | reservations_exists lub reservations()->exists() |
Optymalizacja zapytań
Na liście klientów (index) i w widoku szczegółowym (show) statystyki są obliczane za pomocą subzapytań SQL (ApplyClientAggregatesOrm) zamiast osobnych zapytań dla każdego klienta. System dodaje computed_total_spent, computed_total_refunded i computed_total_pending jako podzapytania SELECT, a także withCount('reservations') i withExists('reservations') — co eliminuje problem N+1.
Różnica między GetTotalPendingAttr a GetAmountDueAttr
GetTotalPendingAttr używa withoutGlobalScope(UserAccessesGlobalScope) co pomija ograniczenia dostępu użytkownika. GetAmountDueAttr używa standardowego Reservation::query() bez usuwania scope'ów. W praktyce zwracają tą samą wartość, ponieważ amount_due jest aliasem total_pending w modelu.
Kolory klientów¶
Każdy klient ma przypisany kolor do identyfikacji w harmonogramie. System posiada paletę 15 kolorów:
| Kolor | Hex |
|---|---|
| Niebieski | #3B82F6 |
| Czerwony | #EF4444 |
| Zielony | #10B981 |
| Żółty/Bursztynowy | #F59E0B |
| Fioletowy | #8B5CF6 |
| Różowy | #EC4899 |
| Turkusowy | #06B6D4 |
| Limonkowy | #84CC16 |
| Pomarańczowy | #F97316 |
| Indygo | #6366F1 |
| Morski | #14B8A6 |
| Jasny fiolet | #A855F7 |
| Jasny zielony | #22C55E |
| Ciepły żółty | #EAB308 |
| Jasny niebieski | #0EA5E9 |
Automatyczne przypisanie
Kolor jest automatycznie losowany z palety przy automatycznym tworzeniu klienta (przez FindOrCreateClient podczas rezerwacji). Przy ręcznym tworzeniu przez API kolor nie jest generowany automatycznie — można go podać w żądaniu. Kolor nie może być czarny (#000 / #000000).
Wyszukiwanie i filtrowanie klientów¶
Dostępne filtry¶
| Filtr | Typ | Opis |
|---|---|---|
filter[search] | Scope (częściowe dopasowanie) | Szuka w: first_name, last_name, email, company_name, nip |
filter[id] | Exact | Dokładne dopasowanie UUID |
filter[is_company] | Exact | Tylko firmy (true) lub osoby fizyczne (false) |
filter[is_active] | Exact | Tylko aktywni (true) lub nieaktywni (false) |
Brak indywidualnych filtrów tekstowych
Filtry takie jak first_name, last_name, email, company_name, nip nie są dostępne jako osobne filtry. Wszystkie te pola są przeszukiwane łącznie przez filtr search (częściowe dopasowanie LIKE).
Zabezpieczenia wyszukiwania
Scope ScopeSearchFunc automatycznie escapuje znaki specjalne SQL (%, _) w wartości wyszukiwania, co zapobiega nieoczekiwanym wynikom i SQL injection.
Paginacja¶
Endpoint GET /api/clients obsługuje standardowe parametry paginacji zdefiniowane w paginationDefaultRequestRules().
Dostęp PIN dla klientów¶
Klienci mogą mieć kod PIN do logowania w systemie:
| Pole | Typ | Opis |
|---|---|---|
access_pin_hash | string (ukryty) | Zahashowany kod PIN — pole oznaczone jako $hidden w modelu |
access_pin_created_at | datetime | Kiedy PIN został utworzony |
access_pin_request_id | string (UUID) | Powiązanie z żądaniem PIN (AccessPinRequest) |
access_enabled | boolean | Czy dostęp PIN jest włączony |
has_access_pin | boolean (obliczany) | Czy klient posiada PIN (access_pin_hash !== null) |
Więcej o PIN
Szczegóły systemu PIN znajdują się w dokumentacji domeny Dostęp (Access).
Automatyczne tworzenie klientów¶
Klasa FindOrCreateClient zarządza automatycznym tworzeniem i rozpoznawaniem klientów przy rezerwacji:
flowchart TD
A[Dane klienta z rezerwacji] --> B{is_company=true<br>i NIP podany?}
B -->|Tak| C{Istnieje klient<br>z tym NIP?}
C -->|Tak| D[Aktualizuj puste pola]
C -->|Nie| E{Istnieje klient<br>z tym emailem?}
B -->|Nie| E
E -->|Tak| D
E -->|Nie| F[Utwórz nowego klienta]
F --> G[Wylosuj kolor]
G --> H[Zwróć klienta]
D --> H - Sprawdzenie NIP (dla firm) — jeśli istnieje klient z tym NIP, aktualizuje puste pola
- Sprawdzenie email — jeśli istnieje klient z tym emailem, aktualizuje puste pola
- Tworzenie nowego — jeśli nie znaleziono dopasowania, tworzy nowego z losowym kolorem
Aktualizacja danych (UpdateClientIfNeeded)
System uzupełnia wyłącznie puste pola z określonej listy:
first_name,last_name,mobile_phonecompany_name,nip,regonstreet,street_number,postal_code,citycommune,district,voivodeship,teryt_data
Email NIE jest aktualizowany — służy wyłącznie do identyfikacji klienta.
Dodatkowo, jeśli klient nie był firmą (is_company=false), a nowe dane wskazują is_company=true, status zostanie zmieniony na firmę.
Odpowiedź API (Resource)¶
Endpoint zwraca następujące pola w odpowiedzi JSON:
| Pole | Typ | Opis |
|---|---|---|
id | string (UUID) | Identyfikator klienta |
first_name | string | Imię |
last_name | string | Nazwisko |
email | string | |
mobile_phone | string\|null | Telefon |
is_company | boolean | Czy firma |
company_name | string\|null | Nazwa firmy |
nip | string\|null | NIP |
regon | string\|null | REGON |
street | string\|null | Ulica |
street_number | string\|null | Numer budynku/lokalu |
postal_code | string\|null | Kod pocztowy |
city | string\|null | Miasto |
commune | string\|null | Gmina |
district | string\|null | Powiat |
voivodeship | string\|null | Województwo |
teryt_data | array\|null | Dane adresowe TERYT |
color | string\|null | Kolor HEX klienta |
is_active | boolean | Czy aktywny |
vat_rate | string | Stawka VAT (domyślnie 23) |
vat_rate_label | string | Etykieta stawki VAT |
vat_note | string\|null | Notatka VAT |
total_reservations | integer | Liczba rezerwacji |
is_in_use | boolean | Czy ma rezerwacje |
access_pin_hash | string\|null | Hash PIN (pole ukryte w modelu) |
access_pin_created_at | string\|null | Data utworzenia PIN |
access_pin_request_id | string\|null | ID żądania PIN |
access_enabled | boolean | Czy dostęp PIN włączony |
created_at | string\|null | Data utworzenia (format: Y-m-d H:i:s) |
updated_at | string\|null | Data aktualizacji (format: Y-m-d H:i:s) |
total_spent | float | Suma wydatków |
total_refunded | float | Suma zwrotów |
total_pending | float | Kwoty oczekujące |
total_balance | float | Saldo (wydane - zwrócone) |
amount_due | float | Zaległości (alias total_pending) |
can | object | Uprawnienia użytkownika (patrz niżej) |
Obiekt can (uprawnienia)¶
| Klucz | Typ | Opis |
|---|---|---|
view | boolean | Czy użytkownik może wyświetlić klienta |
create | boolean | Czy użytkownik może tworzyć klientów |
update | boolean | Czy użytkownik może edytować klienta |
Ukrywanie uprawnień
Pole can jest usuwane z odpowiedzi dla nieuwierzytelnionych użytkowników (UNALLOWED_NO_AUTH_KEYS). W praktyce domena Client nie ma endpointów publicznych, więc ten mechanizm ma znaczenie tylko w kontekście ewentualnego rozszerzenia.
Relacje
ClientResource::RELATIONS jest pustą tablicą — klient nie ładuje automatycznie żadnych relacji zagnieżdżonych w odpowiedzi.
Uprawnienia¶
Middleware (role)¶
| Rola | Podgląd | Tworzenie | Edycja | Zmiana statusu |
|---|---|---|---|---|
| Administrator | Tak | Tak | Tak | Tak |
| Supervisor | Tak | Tak | Tak | Tak |
| Employee | Tak | Tak | Tak | Tak |
Middleware: role:ADMIN|SUPERVISOR|EMPLOYEE — stosowany do wszystkich metod kontrolera (index, show, store, update, changeState).
Policy (ClientPolicy)¶
| Metoda | Logika | Opis |
|---|---|---|
view(User, Client) | Zawsze true | Każdy uwierzytelniony użytkownik z odpowiednią rolą może przeglądać |
create(User) | Zawsze true | Każdy uwierzytelniony użytkownik z odpowiednią rolą może tworzyć |
update(User, Client) | Zawsze true | Każdy uwierzytelniony użytkownik z odpowiednią rolą może edytować |
Dwupoziomowa autoryzacja
Autoryzacja opiera się na dwóch warstwach: middleware sprawdza role (ADMIN|SUPERVISOR|EMPLOYEE), a policy pozwala na wszystkie operacje w ramach tych ról. Efektywnie — dostęp mają tylko użytkownicy z jedną z trzech ról.
Eventy i efekty uboczne¶
Brak dedykowanych eventów
Domena Client nie posiada dedykowanych eventów, jobów, notyfikacji ani listenerów. Observer (ClientObserver) jest zarejestrowany, ale pusty — zarezerwowany na przyszłe rozszerzenia.
Model Client używa traitu Notifiable z Laravel, co umożliwia wysyłanie notyfikacji do klienta w przyszłości.
Znane ograniczenia i TODO¶
- Pusty observer —
ClientObserverjest zarezerwowany, ale nie implementuje żadnych hooków - Pusty enum —
ClientEnumServicejest zarezerwowany na przyszłe statusy/typy klientów - Brak soft delete — klienci nie są usuwani miękko (brak
SoftDeletes), zamiast tego używana jest flagais_active - Brak endpointu DELETE — klienci nie mogą być usuwani przez API, jedynie dezaktywowani
GetAmountDueAttrbez GlobalScope —GetAmountDueAttrnie używawithoutGlobalScopew przeciwieństwie doGetTotalPendingAttr, co może prowadzić do różnic w wynikach jeśliUserAccessesGlobalScopejest aktywny (w praktyceamount_duew modelu jest aliasemtotal_pending, więc różnica nie jest widoczna w API)