Przejdź do treści

Zasoby / Obiekty (Item)

Fundament systemu rezerwacji — wszystko, co można zarezerwować: sale, boiska, sprzęt, pojazdy.


Co to jest?

Domena Zasobów (Item) zarządza obiektami do wynajmu. To jak katalog wszystkich sal i obiektów w gminie — z pełną informacją o lokalizacji, wyposażeniu, dostępności i cenach.

Każdy obiekt może być: - Salą konferencyjną w urzędzie - Halą sportową w szkole - Boiskiem piłkarskim - Kortem tenisowym - Sprzętem do wypożyczenia (namioty, stoły, krzesła)


Jakie problemy rozwiązuje?

Korzyści dla JST

  • Jeden katalog wszystkich obiektów — mieszkańcy widzą pełną ofertę gminy w jednym miejscu
  • Aktualna dostępność — kalendarz na żywo, bez konieczności dzwonienia
  • Blokady i konserwacje — łatwe zarządzanie niedostępnością obiektów
  • Hierarchia obiektów — budynek → piętro → sala (dla dużych kompleksów)
  • Przypisani opiekunowie — każdy obiekt ma swojego managera

Struktura domeny

app/Domain/Item/
├── Action/
│   ├── Attr/                  # Atrybuty obliczane (9 klas)
│   │   ├── GetCurrentPriceAttr.php
│   │   ├── GetCurrentPriceGrossAttr.php
│   │   ├── GetCurrentPriceVatRateAttr.php
│   │   ├── GetCurrentPricingAttr.php
│   │   ├── GetIsParentAttr.php
│   │   ├── GetLowestPriceAttr.php
│   │   ├── GetLowestPriceGrossAttr.php
│   │   ├── GetLowestPriceVatRateAttr.php
│   │   └── GetLowestPricingAttr.php
│   ├── Controller/            # Single-action controllers (12 klas)
│   │   ├── IndexItemController.php
│   │   ├── ShowItemController.php
│   │   ├── StoreItemController.php
│   │   ├── UpdateItemController.php
│   │   ├── IndexItemBlockController.php
│   │   ├── ShowItemBlockController.php
│   │   ├── StoreItemBlockController.php
│   │   ├── StoreMultipleItemBlockController.php
│   │   ├── CheckItemBlockConflictsController.php
│   │   ├── IndexItemManagerController.php
│   │   ├── StoreItemManagerController.php
│   │   └── DeleteItemManagerController.php
│   ├── Other/                 # Logika biznesowa i obserwatory (24 klasy)
│   │   ├── ApplyItemAggregatesOrm.php
│   │   ├── CreateItem.php
│   │   ├── UpdateItem.php
│   │   ├── CreateItemBlock.php
│   │   ├── CreateItemBlockSchedule.php
│   │   ├── CreateItemManager.php
│   │   ├── CreatingItemObserver.php
│   │   ├── UpdatedItemObserver.php
│   │   ├── CreatingItemBlockObserver.php
│   │   ├── CreatedItemBlockObserver.php
│   │   ├── UpdatingItemBlockObserver.php
│   │   ├── UpdatedItemBlockObserver.php
│   │   ├── DeletingItemBlockObserver.php
│   │   ├── ExceptionChangeStateItem.php
│   │   ├── SetActiveInactiveChildItem.php
│   │   ├── DisableOverlappingItemBlocks.php
│   │   ├── ForceCancelReservationAndDisableItemBlock.php
│   │   ├── FindConflictingReservationsForItemBlock.php
│   │   ├── FindConflictingReservationsForPeriod.php
│   │   ├── CancelConflictingReservationsForItemBlock.php
│   │   ├── GetItemBlockConflictSummary.php
│   │   ├── BuildItemBlockCancellationReason.php
│   │   ├── HandleItemBlockDateChange.php
│   │   └── HandleItemBlockIsActiveChange.php
│   ├── Scope/                 # Query scopes (8 klas)
│   │   ├── ScopeSearchFunc.php
│   │   ├── ScopePriceFromFunc.php
│   │   ├── ScopePriceToFunc.php
│   │   ├── ScopeCapacityFromFunc.php
│   │   ├── ScopeCapacityToFunc.php
│   │   ├── ScopeAreaFromFunc.php
│   │   ├── ScopeAreaToFunc.php
│   │   └── ScopeExcludeSelfAndChildrenFunc.php
│   └── Sort/                  # Sortowania (1 klasa)
│       └── SortByLowestPrice.php
├── Controller/                # Kontrolery HTTP (3 klasy)
│   ├── ItemController.php
│   ├── ItemBlockController.php
│   └── ItemManagerController.php
├── Model/                     # Modele Eloquent (3 klasy)
│   ├── Item.php
│   ├── ItemBlock.php
│   └── ItemManager.php
├── Notification/              # Powiadomienia (2 klasy)
│   ├── ItemManagerCreatedNotification.php
│   └── ItemManagerDeletedNotification.php
├── Observer/                  # Obserwatory Eloquent (3 klasy)
│   ├── ItemObserver.php
│   ├── ItemBlockObserver.php
│   └── ItemManagerObserver.php
├── Request/                   # Walidacja żądań (10 klas + 6 reguł)
│   ├── IndexItemControllerRequest.php
│   ├── StoreItemControllerRequest.php
│   ├── UpdateItemControllerRequest.php
│   ├── ChangeCategoryItemControllerRequest.php
│   ├── IndexItemBlockControllerRequest.php
│   ├── StoreItemBlockControllerRequest.php
│   ├── StoreMultipleItemBlockControllerRequest.php
│   ├── CheckItemBlockConflictsRequest.php
│   ├── IndexItemManagerControllerRequest.php
│   ├── StoreItemManagerControllerRequest.php
│   └── Rule/
│       ├── AllowedParentItemRule.php
│       ├── AllowedItemManagerRule.php
│       ├── AssignLikeManagerRule.php
│       ├── UniqueItemManagerRule.php
│       ├── ItemBlockAvailabilityRule.php
│       └── ItemBlockAvailabilityUpdateRule.php
├── Resource/                  # Zasoby API (3 klasy)
│   ├── ItemResource.php
│   ├── ItemBlockResource.php
│   └── ItemManagerResource.php
└── Service/                   # Traity i enumy (3 pliki)
    ├── ItemEnumService.php
    ├── TraitItem.php
    └── TraitControllerItem.php

