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] - Administrator tworzy nowy obiekt w panelu
- Podaje nazwę, opis i kategorię (np. "Sala konferencyjna", kategoria "Sale")
- Wprowadza adres z autouzupełnianiem TERYT (województwo → powiat → gmina → miejscowość)
- Zaznacza wyposażenie: WiFi, parking, klimatyzacja, toalety, kuchnia...
- Ustawia godziny działania (np. 8:00-22:00)
- Dodaje cennik (np. 50 zł/godz. netto) — wymagany gdy
is_paid=true - Wgrywa zdjęcia obiektu
- 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] - Administrator tworzy blokadę dla obiektu
- Wybiera typ: "Konserwacja"
- Podaje zakres dat: 15-17 stycznia
- Dodaje opis: "Wymiana oświetlenia"
- System automatycznie anuluje kolidujące sloty rezerwacji
- Inicjuje zwroty za anulowane sloty (auto-potwierdza oczekujące zwroty ręczne)
- Wysyła powiadomienie do klientów (częściowe lub pełne anulowanie)
- Dezaktywuje nakładające się istniejące blokady na ten sam obiekt
Scenariusz 3: Mieszkaniec szuka sali na imprezę¶
- Mieszkaniec wchodzi na listę obiektów
- Filtruje: kategoria "Sale", pojemność od 50 osób, cena do 100 zł/godz.
- System pokazuje pasujące obiekty posortowane wg ceny
- Mieszkaniec klika w wybrany obiekt
- Widzi: zdjęcia, opis, mapę, wyposażenie, kalendarz dostępności
- 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żywaNotSelfOrChildItemRule(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
- Pełna oferta w jednym miejscu — mieszkańcy widzą wszystkie obiekty gminy bez szukania
- Profesjonalna prezentacja — zdjęcia, opisy, mapa — jak w komercyjnych portalach
- Brak podwójnych rezerwacji — system automatycznie blokuje zajęte terminy
- Elastyczne zarządzanie — blokady na konserwacje bez ryzyka zapomnienia
- 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:
- Znajduje kolidujące sloty rezerwacji (uwzględnia obiekty podrzędne)
- Anuluje kolidujące sloty (
cancelReservationSlots) - Oblicza i inicjuje zwroty (
calculateCancellationRefund) - Auto-potwierdza oczekujące zwroty ręczne (
autoConfirmPendingRefunds) — metoda zwrotu:auto(automatyczny) - Wysyła powiadomienie do klienta (typ:
CANCELLATION_TYPE_DUE_TO_ITEM_BLOCK) - 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 | Przypisanie zarządcy (created) | Zarządca (user.email) | |
ItemManagerDeletedNotification | 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.roles→user.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.role — TraitRelationLoader wykrywa załadowaną relację i pomija dodatkowe query |
| 17 lut 2026 | Bugfix | Fix brakujących powiadomień przy blokadach obiektów — autoConfirmPendingRefunds 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 |