Przejdź do treści

Organizacje (Organization)

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

Jednostki zarządzające obiektami — szkoły, MOSiR-y, domy kultury i inne instytucje publiczne.


1. Opis ogólny

Domena Organizacji zarządza jednostkami odpowiedzialnymi za obiekty i zajęcia w systemie rezerwacji. To jak struktura organizacyjna gminy — każda szkoła, ośrodek sportowy czy dom kultury to osobna organizacja, która ma swoje obiekty, pracowników i rezerwacje.

System obsługuje 22 typy organizacji (od szkół po parki i kluby sportowe) i umożliwia przypisywanie pracowników, zarządzanie stanem aktywności oraz kaskadowe propagowanie zmian na powiązane encje.

Jakie problemy rozwiązuje?

Korzyści dla JST

  • Podział odpowiedzialności — każda jednostka zarządza swoimi obiektami
  • Przypisanie pracowników — wiadomo kto za co odpowiada
  • Raportowanie — statystyki per organizacja (obiekty, zajęcia, pracownicy, opinie)
  • Wielotenantowość — wiele jednostek w jednym systemie
  • Kaskadowa kontrola stanu — dezaktywacja organizacji automatycznie ukrywa jej zasoby

2. Architektura domeny

2.1 Modele danych

Model Organization

  • Plik: app/Domain/Organization/Model/Organization.php
  • Tabela: organizations
  • Klucz główny: UUID (trait HasUuids, $incrementing = false, $keyType = 'string')
  • Bazowy model: Infrastructure\Vendor\CustomModel
  • Traity: TraitOrganization, TraitSupport
  • Observer: OrganizationObserver (atrybut #[ObservedBy])

Pola fillable (17):

Pole Typ (cast) Opis
type string Typ organizacji (enum — 22 wartości)
nip string Numer NIP (10 cyfr, unikalny)
regon string Numer REGON (unikalny)
name string Pełna nazwa organizacji
email string Adres email kontaktowy
mobile_phone string Telefon komórkowy
stationary_phone string Telefon stacjonarny (nullable)
street string Ulica
street_number string Numer budynku
postal_code string Kod pocztowy (6 znaków)
city string Miejscowość
commune string Gmina
district string Powiat
voivodeship string Województwo
teryt_data json Dane TERYT (tablica)
is_active boolean Czy organizacja jest aktywna

Atrybuty wyliczane (accessory):

Atrybut Typ Opis
total_items int Liczba obiektów (delegowane do GetTotalItemsAttr)
total_activities int Liczba zajęć (delegowane do GetTotalActivitiesAttr)
total_employees int Liczba pracowników (delegowane do GetTotalEmployeesAttr)
total_reviews int Liczba opinii (delegowane do getTotalReviewsAttr)
avg_reviews float Średnia ocen (zaokrąglona do 2 miejsc)
is_in_use bool Czy organizacja ma rezerwacje, opinie lub reklamacje

Optymalizacja agregatów

Atrybuty avg_reviews i is_in_use sprawdzają najpierw pre-loaded dane z array_key_exists() (np. reviews_avg_rating, reservations_exists). Jeśli dane nie są załadowane, wykonują zapytania. Dzięki ApplyOrganizationAggregatesOrm dane te są ładowane w jednym zapytaniu SQL na listach.

Relacje (7 × HasMany):

Relacja Model docelowy Opis
organizationEmployees() OrganizationEmployee Pracownicy organizacji
items() Item Obiekty/zasoby
relActivities() Activity Zajęcia/aktywności
schedules() Schedule Harmonogramy
reservations() Reservation Rezerwacje
reviews() Review Opinie
complaints() Complaint Reklamacje

Filtry publiczne (query builder): id, type, name, city, commune, district, voivodeship


Model OrganizationEmployee

  • Plik: app/Domain/Organization/Model/OrganizationEmployee.php
  • Tabela: organization_employees
  • Klucz główny: UUID (trait HasUuids)
  • Observer: OrganizationEmployeeObserver (atrybut #[ObservedBy])
  • Rola: Model pośredni (junction table) łączący User z Organization

Pola fillable (2):

Pole Typ (cast) Opis
organization_id string UUID organizacji
user_id string UUID użytkownika

Relacje (2 × BelongsTo):

Relacja Model docelowy
organization() Organization
user() User

Filtry publiczne: id, organization_id, user_id, organization.type, user.name, organization.nip, organization.regon, organization.name, created_at


2.2 Diagram relacji

erDiagram
    ORGANIZATION ||--o{ ORGANIZATION_EMPLOYEE : "ma pracowników"
    USER ||--o{ ORGANIZATION_EMPLOYEE : "pracuje w"
    ORGANIZATION ||--o{ ITEM : "posiada obiekty"
    ORGANIZATION ||--o{ ACTIVITY : "prowadzi zajęcia"
    ORGANIZATION ||--o{ SCHEDULE : "ma harmonogramy"
    ORGANIZATION ||--o{ RESERVATION : "ma rezerwacje"
    ORGANIZATION ||--o{ REVIEW : "ma opinie"
    ORGANIZATION ||--o{ COMPLAINT : "ma reklamacje"

    ORGANIZATION {
        uuid id PK
        enum type
        string nip UK
        string regon UK
        string name
        string email
        boolean is_active
    }

    ORGANIZATION_EMPLOYEE {
        uuid id PK
        uuid organization_id FK
        uuid user_id FK
    }

2.3 Struktura tabel w bazie danych

Migracja: database/migrations/0001_01_01_000014_create_organizations.php

Tabela organizations

Kolumna Typ Nullable Default Indeks Opis
id uuid Nie PK Klucz główny
type enum Nie INDEX Typ organizacji (22 wartości z OrganizationEnumService)
nip string Nie UNIQUE + INDEX NIP (10 cyfr)
regon string Nie UNIQUE + INDEX REGON
name string Nie INDEX Nazwa organizacji
email string Nie INDEX Email kontaktowy
mobile_phone string Nie Telefon komórkowy
stationary_phone string Tak Telefon stacjonarny
is_active boolean Nie true Czy aktywna
street string Nie Ulica
street_number string Nie Numer budynku
postal_code string(6) Nie Kod pocztowy
city string Nie Miejscowość
commune string Nie Gmina
district string Nie Powiat
voivodeship string Nie Województwo
teryt_data json Nie [] Dane TERYT
created_at timestamp Tak Data utworzenia
updated_at timestamp Tak Data aktualizacji

Tabela organization_employees

Kolumna Typ Nullable Default Indeks Opis
id uuid Nie PK Klucz główny
organization_id uuid Nie INDEX + FK Organizacja
user_id uuid Nie INDEX + FK Użytkownik
created_at timestamp Tak Data utworzenia
updated_at timestamp Tak Data aktualizacji

Constrainty:

  • UNIQUE (organization_id, user_id) — ten sam użytkownik nie może być dwukrotnie przypisany do tej samej organizacji
  • FK organization_id → organizations.id ON DELETE CASCADE
  • FK user_id → users.id ON DELETE CASCADE

3. Endpointy API

3.1 Organizacje

GET /api/organizations

  • Opis: Lista organizacji z paginacją i filtrowaniem
  • Autoryzacja: Publiczny (dostępny bez logowania) + wersja autentykowana
  • Query Parameters:
    • filter[id] — filtr exact po UUID
    • filter[type] — filtr exact po typie organizacji
    • Standardowe parametry paginacji (page, per_page, sort)
  • Response (sukces): 200 OK
    {
      "data": [
        {
          "id": "uuid",
          "type": "school",
          "type_lang": "Szkoła",
          "nip": "1234567890",
          "regon": "123456789",
          "name": "Szkoła Podstawowa nr 1",
          "email": "szkola1@example.com",
          "mobile_phone": "123456789",
          "stationary_phone": null,
          "street": "ul. Szkolna",
          "street_number": "1",
          "postal_code": "00-001",
          "city": "Warszawa",
          "commune": "Warszawa",
          "district": "Warszawa",
          "voivodeship": "mazowieckie",
          "teryt_data": [],
          "is_active": true,
          "created_at": "2026-01-15 10:00:00",
          "updated_at": "2026-01-15 10:00:00",
          "can_be_edit": true,
          "total_items": 5,
          "total_activities": 3,
          "total_employees": 2,
          "total_reviews": 10,
          "avg_reviews": 4.25,
          "can": {}
        }
      ],
      "meta": { "...paginacja..." }
    }
    
  • Uwaga: Dla niezalogowanych użytkowników pola can_be_edit, total_items, total_activities, total_employees, is_in_use i can nie są zwracane

GET /api/organizations/{organizationId}

  • Opis: Szczegóły jednej organizacji
  • Autoryzacja: Publiczny (dostępny bez logowania) + wersja autentykowana
  • Parametry URL: organizationId — UUID organizacji
  • Response (sukces): 200 OK — struktura jak w index, pojedynczy obiekt
  • Response (błędy):
    • 404 Not Found — organizacja nie istnieje (NotFoundModelException)

POST /api/organizations

  • Opis: Utworzenie nowej organizacji
  • Autoryzacja: Rola ADMIN (middleware) + Policy create (zawsze false — blokowane przez policy, dostęp tylko dla ADMIN przez before() w BasePolicy)
  • Body (Request): StoreOrganizationControllerRequest
    {
      "type": "required|string|in:municipality,school,...(22 wartości)",
      "nip": "required|digits:10|unique:organizations,nip|ValidCorrectNIPRule",
      "regon": "required|string|regex:/^[0-9]+$/|unique:organizations,regon|ValidCorrectRegonRule",
      "name": "required|string|max:500",
      "email": "required|email|max:255",
      "mobile_phone": "required|regex:/^([0-9\\s\\-\\+\\(\\)]{6,20})$/",
      "stationary_phone": "nullable|sometimes|regex:/^([0-9\\s\\-\\+\\(\\)]{6,20})$/",
      "street": "required|string|max:255",
      "street_number": "required|string|max:50",
      "postal_code": "required|regex:/^[0-9]{2}-[0-9]{3}$/",
      "city": "required|string|max:255",
      "commune": "required|string|max:255",
      "district": "required|string|max:255",
      "voivodeship": "required|string|max:255",
      "teryt_data": "required|array",
      "teryt_data.*": "string|max:255"
    }
    
  • Response (sukces): 200 OK
    {
      "message": "The Organization has been successfully added",
      "status": 200,
      "error": false
    }
    
  • Reguły walidacji dodatkowe:
    • ValidCorrectNIPRule — walidacja sumy kontrolnej NIP
    • ValidCorrectRegonRule — walidacja sumy kontrolnej REGON
    • NIP i REGON muszą być unikalne w tabeli organizations

PATCH /api/organizations/{organizationId}

  • Opis: Aktualizacja danych organizacji
  • Autoryzacja: Rola ADMIN lub SUPERVISOR (middleware) + Policy update (supervisor musi należeć do organizacji)
  • Parametry URL: organizationId — UUID organizacji
  • Body (Request): UpdateOrganizationControllerRequest
    {
      "type": "sometimes|nullable|string|in:...(22 wartości)",
      "nip": "sometimes|nullable|digits:10|unique:organizations,nip,{organizationId}|ValidCorrectNIPRule",
      "regon": "sometimes|nullable|string|regex:/^[0-9]+$/|unique:organizations,regon,{organizationId}|ValidCorrectRegonRule",
      "name": "sometimes|nullable|string|max:500",
      "email": "sometimes|nullable|email|max:255",
      "mobile_phone": "sometimes|nullable|regex:/^([0-9\\s\\-\\+\\(\\)]{6,20})$/",
      "stationary_phone": "sometimes|nullable|regex:/^([0-9\\s\\-\\+\\(\\)]{6,20})$/",
      "...reszta pól adresowych z 'sometimes|nullable'..."
    }
    
  • Response (sukces): 200 OK
    {
      "message": "The Organization has been successfully updated",
      "status": 200,
      "error": false
    }
    
  • Reguły biznesowe: Reguła unique dla NIP/REGON ignoruje aktualny rekord (parametr ignore($this->route('organizationId')))

PATCH /api/organizations/{organizationId}/change-state

  • Opis: Przełączenie stanu aktywności organizacji (is_active)
  • Autoryzacja: Rola ADMIN (middleware) + Policy update
  • Parametry URL: organizationId — UUID organizacji
  • Body: Brak — stan jest przełączany automatycznie (toggle)
  • Response (sukces): 200 OK
  • Efekty uboczne: Kaskadowa zmiana stanu na powiązanych obiektach (zobacz sekcję Kaskadowa dezaktywacja)

3.2 Pracownicy organizacji

GET /api/organization-employees

  • Opis: Lista pracowników organizacji z paginacją i filtrowaniem
  • Autoryzacja: Publiczny (dostępny bez logowania) + wersja autentykowana
  • Query Parameters:
    • filter[id] — exact po UUID
    • filter[organization_id] — exact po organizacji
    • filter[user_id] — exact po użytkowniku
    • filter[organization.type] — exact po typie organizacji
    • filter[user.name] — partial po nazwie użytkownika
    • filter[organization.nip] — partial po NIP
    • filter[organization.regon] — partial po REGON
    • filter[organization.name] — partial po nazwie organizacji
    • Standardowe parametry paginacji
  • Response (sukces): 200 OK
    {
      "data": [
        {
          "id": "uuid",
          "organization_id": "uuid",
          "user_id": "uuid",
          "can_be_edit": true,
          "created_at": "2026-01-15 10:00:00",
          "updated_at": "2026-01-15 10:00:00",
          "can": {},
          "organization": { "...dane organizacji..." },
          "user": { "...dane użytkownika..." }
        }
      ]
    }
    
  • Relacje eager-loaded: organization, user
  • Uwaga: Dla niezalogowanych pola can nie jest zwracane

POST /api/organization-employees

  • Opis: Przypisanie pracownika do organizacji
  • Autoryzacja: Rola ADMIN (middleware) + Policy create (zawsze false — dostęp przez BasePolicy::before())
  • Body (Request): StoreOrganizationEmployeeControllerRequest
    {
      "organization_id": "required|uuid|exists:organizations,id",
      "user_id": "required|uuid|exists:users,id|UniqueOrganizationEmployeeRule"
    }
    
  • Response (sukces): 200 OK
    {
      "message": "The Organization Employee has been successfully added",
      "status": 200,
      "error": false
    }
    
  • Reguły walidacji:
    • UniqueOrganizationEmployeeRule — sprawdza czy użytkownik nie jest już pracownikiem tej organizacji
    • organization_id musi istnieć w tabeli organizations
    • user_id musi istnieć w tabeli users
  • Efekty uboczne: Powiadomienie email do nowego pracownika (OrganizationEmployeeCreatedNotification)

DELETE /api/organization-employees/{organizationEmployeeId}

  • Opis: Usunięcie pracownika z organizacji
  • Autoryzacja: Rola ADMIN (middleware) + Policy update (zawsze false — dostęp przez BasePolicy::before())
  • Parametry URL: organizationEmployeeId — UUID powiązania pracownik-organizacja
  • Response (sukces): 200 OK
    {
      "message": "The Organization Employee was deleted successfully",
      "status": 200,
      "error": false
    }
    
  • Response (błędy):
    • 404 Not Found — powiązanie nie istnieje (NotFoundModelException)
  • Efekty uboczne:
    1. Usunięcie uprawnień managera obiektów dla tego pracownika w tej organizacji (DeleteEmployeeItemManagers)
    2. Powiadomienie email do pracownika (OrganizationEmployeeDeletedNotification)

4. Logika biznesowa

4.1 Główne procesy

Tworzenie organizacji

flowchart LR
    A[Request POST] --> B[Walidacja\nStoreOrganizationControllerRequest]
    B --> C[Sprawdzenie roli ADMIN]
    C --> D[Policy: create]
    D --> E[CreateOrganization\n__invoke]
    E --> F[Organization::create]
    F --> G[Odpowiedź 200]
  1. Request przechodzi walidację (typ, NIP z sumą kontrolną, REGON z sumą kontrolną, dane adresowe)
  2. Middleware sprawdza rolę ADMIN
  3. Policy create (w BasePolicy::before() admin ma pełen dostęp)
  4. Klasa CreateOrganization wywołuje Organization::create($validated)
  5. Zwraca komunikat sukcesu

Przypisanie pracownika

flowchart TD
    A[Request POST] --> B[Walidacja]
    B --> C{Czy user już\njest pracownikiem?}
    C -->|Tak| D[Błąd 422:\nUniqueOrganizationEmployeeRule]
    C -->|Nie| E[CreateOrganizationEmployee]
    E --> F[OrganizationEmployee::create]
    F --> G[Observer: created]
    G --> H[SimpleSendNotificationJob]
    H --> I[Email:\nPrzypisanie do organizacji]

Usunięcie pracownika

flowchart TD
    A[Request DELETE] --> B[Znalezienie rekordu]
    B --> C{Istnieje?}
    C -->|Nie| D[404 NotFoundModelException]
    C -->|Tak| E[delete]
    E --> F[Observer: deleting]
    F --> G[DeleteEmployeeItemManagers\nUsunięcie uprawnień managera]
    E --> H[Observer: deleted]
    H --> I[SimpleSendNotificationJob]
    I --> J[Email:\nUsunięcie z organizacji]

4.2 Reguły biznesowe

  1. NIP musi być unikalny — każda organizacja ma inny NIP, walidowany sumą kontrolną (ValidCorrectNIPRule)
  2. REGON musi być unikalny — walidowany sumą kontrolną (ValidCorrectRegonRule)
  3. Pracownik nie może być przypisany dwukrotnie — tabela ma constraint UNIQUE(organization_id, user_id) + walidacja UniqueOrganizationEmployeeRule
  4. Dezaktywacja kaskaduje — zmiana is_active propaguje się na wszystkie powiązane obiekty (przez organization_id)
  5. Usunięcie pracownika czyści uprawnienia manageraItemManager powiązane z pracownikiem i organizacją są kasowane
  6. Supervisor może edytować tylko swoją organizację — Policy update wymaga przynależności do organizacji
  7. Usunięcie organizacji kaskaduje na pracownikówON DELETE CASCADE na foreign key
  8. Usunięcie użytkownika kaskaduje na powiązaniaON DELETE CASCADE na foreign key user_id
  9. Asynchroniczna kaskada dla dużych organizacji — jeśli >50 obiektów, zmiana stanu realizowana jest przez job na kolejce

4.3 Walidacje

Tworzenie organizacji (StoreOrganizationControllerRequest)

Pole Reguły
type required\|string\|in:{22 typów z OrganizationEnumService}
nip required\|digits:10\|unique:organizations,nip\|ValidCorrectNIPRule
regon required\|string\|regex:/^[0-9]+$/\|unique:organizations,regon\|ValidCorrectRegonRule
name required\|string\|max:500
email required\|email\|max:255
mobile_phone required\|regex:/^([0-9\s\-\+\(\)]{6,20})$/
stationary_phone nullable\|sometimes\|regex:/^([0-9\s\-\+\(\)]{6,20})$/
street required\|string\|max:255
street_number required\|string\|max:50
postal_code required\|regex:/^[0-9]{2}-[0-9]{3}$/
city required\|string\|max:255
commune required\|string\|max:255
district required\|string\|max:255
voivodeship required\|string\|max:255
teryt_data required\|array
teryt_data.* string\|max:255

Aktualizacja organizacji (UpdateOrganizationControllerRequest)

Identyczne pola jak przy tworzeniu, ale z sometimes|nullable zamiast required. Reguły unique dla NIP/REGON ignorują bieżący rekord (ignore($this->route('organizationId'))).

Przypisanie pracownika (StoreOrganizationEmployeeControllerRequest)

Pole Reguły
organization_id required\|uuid\|exists:organizations,id
user_id required\|uuid\|exists:users,id\|UniqueOrganizationEmployeeRule

5. Autoryzacja i uprawnienia

Middleware (w konstruktorze kontrolera)

Akcja Wymagana rola
store (tworzenie organizacji) ADMIN
changeState (zmiana stanu) ADMIN
update (edycja organizacji) ADMIN lub SUPERVISOR
store (dodanie pracownika) ADMIN
delete (usunięcie pracownika) ADMIN

Policy OrganizationPolicy

Metoda Logika
view Użytkownik musi należeć do organizacji (belongsToOrganization)
create Zawsze false (dostęp przez BasePolicy::before() dla ADMIN)
update Supervisor musi należeć do organizacji; inaczej false

Policy OrganizationEmployeePolicy

Metoda Logika
view Użytkownik musi należeć do organizacji pracownika
create Zawsze false (dostęp przez BasePolicy::before() dla ADMIN)
update Zawsze false (dostęp przez BasePolicy::before() dla ADMIN)

Macierz uprawnień

Akcja Administrator Supervisor Employee Niezalogowany
Lista organizacji (ograniczona)
Szczegóły organizacji (ograniczona)
Tworzenie organizacji
Edycja organizacji (tylko swojej)
Zmiana stanu (aktywacja/dezaktywacja)
Lista pracowników (ograniczona)
Dodanie pracownika
Usunięcie pracownika

6. Eventy i efekty uboczne

Łańcuch observerów

flowchart TD
    subgraph "Organization updated"
        A1[OrganizationObserver::updated] --> A2[UpdatedOrganizationObserver]
        A2 --> A3[UpdatedIsActiveOrganizationObserver]
        A3 --> A4{is_active changed?}
        A4 -->|Tak| A5{total_items > 50?}
        A5 -->|Tak| A6[ChangeStateAllObjectModelsJob\nkolejka: domain]
        A5 -->|Nie| A7[changeStateAllObjectModels\nsynchronicznie]
        A4 -->|Nie| A8[Brak akcji]
    end

    subgraph "OrganizationEmployee created"
        B1[OrganizationEmployeeObserver::created] --> B2[SimpleSendNotificationJob]
        B2 --> B3[OrganizationEmployeeCreatedNotification\nemail]
    end

    subgraph "OrganizationEmployee deleting"
        C1[OrganizationEmployeeObserver::deleting] --> C2[DeletingOrganizationEmployeeObserver]
        C2 --> C3[DeleteEmployeeItemManagers]
        C3 --> C4[Usunięcie ItemManager\ndla user_id + organization_id]
    end

    subgraph "OrganizationEmployee deleted"
        D1[OrganizationEmployeeObserver::deleted] --> D2[SimpleSendNotificationJob]
        D2 --> D3[OrganizationEmployeeDeletedNotification\nemail]
    end

Klasy Action/Other

Klasa Wyzwalacz Działanie
CreateOrganization StoreOrganizationController Organization::create($validated)
CreateOrganizationEmployee StoreOrganizationEmployeeController OrganizationEmployee::create($validated)
ExistOrganizationEmployee Wewnętrzne użycie Sprawdza WHERE organization_id AND user_id EXISTS
DeleteEmployeeItemManagers Observer deleting Usuwa ItemManager dla pracownika w danej organizacji
ApplyOrganizationAggregatesOrm Index/Show kontrolery Dodaje withCount, withAvg, withExists do query
UpdatedOrganizationObserver Observer updated Deleguje do UpdatedIsActiveOrganizationObserver
UpdatedIsActiveOrganizationObserver j.w. Kaskaduje zmianę is_active (sync lub async)
DeletingOrganizationEmployeeObserver Observer deleting Deleguje do DeleteEmployeeItemManagers

7. Powiadomienia

Powiadomienie Klasa Kanał Kiedy Odbiorca Treść
Przypisanie do organizacji OrganizationEmployeeCreatedNotification Email Po utworzeniu OrganizationEmployee Email pracownika Informacja o przypisaniu do organizacji w roli pracownika
Usunięcie z organizacji OrganizationEmployeeDeletedNotification Email Po usunięciu OrganizationEmployee Email pracownika Informacja o cofnięciu dostępu do organizacji

Obie notyfikacje są wysyłane asynchronicznie przez SimpleSendNotificationJob z kolejki powiadomień.


8. Powiązania z innymi domenami

Domena Powiązanie Typ relacji
Zasoby (Item) Organizacja posiada obiekty (items) HasMany
Aktywności (Activity) Organizacja prowadzi zajęcia (relActivities) HasMany
Użytkownicy (User) Pracownicy przypisani przez OrganizationEmployee Przez model pośredni
Rezerwacje (Reservation) Rezerwacje dotyczą obiektów organizacji (reservations) HasMany
Opinie (Review) Opinie o organizacji (reviews) HasMany
Reklamacje (Complaint) Reklamacje do organizacji (complaints) HasMany
Harmonogramy (Schedule) Harmonogramy obiektów organizacji (schedules) HasMany
Item Manager Usuwane przy usunięciu pracownika z organizacji Kasowanie powiązane

9. Konfiguracja

Domena Organization nie posiada dedykowanych parametrów konfiguracyjnych w domains/configuration.md. Kluczowe stałe:

  • Próg asynchronicznej kaskady: 50 obiektów (UpdatedIsActiveOrganizationObserver — jeśli organizacja ma >50 items, zmiana stanu trafia na kolejkę domain)
  • Kolejka dla kaskady: SupportEnumService::ON_QUEUE_DOMAIN
  • Typy organizacji: 22 wartości zdefiniowane w OrganizationEnumService (nie konfigurowane — hardcoded w enum)

10. Znane ograniczenia i TODO

  1. Brak softDelete — organizacje są usuwane permanentnie (hard delete z kaskadą). W praktyce dezaktywacja (is_active = false) pełni rolę soft delete.
  2. Policy create/update zawsze false — autoryzacja opiera się wyłącznie na BasePolicy::before() i middleware ról, co może powodować niespójności
  3. Brak walidacji unikalności email — w przeciwieństwie do NIP i REGON, email organizacji nie ma constraintu unikalności
  4. 22 typów, nie 21 — enum OrganizationEnumService zawiera 22 wartości (dodano event_venue)

Typy organizacji

System obsługuje 22 typów organizacji:

Typ Kod Opis
Gmina/Miasto municipality Urząd gminy lub miasta
Szkoła school Szkoły podstawowe i średnie
Dom Kultury cultural_center Ośrodki kultury
Ośrodek Sportowy sports_center Centra sportowe
Biblioteka library Biblioteki publiczne
Przedszkole kindergarten Placówki przedszkolne
Firma company Podmioty prywatne
Inne other Organizacje niesklasyfikowane
OSiR/MOSiR osir Ośrodki sportu i rekreacji
Zoo zoo Ogrody zoologiczne
Muzeum museum Muzea i galerie
Świetlica community_center Świetlice środowiskowe
Przychodnia health_center Placówki zdrowotne
Straż Pożarna fire_department Jednostki OSP/PSP
Wodociągi water_center Zakłady wodociągowe
Park park Parki miejskie
Klub Seniora senior_center Kluby dla seniorów
Młodzieżowy Dom youth_center Domy młodzieży
Jednostka Administracyjna admin_unit Inne jednostki urzędowe
Transport transport_unit Przedsiębiorstwa transportowe
Klub Sportowy sport_club Kluby i stowarzyszenia
Obiekt Eventowy event_venue Sale eventowe

Kaskadowa dezaktywacja

Gdy organizacja zostaje dezaktywowana (is_active: true → false):

flowchart TD
    A[Dezaktywacja organizacji] --> B[OrganizationObserver::updated]
    B --> C[UpdatedIsActiveOrganizationObserver]
    C --> D{is_active zmienione?}
    D -->|Nie| E[Koniec]
    D -->|Tak| F{Więcej niż 50 obiektów?}
    F -->|Tak| G[ChangeStateAllObjectModelsJob\nkolejka: domain]
    F -->|Nie| H[changeStateAllObjectModels\nsynchronicznie]
    G --> I[Dezaktywacja obiektów]
    H --> I
    I --> J[Dezaktywacja zajęć]
    J --> K[Dezaktywacja harmonogramów]

Wpływ na rezerwacje

Dezaktywacja organizacji nie anuluje istniejących rezerwacji. Blokuje jedynie możliwość tworzenia nowych.

Reaktywacja

Ponowna aktywacja organizacji (is_active: false → true) przywraca stan aktywności powiązanych obiektów. Parametr !$new w kodzie zapewnia symetryczność operacji.


Pracownicy organizacji

Przypisanie pracownika

sequenceDiagram
    participant Admin
    participant API
    participant Walidacja
    participant Observer
    participant Email

    Admin->>API: POST /organization-employees
    API->>Walidacja: StoreOrganizationEmployeeControllerRequest
    Walidacja->>Walidacja: UUID exists? Unique?
    Walidacja-->>API: OK
    API->>API: OrganizationEmployee::create()
    API->>Observer: created event
    Observer->>Email: SimpleSendNotificationJob
    Email->>Email: "Przypisanie do organizacji"
    API-->>Admin: 200 OK

Usunięcie pracownika

sequenceDiagram
    participant Admin
    participant API
    participant Observer
    participant ItemManager
    participant Email

    Admin->>API: DELETE /organization-employees/{id}
    API->>API: OrganizationEmployee::find(id)
    API->>Observer: deleting event
    Observer->>ItemManager: DeleteEmployeeItemManagers
    ItemManager->>ItemManager: Usunięcie uprawnień managera
    API->>API: delete()
    API->>Observer: deleted event
    Observer->>Email: SimpleSendNotificationJob
    Email->>Email: "Usunięcie z organizacji"
    API-->>Admin: 200 OK

Statystyki organizacji

System automatycznie oblicza agregaty za pomocą ApplyOrganizationAggregatesOrm:

Metryka Źródło ORM Opis
items_count withCount('items') Liczba obiektów
rel_activities_count withCount('relActivities') Liczba zajęć
organization_employees_count withCount('organizationEmployees') Liczba pracowników
reviews_count withCount(['reviews' => is_active=true]) Liczba aktywnych opinii
reviews_avg_rating withAvg(['reviews' => is_active=true], 'rating') Średnia ocen aktywnych opinii
reservations_exists withExists('reservations') Czy ma rezerwacje
reviews_exists withExists('reviews') Czy ma opinie
complaints_exists withExists('complaints') Czy ma reklamacje

Wartość biznesowa

Argumenty dla decydentów

  1. Podział administracyjny — każda jednostka ma swoją przestrzeń w systemie
  2. Delegacja uprawnień — supervisor szkoły zarządza tylko swoimi obiektami
  3. Przejrzystość — wiadomo która jednostka za co odpowiada
  4. Skalowalność — łatwe dodawanie nowych jednostek (22 typów)
  5. Automatyzacja — kaskadowa zmiana stanu eliminuje ręczne wyłączanie obiektów

Historia zmian

Data Typ Opis
26 lut 2026 Dokumentacja Pełna aktualizacja dokumentacji domeny — dodano sekcje: endpointy API z walidacjami, logika biznesowa, autoryzacja i policy, eventy i observery, struktura bazy danych, statystyki ORM, znane ograniczenia
11 lut 2026 Optymalizacja Eliminacja N+1 query w GET /api/organizations — Utworzono ApplyOrganizationAggregatesOrm z withCount, withAvg, withExists. Zmodyfikowano accessory w modelu z array_key_exists() fallback