Kluczowe funkcjonalności

Dla mieszkańca

Funkcja Opis Status
Przeglądanie obiektów Lista wszystkich dostępnych obiektów z filtrami Dostępne
Szczegóły obiektu Opis, zdjęcia, wyposażenie, lokalizacja na mapie Dostępne
Sprawdzenie dostępności Kalendarz z wolnymi terminami Dostępne
Filtrowanie Po kategorii, lokalizacji, pojemności, cenie Dostępne
Galeria zdjęć Zdjęcia obiektu z podglądem Dostępne

Dla administratora

Funkcja Opis Status
Tworzenie obiektów Dodawanie nowych obiektów z pełnym opisem Dostępne
Edycja obiektów Zmiana opisu, cen, godzin działania Dostępne
Blokady czasowe Blokowanie terminów (konserwacja, imprezy, awarie) Dostępne
Przypisanie managerów Wyznaczanie opiekunów obiektów Dostępne
Aktywacja/Dezaktywacja Ukrywanie obiektów przed mieszkańcami Dostępne
Hierarchia obiektów Tworzenie struktury budynek → sale Dostępne
Zmiana kategorii Przenoszenie obiektu do innej kategorii Dostępne
Masowe blokady Tworzenie wielu blokad jednocześnie Dostępne
Sprawdzanie konfliktów Preview kolidujących rezerwacji przed blokadą Dostępne

Jak to działa?

Scenariusz 1: Dodanie nowego obiektu

flowchart LR
    A[Nowy obiekt] --> B[Dane podstawowe]
    B --> C[Lokalizacja TERYT]
    C --> D[Wyposażenie]
    D --> E[Godziny działania]
    E --> F[Cennik]
    F --> G[Zdjęcia]
    G --> H[Publikacja]
  1. Administrator tworzy nowy obiekt w panelu
  2. Podaje nazwę, opis i kategorię (np. "Sala konferencyjna", kategoria "Sale")
  3. Wprowadza adres z autouzupełnianiem TERYT (województwo → powiat → gmina → miejscowość)
  4. Zaznacza wyposażenie: WiFi, parking, klimatyzacja, toalety, kuchnia...
  5. Ustawia godziny działania (np. 8:00-22:00)
  6. Dodaje cennik (np. 50 zł/godz. netto) — wymagany gdy is_paid=true
  7. Wgrywa zdjęcia obiektu
  8. Publikuje obiekt — od teraz widoczny dla mieszkańców

Automatyczny slug

Przy tworzeniu obiektu system automatycznie generuje slug z nazwy — zamienia polskie znaki, spacje na podkreślenia i dodaje losowy 3-znakowy suffix (np. sala_konferencyjna_x4k).

Scenariusz 2: Blokada na czas konserwacji

flowchart TD
    A[Tworzenie blokady] --> B{Koliduje z rezerwacjami?}
    B -->|Nie| C[Blokada aktywna]
    B -->|Tak| D[Automatyczna anulacja slotów]
    D --> E[Obliczenie zwrotów]
    E --> F[Wysłanie powiadomień]
    F --> G[Auto-potwierdzenie zwrotów]
    G --> C
    C --> H[Dezaktywacja nakładających się blokad]
  1. Administrator tworzy blokadę dla obiektu
  2. Wybiera typ: "Konserwacja"
  3. Podaje zakres dat: 15-17 stycznia
  4. Dodaje opis: "Wymiana oświetlenia"
  5. System automatycznie anuluje kolidujące sloty rezerwacji
  6. Inicjuje zwroty za anulowane sloty (auto-potwierdza oczekujące zwroty ręczne)
  7. Wysyła powiadomienie do klientów (częściowe lub pełne anulowanie)
  8. Dezaktywuje nakładające się istniejące blokady na ten sam obiekt

Scenariusz 3: Mieszkaniec szuka sali na imprezę

  1. Mieszkaniec wchodzi na listę obiektów
  2. Filtruje: kategoria "Sale", pojemność od 50 osób, cena do 100 zł/godz.
  3. System pokazuje pasujące obiekty posortowane wg ceny
  4. Mieszkaniec klika w wybrany obiekt
  5. Widzi: zdjęcia, opis, mapę, wyposażenie, kalendarz dostępności
  6. Klika "Rezerwuj" i przechodzi do procesu rezerwacji

Modele danych

Item (Obiekt)

Model: Domain\Item\Model\Item Tabela: items UUID: Tak (HasUuids) Observer: ItemObserver (creating, updated) MediaLibrary: Tak (HasMedia, InteractsWithMedia) Activity Log: Tak (dziedziczy z CustomModel) Appends: avg_reviews, total_reviews

