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
UserzOrganization
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 organizacjiFK organization_id → organizations.id ON DELETE CASCADEFK 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 UUIDfilter[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_useicannie 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) + Policycreate(zawszefalse— blokowane przez policy, dostęp tylko dla ADMIN przezbefore()wBasePolicy) - 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 - Reguły walidacji dodatkowe:
ValidCorrectNIPRule— walidacja sumy kontrolnej NIPValidCorrectRegonRule— walidacja sumy kontrolnej REGON- NIP i REGON muszą być unikalne w tabeli
organizations
PATCH /api/organizations/{organizationId}¶
- Opis: Aktualizacja danych organizacji
- Autoryzacja: Rola
ADMINlubSUPERVISOR(middleware) + Policyupdate(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 - Reguły biznesowe: Reguła
uniquedla NIP/REGON ignoruje aktualny rekord (parametrignore($this->route('organizationId')))
PATCH /api/organizations/{organizationId}/change-state¶
- Opis: Przełączenie stanu aktywności organizacji (
is_active) - Autoryzacja: Rola
ADMIN(middleware) + Policyupdate - 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 UUIDfilter[organization_id]— exact po organizacjifilter[user_id]— exact po użytkownikufilter[organization.type]— exact po typie organizacjifilter[user.name]— partial po nazwie użytkownikafilter[organization.nip]— partial po NIPfilter[organization.regon]— partial po REGONfilter[organization.name]— partial po nazwie organizacji- Standardowe parametry paginacji
- Response (sukces):
200 OK - Relacje eager-loaded:
organization,user - Uwaga: Dla niezalogowanych pola
cannie jest zwracane
POST /api/organization-employees¶
- Opis: Przypisanie pracownika do organizacji
- Autoryzacja: Rola
ADMIN(middleware) + Policycreate(zawszefalse— dostęp przezBasePolicy::before()) - Body (Request):
StoreOrganizationEmployeeControllerRequest - Response (sukces):
200 OK - Reguły walidacji:
UniqueOrganizationEmployeeRule— sprawdza czy użytkownik nie jest już pracownikiem tej organizacjiorganization_idmusi istnieć w tabeliorganizationsuser_idmusi istnieć w tabeliusers
- Efekty uboczne: Powiadomienie email do nowego pracownika (
OrganizationEmployeeCreatedNotification)
DELETE /api/organization-employees/{organizationEmployeeId}¶
- Opis: Usunięcie pracownika z organizacji
- Autoryzacja: Rola
ADMIN(middleware) + Policyupdate(zawszefalse— dostęp przezBasePolicy::before()) - Parametry URL:
organizationEmployeeId— UUID powiązania pracownik-organizacja - Response (sukces):
200 OK - Response (błędy):
404 Not Found— powiązanie nie istnieje (NotFoundModelException)
- Efekty uboczne:
- Usunięcie uprawnień managera obiektów dla tego pracownika w tej organizacji (
DeleteEmployeeItemManagers) - Powiadomienie email do pracownika (
OrganizationEmployeeDeletedNotification)
- Usunięcie uprawnień managera obiektów dla tego pracownika w tej organizacji (
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] - Request przechodzi walidację (typ, NIP z sumą kontrolną, REGON z sumą kontrolną, dane adresowe)
- Middleware sprawdza rolę
ADMIN - Policy
create(wBasePolicy::before()admin ma pełen dostęp) - Klasa
CreateOrganizationwywołujeOrganization::create($validated) - 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¶
- NIP musi być unikalny — każda organizacja ma inny NIP, walidowany sumą kontrolną (
ValidCorrectNIPRule) - REGON musi być unikalny — walidowany sumą kontrolną (
ValidCorrectRegonRule) - Pracownik nie może być przypisany dwukrotnie — tabela ma constraint
UNIQUE(organization_id, user_id)+ walidacjaUniqueOrganizationEmployeeRule - Dezaktywacja kaskaduje — zmiana
is_activepropaguje się na wszystkie powiązane obiekty (przezorganization_id) - Usunięcie pracownika czyści uprawnienia managera —
ItemManagerpowiązane z pracownikiem i organizacją są kasowane - Supervisor może edytować tylko swoją organizację — Policy
updatewymaga przynależności do organizacji - Usunięcie organizacji kaskaduje na pracowników —
ON DELETE CASCADEna foreign key - Usunięcie użytkownika kaskaduje na powiązania —
ON DELETE CASCADEna foreign keyuser_id - 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 | Po utworzeniu OrganizationEmployee | Email pracownika | Informacja o przypisaniu do organizacji w roli pracownika | |
| Usunięcie z organizacji | OrganizationEmployeeDeletedNotification | 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¶
- Brak softDelete — organizacje są usuwane permanentnie (hard delete z kaskadą). W praktyce dezaktywacja (
is_active = false) pełni rolę soft delete. - Policy create/update zawsze false — autoryzacja opiera się wyłącznie na
BasePolicy::before()i middleware ról, co może powodować niespójności - Brak walidacji unikalności email — w przeciwieństwie do NIP i REGON, email organizacji nie ma constraintu unikalności
- 22 typów, nie 21 — enum
OrganizationEnumServicezawiera 22 wartości (dodanoevent_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
- Podział administracyjny — każda jednostka ma swoją przestrzeń w systemie
- Delegacja uprawnień — supervisor szkoły zarządza tylko swoimi obiektami
- Przejrzystość — wiadomo która jednostka za co odpowiada
- Skalowalność — łatwe dodawanie nowych jednostek (22 typów)
- 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 |