Pole Typ Opis
id uuid Identyfikator
organization_id uuid (FK) Organizacja właściciel
parent_id uuid (FK, nullable) Obiekt nadrzędny (hierarchia)
category_id uuid (FK) Kategoria obiektu
name string(255) Nazwa obiektu
slug string Slug (auto-generowany)
desc string(5000) Opis
is_active boolean Czy aktywny
is_public boolean Czy publicznie widoczny
street string(255) Ulica
street_number string(50) Numer budynku
postal_code string Kod pocztowy (format XX-XXX)
city string(255) Miejscowość
commune string(255) Gmina
district string(255) Powiat
voivodeship string(255) Województwo
teryt_data json Dane TERYT
lat decimal(8) Szerokość geograficzna
lng decimal(8) Długość geograficzna
capacity integer Pojemność (osoby)
area float Powierzchnia (m²)
is_paid boolean Czy płatny
reservation_start_time time (TimeCast) Godzina otwarcia
reservation_end_time time (TimeCast) Godzina zamknięcia
number_of_floors integer Liczba pięter
are_there_stairs boolean Czy są schody
is_there_an_elevator boolean Czy jest winda
are_there_facilities_for_disabled_people boolean Udogodnienia dla niepełnosprawnych
has_parking boolean Parking
has_wifi boolean WiFi
has_air_conditioning boolean Klimatyzacja
is_pet_friendly boolean Zwierzęta dozwolone
is_kid_friendly boolean Przyjazny dzieciom
is_smoking_allowed boolean Palenie dozwolone
has_smoking_area boolean Strefa dla palących
has_restrooms boolean Toalety
has_kitchen boolean Kuchnia
has_garden boolean Ogród
reservation_deadline_hours integer Min. godzin wyprzedzenia rezerwacji
cancellation_deadline_hours integer Min. godzin wyprzedzenia anulowania
cancellation_policy_description string(1000) Opis polityki anulowania
meta_title string(255) Tytuł SEO
meta_description string(5000) Opis SEO
tags array (json) Tagi
statute_src string Ścieżka do regulaminu (PDF)
is_public_price_list boolean Czy cennik publiczny
can_pay_online boolean Czy dostępna płatność online (Przelewy24)
can_full_day_reservation boolean Czy rezerwacja całodniowa
prices string Opis cenowy
color string Kolor (hex, nie może być #000/#000000)

Relacje:

Relacja Typ Model docelowy
organization BelongsTo Organization
category BelongsTo Category
parent BelongsTo (self) Item
childrens HasMany (self) Item
reservations MorphMany Reservation
pricings MorphMany Pricing
reviews MorphMany Review
notes MorphMany Note
schedules HasMany Schedule
itemManagers HasMany ItemManager
media MorphMany (Spatie) Media

Atrybuty obliczane (accessory z wzorcem array_key_exists fallback):

Atrybut Typ Opis
avg_reviews float Średnia ocen (tylko aktywne recenzje)
total_reviews int Liczba recenzji
thumbnail string/null URL miniatury (Spatie MediaLibrary)
current_pricing object/null Aktualny cennik (aktywny, bez client_id)
current_price float Aktualna cena netto
current_price_gross float Aktualna cena brutto (netto + VAT)
current_price_vat_rate string/null Stawka VAT aktualnego cennika
lowest_pricing object/null Najniższy cennik z ostatnich 31 dni
lowest_price float Najniższa cena netto (31 dni)
lowest_price_gross float Najniższa cena brutto (31 dni)
lowest_price_vat_rate string/null Stawka VAT najniższego cennika
is_parent bool Czy ma obiekty podrzędne
is_in_use bool Czy ma rezerwacje/recenzje/harmonogramy

Najniższa cena — 31 dni

Atrybut lowest_pricing zwraca najniższy cennik z ostatnich 31 dni (created_at >= now()-31 dni). Jest to zgodne z dyrektywą Omnibus o prezentowaniu najniższej ceny z ostatnich 30 dni.

Konwersje mediów (Spatie MediaLibrary):

Konwersja Format Wymiary Tryb
preview webp 300x300 Contain
web webp 1200x1200 Max

ItemBlock (Blokada obiektu)

Model: Domain\Item\Model\ItemBlock Tabela: item_blocks UUID: Tak (HasUuids) Observer: ItemBlockObserver (creating, created, updating, updated, deleting) Activity Log: Tak (dziedziczy z CustomModel)

Pole Typ Opis
id uuid Identyfikator
item_id uuid (FK) Obiekt blokowany
date_time_from datetime Początek blokady
date_time_to datetime Koniec blokady
created_by uuid (FK) Użytkownik tworzący (auto z auth)
reason_type string Typ powodu (enum)
reason_details string(5000) Szczegóły powodu
is_active boolean Czy blokada aktywna (domyślnie true)
cancel_by uuid (FK, nullable) Użytkownik dezaktywujący
cancel_at datetime (nullable) Data dezaktywacji

Relacje:

Relacja Typ Model docelowy
item BelongsTo Item
creator BelongsTo User
canceler BelongsTo User
schedule MorphOne Schedule

ItemManager (Zarządca obiektu)

Model: Domain\Item\Model\ItemManager Tabela: item_managers UUID: Tak (HasUuids) Observer: ItemManagerObserver (created, deleted) Activity Log: Tak (dziedziczy z CustomModel)

Pole Typ Opis
id uuid Identyfikator
item_id uuid (FK) Obiekt zarządzany
user_id uuid (FK) Użytkownik-zarządca

Relacje:

Relacja Typ Model docelowy
item BelongsTo Item
user BelongsTo User

Endpointy API

Obiekty (Items)

Metoda URL Akcja Rola Auth
GET /api/items Lista obiektów Brak Auth + NoAuth
GET /api/items/{itemId} Szczegóły obiektu Brak Auth + NoAuth
POST /api/items Tworzenie obiektu ADMIN, SUPERVISOR, EMPLOYEE Auth
PATCH /api/items/{itemId} Edycja obiektu ADMIN, SUPERVISOR, EMPLOYEE Auth
PATCH /api/items/{itemId}/change-state Zmiana stanu (aktywny/nieaktywny) ADMIN, SUPERVISOR, EMPLOYEE Auth
PATCH /api/items/{itemId}/change-category Zmiana kategorii ADMIN, SUPERVISOR, EMPLOYEE Auth

Blokady obiektów (ItemBlocks)

Metoda URL Akcja Rola Auth
GET /api/item-blocks Lista blokad Brak Auth
GET /api/item-blocks/{itemBlockId} Szczegóły blokady ADMIN, SUPERVISOR, EMPLOYEE Auth
POST /api/item-blocks Tworzenie blokady ADMIN, SUPERVISOR, EMPLOYEE Auth
PATCH /api/item-blocks/{itemBlockId}/change-state Zmiana stanu blokady ADMIN, SUPERVISOR, EMPLOYEE Auth
POST /api/store-multiple-item-blocks Masowe tworzenie blokad ADMIN, SUPERVISOR, EMPLOYEE Auth
POST /api/item-blocks-check-conflicts Sprawdzenie konfliktów ADMIN, SUPERVISOR, EMPLOYEE Auth

Zarządcy obiektów (ItemManagers)

Metoda URL Akcja Rola Auth
GET /api/item-managers Lista zarządców Brak Auth + NoAuth
POST /api/item-managers Przypisanie zarządcy ADMIN, SUPERVISOR Auth
DELETE /api/item-managers/{itemManagerId} Usunięcie zarządcy ADMIN, SUPERVISOR Auth

EMPLOYEE nie zarządza zarządcami

Rola EMPLOYEE nie ma uprawnień do dodawania i usuwania zarządców obiektów — tylko ADMIN i SUPERVISOR.


Autoryzacja i uprawnienia (Policies)

System autoryzacji opiera się na trzech policy: ItemPolicy, ItemBlockPolicy i ItemManagerPolicy. Wszystkie rozszerzają BasePolicy, która dostarcza metody helper: belongsToOrganization(), isSupervisor(), isEmployee(), isItemManager().

ItemPolicy

Metoda ADMIN SUPERVISOR EMPLOYEE Warunek
view Użytkownik należy do organizacji obiektu
create Użytkownik należy do organizacji przekazanej w żądaniu
update * Supervisor: należy do organizacji obiektu. Employee: musi być zarządcą (ItemManager) tego obiektu

ADMIN a Policy

Role ADMIN omijają policy dzięki metodzie before() w BasePolicy — dlatego ADMIN nie jest explicite wymieniony w policy, ale ma pełen dostęp.

ItemBlockPolicy

Metoda ADMIN SUPERVISOR EMPLOYEE Warunek
view Użytkownik należy do organizacji obiektu blokady
create * Supervisor: należy do organizacji obiektu. Employee: musi być zarządcą obiektu
update * Supervisor: należy do organizacji obiektu. Employee: musi być zarządcą obiektu

ItemBlock — withoutGlobalScopes

Metody view i update w ItemBlockPolicy wyszukują powiązany obiekt via Item::withoutGlobalScopes()->find(...), aby uniknąć problemów z filtrami globalnymi.

ItemManagerPolicy

Metoda ADMIN SUPERVISOR EMPLOYEE Warunek
view Użytkownik należy do organizacji obiektu
create Supervisor musi należeć do organizacji obiektu
update Supervisor musi należeć do organizacji obiektu

Employee nie zarządza zarządcami

ItemManagerPolicy nie przyznaje żadnych uprawnień roli EMPLOYEE — w przeciwieństwie do ItemPolicy i ItemBlockPolicy, gdzie employee z rolą zarządcy obiektu ma dostęp do tworzenia i edycji.


Walidacja żądań

StoreItemControllerRequest (tworzenie obiektu)

Pola wymagane: organization_id, category_id, name, is_active, is_public, street, street_number, postal_code, city, commune, district, voivodeship, teryt_data, is_paid, reservation_start_time, reservation_end_time, can_full_day_reservation, color

Pole Walidacja
organization_id required, uuid, exists:organizations
parent_id sometimes, nullable, uuid, exists:items, AllowedParentItemRule
category_id required, uuid, exists:categories, ActiveCategoryRule(ITEM)
name required, string, max:255
desc sometimes, nullable, string, max:5000
postal_code required, regex /^[0-9]{2}-[0-9]{3}$/
capacity sometimes, nullable, integer, 0-100000
area sometimes, nullable, numeric, 0-100000
reservation_start_time required, format H:i, before:end
reservation_end_time required, format H:i, after:start
is_public_price_list sometimes, boolean
can_pay_online sometimes, boolean
statute_src sometimes, nullable, file, mimes:pdf, max:10048
pricing required_if:is_paid,true, array
color required, regex hex (#XXX/#XXXX/#XXXXXX/#XXXXXXXX), not #000/#000000
tags sometimes, array, każdy element string max:255
assign_like_manager sometimes, boolean, AssignLikeManagerRule

Reguły niestandardowe:

Reguła Opis
AllowedParentItemRule Parent item musi należeć do tej samej organizacji
ActiveCategoryRule Kategoria musi być aktywna i mieć scope ITEM
AssignLikeManagerRule Zalogowany użytkownik musi być pracownikiem organizacji (OrganizationEmployee) — jeśli true, po utworzeniu obiektu automatycznie przypisuje użytkownika jako zarządcę (ItemManager)

UpdateItemControllerRequest (edycja obiektu)

Wszystkie pola są opcjonalne (sometimes) — umożliwia częściową aktualizację (partial update).

Pole Walidacja
organization_id sometimes, nullable, uuid, exists:organizations
parent_id sometimes, nullable, uuid, exists:items, NotSelfOrChildItemRule
category_id sometimes, nullable, uuid, exists:categories, ActiveCategoryRule(ITEM)
name sometimes, nullable, string, max:255
slug sometimes, nullable, string, max:255, unique:items (wyklucza aktualny)
desc sometimes, nullable, string, max:5000
is_public sometimes, boolean
street sometimes, nullable, string, max:255
street_number sometimes, nullable, string, max:50
postal_code sometimes, nullable, regex /^[0-9]{2}-[0-9]{3}$/
city sometimes, nullable, string, max:255
commune...voivodeship sometimes, nullable, string, max:255
teryt_data sometimes, nullable, array
capacity sometimes, nullable, integer, 0-100000
area sometimes, nullable, numeric, 0-100000
is_paid sometimes, boolean
reservation_start_time sometimes, nullable, format H:i
reservation_end_time sometimes, nullable, format H:i
can_full_day_reservation sometimes, boolean
is_public_price_list sometimes, boolean
can_pay_online sometimes, boolean
statute_src sometimes, nullable, file, mimes:pdf, max:10048
pricing sometimes, nullable, array
color sometimes, nullable, regex hex, not #000/#000000

Reguły niestandardowe (Update):

Reguła Opis
NotSelfOrChildItemRule Nie można ustawić obiektu jako rodzica samego siebie ani swojego potomka (zapobiega cyklom w hierarchii)
ActiveCategoryRule Kategoria musi być aktywna i mieć scope ITEM

Różnice Store vs Update

  • Store wymaga organization_id, name, is_active, is_public, adres, is_paid, godziny, color — w Update wszystkie opcjonalne
  • Store używa AllowedParentItemRule (ta sama organizacja) — Update używa NotSelfOrChildItemRule (zapobiega cyklom)
  • Update pozwala na edycję slug (z walidacją unikalności wykluczającą aktualny rekord)
  • Update nie ma pola assign_like_manager

StoreItemBlockControllerRequest (tworzenie blokady)

Pole Walidacja
item_id required, uuid, exists:items
date_time_from required, format Y-m-d H:i, before:date_time_to
date_time_to required, format Y-m-d H:i, after:date_time_from
reason_type required, string, in: maintenance/failure/internal/event/other
reason_details sometimes, nullable, string, max:5000

StoreMultipleItemBlockControllerRequest (masowe blokady)

Pole Walidacja
items required, array
items.*.item_id required, uuid, exists:items
items.*.date_time_from required, format Y-m-d H:i, before:date_time_to
items.*.date_time_to required, format Y-m-d H:i, after:date_time_from
items.*.reason_type required, string, in: maintenance/failure/internal/event/other
items.*.reason_details sometimes, nullable, string, max:5000

CheckItemBlockConflictsRequest (sprawdzanie konfliktów)

Pole Walidacja
item_id required, uuid, exists:items
ranges required, array, min:1
ranges.*.date_time_from required, format Y-m-d H:i
ranges.*.date_time_to required, format Y-m-d H:i, after:from

StoreItemManagerControllerRequest (przypisanie zarządcy)

Pole Walidacja
item_id required, uuid, exists:items
user_id required, uuid, exists:users, UniqueItemManagerRule, AllowedItemManagerRule

Reguły niestandardowe:

Reguła Opis
UniqueItemManagerRule Użytkownik nie jest już zarządcą tego obiektu
AllowedItemManagerRule Użytkownik musi być pracownikiem organizacji obiektu (OrganizationEmployee)

ChangeCategoryItemControllerRequest (zmiana kategorii)

Pole Walidacja
category_id required, uuid, exists:categories, ActiveCategoryRule(ITEM)

Zasoby API (Resources)

ItemResource

Relacje ładowane: organization, category, parent Ukryte dla niezalogowanych (UNALLOWED_NO_AUTH_KEYS): can_be_edit, is_in_use, reservations, reviews, schedules, can

Zawiera wszystkie pola modelu Item plus atrybuty obliczane: can_be_edit, total_reviews, avg_reviews, current_price, current_price_gross, current_price_vat_rate, lowest_price, lowest_price_gross, lowest_price_vat_rate, is_in_use, is_parent, thumbnail, color.

Ukrywanie cennika

Gdy is_public_price_list = false, dodatkowo ukrywane są pola cenowe: current_price, current_price_gross, current_price_vat_rate, lowest_price, lowest_price_gross, lowest_price_vat_rate (via getExtraUnallowedNoAuthKeys).

ItemBlockResource

Relacje ładowane: creator, canceler Ukryte dla niezalogowanych: tylko can

Pole Typ Opis
id string UUID
can_be_edit bool Czy można edytować
item_id string UUID obiektu
date_time_from string Początek blokady
date_time_to string Koniec blokady
created_by string UUID twórcy
reason_type string Klucz typu powodu
reason_type_lang string Przetłumaczony typ powodu (via ItemEnumService::getItemBlockReasons())
is_active boolean Czy aktywna
cancel_by string/null UUID osoby dezaktywującej
cancel_at string/null Data dezaktywacji
reason_details string/null Szczegóły powodu
created_at string Data utworzenia
updated_at string Data ostatniej modyfikacji

ItemManagerResource

Relacje ładowane: item, user Ukryte dla niezalogowanych: tylko can

Pole Typ Opis
id string UUID
item_id string UUID obiektu
user_id string UUID użytkownika
can_be_edit bool Czy można edytować
created_at string Data utworzenia
updated_at string Data ostatniej modyfikacji

Filtrowanie i sortowanie

Filtry obiektów (IndexItemController)

Filtr Typ Opis
filter[id] exact ID obiektu
filter[parent_id] exact ID obiektu nadrzędnego
filter[exclude_self_and_children] scope Wyklucz obiekt i jego potomków (rekurencyjnie)
filter[category_id] exact ID kategorii
filter[is_active] exact Czy aktywny
filter[is_public] exact Czy publiczny
filter[is_paid] exact Czy płatny
filter[is_public_price_list] exact Czy cennik publiczny
filter[are_there_stairs] exact Schody
filter[is_there_an_elevator] exact Winda
filter[are_there_facilities_for_disabled_people] exact Udogodnienia
filter[has_parking] exact Parking
filter[has_wifi] exact WiFi
filter[has_air_conditioning] exact Klimatyzacja
filter[is_pet_friendly] exact Zwierzęta
filter[is_kid_friendly] exact Dzieci
filter[is_smoking_allowed] exact Palenie
filter[has_smoking_area] exact Strefa palących
filter[has_restrooms] exact Toalety
filter[has_kitchen] exact Kuchnia
filter[has_garden] exact Ogród
filter[organization.name] partial Nazwa organizacji
filter[category.name] partial Nazwa kategorii
filter[parent.name] partial Nazwa obiektu nadrzędnego
filter[search] scope Wyszukiwanie pełnotekstowe
filter[price_from] scope Cena brutto od (z VAT)
filter[price_to] scope Cena brutto do (z VAT)
filter[capacity_from] scope Pojemność od
filter[capacity_to] scope Pojemność do
filter[area_from] scope Powierzchnia od
filter[area_to] scope Powierzchnia do

Wyszukiwanie pełnotekstowe (scope search)

Przeszukuje pola: name, slug, desc, tags, meta_title, meta_description oraz relacyjne category.name.

Filtrowanie po cenie (scope price_from/price_to)

Filtr ceny działa na wartościach brutto — oblicza cenę netto × (1 + VAT%). Dotyczy tylko cenników aktywnych, bez przypisanego klienta.

Sortowania obiektów

Sort Opis
sort=avg_reviews / sort=-avg_reviews Wg średniej ocen
sort=lowest_price / sort=-lowest_price Wg najniższej ceny (custom sort via SortByLowestPrice)
sort=name / sort=-name Wg nazwy
sort=created_at / sort=-created_at Wg daty utworzenia

Filtry blokad (IndexItemBlockController)

Filtr Typ Opis
filter[id] exact ID blokady
filter[item_id] exact ID obiektu

Filtry zarządców (IndexItemManagerController)

Filtr Typ Opis
filter[id] exact ID zarządcy
filter[item_id] exact ID obiektu
filter[item.organization_id] exact ID organizacji obiektu
filter[user_id] exact ID użytkownika
filter[user.name] partial Nazwa użytkownika
filter[item.name] partial Nazwa obiektu

Optymalizacja N+1 (ApplyItemAggregatesOrm)

Lista obiektów (GET /api/items) używa ApplyItemAggregatesOrm do eliminacji problemu N+1 query. Zamiast ładować relacje osobno dla każdego obiektu, system wykonuje agregaty w jednym zapytaniu:

Agregat Typ Opis
reviews_count withCount Liczba aktywnych recenzji
reviews_avg_rating withAvg Średnia ocen (aktywne recenzje)
lowest_price_value withMin Najniższa aktywna cena (bez client_id)
childrens_exists withExists Czy ma obiekty podrzędne
reservations_exists withExists Czy ma rezerwacje
reviews_exists withExists Czy ma recenzje
schedules_exists withExists Czy ma wpisy w harmonogramie
computed_current_price addSelect (subquery) Aktualna cena netto
computed_current_price_vat_rate addSelect (subquery) Stawka VAT aktualnej ceny
computed_lowest_price addSelect (subquery) Najniższa cena netto (31 dni)
computed_lowest_price_vat_rate addSelect (subquery) Stawka VAT najniższej ceny

Accessory w modelu Item sprawdzają array_key_exists() — jeśli agregat jest dostępny w atrybutach, używają go zamiast wykonywać osobne zapytanie.


Cykl życia obiektów (Observers)

ItemObserver

flowchart LR
    A[creating] --> B["CreatingItemObserver<br/>auto-generowanie slug"]
    C[updated] --> D["UpdatedItemObserver<br/>is_paid → zerowanie cennika<br/>is_active → kaskada na dzieci"]
Zdarzenie Akcja Opis
creating CreatingItemObserver Generuje slug z nazwy (polskie znaki → ASCII, spacje → _, + losowy 3-znakowy suffix)
updated UpdatedItemObserver Gdy is_paid zmienione na false → zeruje cennik (setZeroForIsPaidDirtyField). Gdy is_active zmienione → propaguje na wszystkie obiekty podrzędne (SetActiveInactiveChildItem)

ItemBlockObserver

flowchart LR
    A[creating] --> B["CreatingItemBlockObserver<br/>created_by = auth user<br/>is_active = true"]
    C[created] --> D["CreatedItemBlockObserver<br/>tworzenie Schedule<br/>anulacja kolidujących rezerwacji<br/>dezaktywacja nakładających się blokad"]
    E[updating] --> F["UpdatingItemBlockObserver<br/>cancel_by/cancel_at przy dezaktywacji"]
    G[updated] --> H["UpdatedItemBlockObserver<br/>obsługa zmiany is_active<br/>obsługa zmiany dat"]
    I[deleting] --> J["DeletingItemBlockObserver<br/>usunięcie Schedule"]
Zdarzenie Akcja Opis
creating CreatingItemBlockObserver Ustawia created_by na zalogowanego użytkownika, is_active na true
created CreatedItemBlockObserver Jeśli aktywna: tworzy wpis w Schedule. Następnie ForceCancelReservationAndDisableItemBlock: anuluje kolidujące rezerwacje + dezaktywuje nakładające się blokady
updating UpdatingItemBlockObserver Przy dezaktywacji ustawia cancel_by i cancel_at
updated UpdatedItemBlockObserver Gdy zmieniono is_active → zarządza Schedule (tworzenie/usuwanie). Gdy zmieniono daty (i blokada aktywna) → aktualizuje Schedule
deleting DeletingItemBlockObserver Usuwa powiązany Schedule

ItemManagerObserver

Zdarzenie Akcja Opis
created ItemManagerCreatedNotification Email do zarządcy: "Przypisanie jako zarządca obiektu" (via SimpleSendNotificationJob, kanał: mail)
deleted ItemManagerDeletedNotification Email do zarządcy: "Usunięcie z roli zarządcy obiektu" (via SimpleSendNotificationJob, kanał: mail)

Powiązania z innymi domenami

Domena Powiązanie
Rezerwacje (Reservation) Obiekty są rezerwowane przez mieszkańców (MorphMany)
Cenniki (Pricing) Każdy obiekt ma przypisany cennik godzinowy (MorphMany)
Kategorie (Category) Obiekty są grupowane w kategorie (BelongsTo)
Organizacje (Organization) Obiekt należy do organizacji (BelongsTo)
Harmonogramy (Schedule) Rezerwacje i blokady tworzą wpisy w kalendarzu (HasMany / MorphOne)
Media (Media) Zdjęcia obiektów (Spatie MediaLibrary)
Opinie (Review) Klienci mogą oceniać obiekty po rezerwacji (MorphMany)
Notatki (Note) Notatki do obiektów (MorphMany)
TERYT (TERYT) Adresy obiektów z integracji z rejestrem

Wartość biznesowa

Argumenty dla decydentów

  1. Pełna oferta w jednym miejscu — mieszkańcy widzą wszystkie obiekty gminy bez szukania
  2. Profesjonalna prezentacja — zdjęcia, opisy, mapa — jak w komercyjnych portalach
  3. Brak podwójnych rezerwacji — system automatycznie blokuje zajęte terminy
  4. Elastyczne zarządzanie — blokady na konserwacje bez ryzyka zapomnienia
  5. Hierarchia obiektów — duże kompleksy sportowe mogą być logicznie uporządkowane

Przykładowe metryki

Metryka Wartość
Obiekty na organizację Bez limitu
Poziomy hierarchii Nieograniczone
Zdjęcia na obiekt Bez limitu
Czas dodania obiektu < 10 min

Wyposażenie obiektów

System pozwala oznaczyć obiekty następującymi udogodnieniami:

Udogodnienie Pole Opis
Schody are_there_stairs Obecność schodów
Winda is_there_an_elevator Dostęp windą
Udogodnienia dla niepełnosprawnych are_there_facilities_for_disabled_people Podjazdy, toalety, oznaczenia
Parking has_parking Miejsca parkingowe przy obiekcie
WiFi has_wifi Dostęp do internetu bezprzewodowego
Klimatyzacja has_air_conditioning Klimatyzacja / ogrzewanie
Zwierzęta dozwolone is_pet_friendly Można przyjść ze zwierzętami
Przyjazny dzieciom is_kid_friendly Miejsce bezpieczne dla dzieci
Palenie dozwolone is_smoking_allowed Czy palenie jest dozwolone
Strefa dla palących has_smoking_area Wydzielona strefa
Toalety has_restrooms Toalety w obiekcie
Kuchnia has_kitchen Aneks kuchenny lub catering
Ogród has_garden Teren zewnętrzny

Blokady obiektów

Blokady pozwalają czasowo wyłączyć obiekt z rezerwacji:

Typ blokady Wartość enum Opis Przykład
Konserwacja maintenance Serwis, przeglądy "Wymiana podłogi"
Awaria failure Awaria, usterka "Awaria ogrzewania"
Użytek wewnętrzny internal Użytek wewnętrzny "Sesja rady gminy"
Wydarzenie event Impreza, wydarzenie "Turniej międzyszkolny"
Inne other Inny powód "Kwarantanna COVID"

Automatyczne anulowanie przy tworzeniu blokady

Tworzenie blokady automatycznie anuluje kolidujące rezerwacje — bez konieczności potwierdzenia force=true. Observer created wykonuje ForceCancelReservationAndDisableItemBlock, który:

  1. Znajduje kolidujące sloty rezerwacji (uwzględnia obiekty podrzędne)
  2. Anuluje kolidujące sloty (cancelReservationSlots)
  3. Oblicza i inicjuje zwroty (calculateCancellationRefund)
  4. Auto-potwierdza oczekujące zwroty ręczne (autoConfirmPendingRefunds) — metoda zwrotu: auto (automatyczny)
  5. Wysyła powiadomienie do klienta (typ: CANCELLATION_TYPE_DUE_TO_ITEM_BLOCK)
  6. Dezaktywuje nakładające się inne aktywne blokady na ten sam obiekt (DisableOverlappingItemBlocks)

Sprawdzanie konfliktów (check-conflicts)

Endpoint POST /api/item-blocks-check-conflicts pozwala sprawdzić, które rezerwacje kolidują z planowaną blokadą przed jej utworzeniem. Obsługuje wiele zakresów dat w jednym zapytaniu:

{
    "item_id": "uuid-obiektu",
    "ranges": [
        {"date_time_from": "2026-02-27 00:00", "date_time_to": "2026-02-27 23:59"},
        {"date_time_from": "2026-02-28 00:00", "date_time_to": "2026-02-28 23:59"},
        {"date_time_from": "2026-03-01 00:00", "date_time_to": "2026-03-01 23:59"}
    ]
}

Odpowiedź (GetItemBlockConflictSummary):

{
    "has_conflicts": true,
    "total_count": 2,
    "total_refund_amount": 150.00,
    "reservations": [
        {
            "id": "uuid",
            "status": "confirmed",
            "payment_status": "completed",
            "client": {
                "first_name": "Jan",
                "last_name": "Kowalski",
                "email": "jan@example.com",
                "mobile_phone": "123456789"
            },
            "total_price": 200.00,
            "is_paid": true,
            "total_paid": 200.00,
            "refund_amount": 100.00,
            "total_slots_count": 5,
            "conflicting_slots_count": 2,
            "will_cancel_entire_reservation": false,
            "conflicting_slots": [
                {"id": "uuid", "date": "2026-02-27", "time_from": "10:00", "time_to": "11:00", "price": 50.00}
            ]
        }
    ]
}

Zliczanie slotów

Przy określaniu czy rezerwacja zostanie w pełni anulowana (will_cancel_entire_reservation), system liczy tylko aktywne sloty (pomija wcześniej anulowane). Dzięki temu poprawnie rozpoznaje sytuację, gdy np. rezerwacja miała 5 slotów, 3 zostały wcześniej anulowane, a 2 aktywne kolidują z blokiem — wynik to pełne anulowanie (2/2).


Dane demo (Seeder)

ItemBlockSeeder generuje 2–5 blokad na każdy aktywny obiekt. Blokady są nakładane wyłącznie na wolne terminy (bez konfliktów z istniejącymi rezerwacjami i innymi blokadami). Typy blokad: maintenance, failure, internal, event, other — losowo.


Konfiguracja obiektu

Godziny działania

Parametr Typ Opis Przykład
reservation_start_time time (H:i) Od której godziny rezerwacje 06:00
reservation_end_time time (H:i) Do której godziny rezerwacje 22:00
can_full_day_reservation boolean Czy można rezerwować cały dzień true/false

Polityka rezerwacji

Parametr Typ Opis Przykład
reservation_deadline_hours integer Wyprzedzenie rezerwacji (min. godzin) 2
cancellation_deadline_hours integer Wyprzedzenie anulowania (min. godzin) 24
cancellation_policy_description string(1000) Tekst polityki anulowania "Bezpłatne anulowanie..."

Widoczność i płatności

Parametr Typ Opis
is_active boolean Czy obiekt jest aktywny w systemie
is_public boolean Czy obiekt jest widoczny publicznie
is_paid boolean Czy obiekt jest płatny
is_public_price_list boolean Czy cennik jest publiczny
can_pay_online boolean Czy dostępna płatność online (Przelewy24)

SEO i identyfikacja

Parametr Typ Opis
meta_title string(255) Tytuł SEO
meta_description string(5000) Opis SEO
tags array Tagi (tablica stringów)
color string Kolor obiektu (hex, wymagany, nie może być czarny)
statute_src file (PDF) Regulamin obiektu (max 10 MB)

Hierarchia obiektów

System pozwala tworzyć struktury hierarchiczne:

Kompleks Sportowy "Orlik"
├── Boisko główne
├── Boisko treningowe
└── Budynek zaplecza
    ├── Szatnia A
    ├── Szatnia B
    └── Sala konferencyjna

Dziedziczenie stanu

Gdy obiekt nadrzędny zostanie dezaktywowany, wszystkie obiekty podrzędne również zostaną ukryte (SetActiveInactiveChildItem w UpdatedItemObserver).

Walidacja hierarchii

Parent item musi należeć do tej samej organizacji co obiekt podrzędny (AllowedParentItemRule). Filtr exclude_self_and_children rekurencyjnie wyklucza obiekt i wszystkich jego potomków.

Konflikty blokad uwzględniają hierarchię

Przy wyszukiwaniu kolidujących rezerwacji (FindConflictingReservationsForPeriod) system sprawdza rezerwacje nie tylko dla samego obiektu, ale też dla jego obiektów podrzędnych (rekurencyjnie przez getRelatedItemIds).


Managerowie obiektów

Każdy obiekt może mieć przypisanych managerów — użytkowników odpowiedzialnych za ten obiekt:

  • Otrzymują powiadomienia email o przypisaniu/usunięciu (via SimpleSendNotificationJob)
  • Mogą przeglądać harmonogram swojego obiektu
  • Mogą tworzyć blokady
  • Mogą dodawać notatki do rezerwacji

Wymagania przy przypisaniu

  • Użytkownik musi być pracownikiem organizacji obiektu (OrganizationEmployee) — AllowedItemManagerRule
  • Użytkownik nie może być już zarządcą tego samego obiektu — UniqueItemManagerRule
  • Tylko role ADMIN i SUPERVISOR mogą zarządzać zarządcami (EMPLOYEE nie ma dostępu)

Przykład

Pani Kowalska jest managerem "Sali gimnastycznej ZS1". Gdy zostanie przypisana, otrzyma email "Przypisanie jako zarządca obiektu" z informacją o obiekcie i organizacji.


Wyjątki

Wyjątek Kod Kontekst
ChangeStateItemException 409 Próba aktywacji obiektu gdy organizacja lub kategoria jest nieaktywna
ChangeStateActivityItemException 409 Analogiczny wyjątek dla ActivityItem
NotFoundModelException 404 Obiekt/blokada/zarządca nie znaleziony

Warunki rzucania ChangeStateItemException (ExceptionChangeStateItem):

  • Obiekt jest nieaktywny i próbujemy go aktywować
  • Organizacja obiektu jest nieaktywna → "Cannot activate this item because the organization is inactive."
  • Kategoria obiektu jest nieaktywna → "Cannot activate this item because the category is inactive."

Powiadomienia

Powiadomienie Kanał Wyzwalacz Odbiorca
ItemManagerCreatedNotification mail Przypisanie zarządcy (created) Zarządca (user.email)
ItemManagerDeletedNotification mail Usunięcie zarządcy (deleted) Zarządca (user.email)

Powiadomienia wysyłane asynchronicznie via SimpleSendNotificationJob (kolejka domyślna).


Enumy (ItemEnumService)

Backed string enum z 5 wartościami — typy powodów blokad:

Case Wartość Tłumaczenie
ITEM_BLOCK_MAINTENANCE maintenance Serwis, przegląd
ITEM_BLOCK_FAILURE failure Awaria
ITEM_BLOCK_INTERNAL internal Użytek wewnętrzny
ITEM_BLOCK_EVENT event Wydarzenie
ITEM_BLOCK_OTHER other Inny powód

Historia zmian

Data Typ Opis
26 lut 2026 Dokumentacja Aktualizacja dokumentacji domeny — Dodano sekcję Autoryzacja i uprawnienia (Policies) z pełnym opisem ItemPolicy, ItemBlockPolicy, ItemManagerPolicy. Dodano dokumentację UpdateItemControllerRequest z różnicami vs Store (slug, NotSelfOrChildItemRule). Uzupełniono brakujące pola walidacji (can_pay_online, is_public_price_list). Dodano created_at/updated_at do resources. Zaktualizowano drzewo katalogów (6 reguł)
23 lut 2026 Cleanup Usunięcie zakomentowanego kodu z StoreMultipleItemBlockControllerRequest — Usunięto TODO i zakomentowaną regułę ItemBlockAvailabilityRule z walidacji items.*.date_time_to
20 lut 2026 Nowa funkcjonalność Pole assign_like_manager przy tworzeniu obiektu — Nowe opcjonalne pole boolean w StoreItemControllerRequest. Gdy true, walidacja AssignLikeManagerRule sprawdza czy zalogowany użytkownik jest pracownikiem organizacji (OrganizationEmployee). Po utworzeniu obiektu automatycznie tworzy ItemManager z user_id = Auth::id(). Przy braku przypisania do organizacji zwraca 422
18 lut 2026 Optymalizacja Fix N+1: user.rolesuser.role w IndexItemManagerController — Korekta eager loading: user.roles (Spatie MorphToMany) nie eliminował N+1, ponieważ TraitUserRelations::user() ładuje "$keyName.role" (singular HasOneThrough via ModelHasRoles). Zmieniono na user.roleTraitRelationLoader wykrywa załadowaną relację i pomija dodatkowe query
17 lut 2026 Bugfix Fix brakujących powiadomień przy blokadach obiektówautoConfirmPendingRefunds w CancelConflictingReservationsForItemBlock rzucał wyjątek dla płatności ręcznych z refund_method = 'undefined', blokując wysyłkę notyfikacji. Dodano nową metodę zwrotu REFUND_METHOD_AUTO (auto) oraz migrację na rozszerzenie ENUM
15 lut 2026 Dokumentacja Pełna aktualizacja dokumentacji domeny — struktura, modele, endpointy, walidacja, observers, notifications, scopes, sorts, attrs, resources, exceptions
11 lut 2026 Optymalizacja Eliminacja N+1 query w GET /api/items — Utworzono ApplyItemAggregatesOrm w Action/Other/ z pre-computed subqueries (computed_current_price, computed_current_price_vat_rate, computed_lowest_price, computed_lowest_price_vat_rate via addSelect), withCount (reviews_count), withAvg (reviews_avg_rating), withMin (lowest_price_value), withExists (childrens, reservations, reviews, schedules), with('media'). Zmodyfikowano 10 accessorów w modelu Item z array_key_exists() fallback pattern (avg_reviews, current_price, current_price_gross, current_price_vat_rate, lowest_price, lowest_price_gross, lowest_price_vat_rate, is_parent, is_in_use). Zaktualizowano TraitItem i IndexItemController