Przejdź do treści

Harmonogramy (Schedule)

Ostatnia aktualizacja dokumentacji: 2026-02-26 Stan synchronizacji z kodem: Zsynchronizowany


1. Opis ogólny

Domena Harmonogramów odpowiada za wizualizację czasu w systemie ZAJMIJ.TO. Jest to centralny punkt agregacji danych z różnych źródeł — rezerwacje, blokady obiektów i sesje zajęć grupowych — prezentowanych w formie kalendarza z możliwością eksportu do PDF.

Model Schedule jest zapisem czasowym o polimorficznym powiązaniu (scheduleable), reprezentującym:

  • slot rezerwacji (ReservationSlot) — zarezerwowany czas przez klienta,
  • slot oczekującej rezerwacji (WaitingReservationSlot) — tymczasowa blokada (soft-lock) podczas składania rezerwacji przez niezalogowanego użytkownika,
  • blokadę obiektu (ItemBlock) — planowe lub awaryjne wyłączenie obiektu.

Sesje zajęć (ActivityItem) nie są przechowywane w tabeli schedules — są konwertowane do formatu harmonogramu w locie podczas pobierania danych (klasa ConvertActivityItemsToScheduleFormat).

Kluczowe cechy

  • Harmonogram jest tylko do odczytu — nie posiada endpointów tworzenia, aktualizacji ani usuwania. Wpisy tworzone są automatycznie przez inne domeny (Reservation, Item).
  • Obsługuje filtrowanie z uwzględnieniem hierarchii obiektów (obiekt nadrzędny i podrzędne w jednym zapytaniu).
  • Eksport PDF obsługuje trzy widoki: miesięczny (kalendarz siatki), tygodniowy (oparty na minutach) i listowy (tabela z podsumowaniem statystyk).
  • Dostęp publiczny (bez logowania) do endpointu index jest możliwy, lecz z ograniczeniami.

2. Architektura domeny

2.1 Modele danych

Schedule (Domain\Schedule\Model\Schedule)

Główny model harmonogramu. Każdy rekord reprezentuje jedno zdarzenie czasowe.

Pole Typ Opis
id uuid (PK) Identyfikator rekordu
organization_id uuid (FK, nullable) Powiązana organizacja
scheduleable_type string Klasa polimorficzna (ReservationSlot, WaitingReservationSlot, ItemBlock)
scheduleable_id uuid ID rekordu polimorficznego
item_id uuid (FK) Powiązany obiekt (items.id, cascade delete)
start_at datetime Czas rozpoczęcia zdarzenia
end_at datetime Czas zakończenia zdarzenia
created_at datetime Data utworzenia rekordu
updated_at datetime Data ostatniej aktualizacji

Atrybuty wirtualne (computed):

Atrybut Typ Opis
scheduleable_color string Kolor heksadecymalny zdarzenia (obliczany przez GetSchedulableColorAttr)

Relacje:

Relacja Typ Opis
scheduleable() MorphTo Polimorficzny właściciel harmonogramu
organization() BelongsTo Organizacja powiązana z harmonogramem
item() BelongsTo Obiekt, którego dotyczy harmonogram

Publiczne filtry ($publicFilters):

id, item_id, organization_id, dates_between

Scope'y modelu:

Scope Opis
scopeDatesBetween(string $dates) Filtruje rekordy nakładające się z podanym zakresem dat
scopeClientId(string $clientId) Filtruje rekordy ReservationSlot powiązane z klientem

2.2 Diagram relacji (Mermaid)

erDiagram
    schedules {
        uuid id PK
        uuid organization_id FK
        string scheduleable_type
        uuid scheduleable_id
        uuid item_id FK
        datetime start_at
        datetime end_at
    }

    organizations {
        uuid id PK
        string name
    }

    items {
        uuid id PK
        string name
        uuid parent_id FK
    }

    reservation_slots {
        uuid id PK
        uuid reservation_id FK
    }

    waiting_reservation_slots {
        uuid id PK
        uuid waiting_reservation_id FK
    }

    item_blocks {
        uuid id PK
        string reason_type
    }

    activity_items {
        uuid id PK
        uuid activity_id FK
        date date
        time start_time
        time end_time
    }

    schedules }o--|| organizations : "organization_id"
    schedules }o--|| items : "item_id (cascade)"
    schedules }o--o| reservation_slots : "scheduleable (morph)"
    schedules }o--o| waiting_reservation_slots : "scheduleable (morph)"
    schedules }o--o| item_blocks : "scheduleable (morph)"
flowchart TD
    subgraph "Źródła danych harmonogramu"
        RS[ReservationSlot\nrezerwacja klienta]
        WRS[WaitingReservationSlot\nsoft-lock niezalogowanego]
        IB[ItemBlock\nblokada obiektu]
        AI[ActivityItem\nsesja zajęć grupowych]
    end

    subgraph "Tabela schedules"
        S[Schedule\n📅 start_at / end_at]
    end

    subgraph "Źródła wirtualne (bez DB)"
        AIV[ActivityItem\nkonwertowane w locie]
    end

    RS -->|morphOne| S
    WRS -->|morphOne| S
    IB -->|morphOne| S
    AI -->|ConvertActivityItemsToScheduleFormat| AIV
    AIV -.->|wynik zapytania PDF| S

2.3 Struktura tabel w bazie danych

Tabela schedules (migracja: 0001_01_01_000022_create_schedules.php):

CREATE TABLE schedules (
    id             UUID PRIMARY KEY,
    organization_id UUID NULL,
    scheduleable_type VARCHAR(255) NOT NULL,
    scheduleable_id   UUID NOT NULL,
    item_id        UUID NOT NULL,
    start_at       DATETIME NOT NULL,
    end_at         DATETIME NOT NULL,
    created_at     DATETIME,
    updated_at     DATETIME,

    INDEX (organization_id),
    INDEX (scheduleable_type, scheduleable_id),
    INDEX (item_id),

    FOREIGN KEY (item_id) REFERENCES items(id) ON DELETE CASCADE,
    FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE CASCADE
);

3. Endpointy API

Uwierzytelnianie

Endpoint Wymaga logowania Role
GET /api/schedules Nie (dostępny publicznie) Brak ograniczenia roli
GET /api/schedules/export-pdf Tak admin, supervisor, employee

3.1 GET /api/schedules

Trasa: no-auth-schedules.index (publiczny) / schedules.index (zalogowany)

Opis: Pobiera listę harmonogramów z paginacją i filtrowaniem. Zwraca kolekcję ScheduleResource. Klucze scheduleable, scheduleable_id i can są usuwane z odpowiedzi dla niezalogowanych użytkowników.

Klasy: ScheduleController::indexTraitControllerSchedule::indexScheduleControllerIndexScheduleController::__invoke

Parametry zapytania:

Parametr Typ Opis
filter[id] string (UUID) Filtruj po ID harmonogramu
filter[item_id] string (UUID) Filtruj po ID obiektu (dokładne dopasowanie)
filter[item_with_hierarchy] string (UUID lub lista UUID) Filtruj po obiekcie uwzględniając hierarchię (obiekt + rodzic + dzieci)
filter[dates_between] string Zakres dat w formacie YYYY-MM-DD,YYYY-MM-DD lub YYYY-MM-DD\|YYYY-MM-DD
sort string Sortowanie: start_at, end_at, created_at (domyślnie: start_at)
page int Numer strony (paginacja)
per_page int Liczba rekordów na stronę
include string Relacje do załadowania: organization, scheduleable, item, item.childrens

Ograniczenie dla niezalogowanych: Filtr dates_between ograniczony do maksymalnie 365 dni (klasa ScopeDatesBetweenFunc, stała MAX_DAYS_FOR_GUEST = 365).

Eager loading (domyślne): Poprzez ApplyScheduleAggregatesOrm automatycznie ładowane są: - scheduleable z morphWith: - ReservationSlot['refunds', 'reservation.client', 'reservation.latestNote'] - ItemBlock['creator.role'] - item.childrens

Odpowiedź (ScheduleResource):

{
  "data": [
    {
      "id": "uuid",
      "organization_id": "uuid|null",
      "scheduleable_type": "Domain\\Reservation\\Model\\ReservationSlot",
      "scheduleable_id": "uuid",
      "item_id": "uuid",
      "start_at": "2026-02-01 08:00:00",
      "end_at": "2026-02-01 10:00:00",
      "can_be_edit": false,
      "created_at": "2026-01-01 12:00:00",
      "updated_at": "2026-01-01 12:00:00",
      "scheduleable_color": "#248bc7",
      "can": {
        "view": true,
        "update": false,
        "delete": false
      },
      "scheduleable": { ... },
      "item": { ... },
      "organization": { ... }
    }
  ],
  "meta": { ... },
  "links": { ... }
}

Uwaga: Dla niezalogowanych użytkowników pola scheduleable, scheduleable_id i can są usuwane z odpowiedzi (tablica UNALLOWED_NO_AUTH_KEYS).

3.2 GET /api/schedules/export-pdf

Trasa: schedules.export-pdf (wymaga logowania)

Opis: Generuje plik PDF z harmonogramem w jednym z trzech widoków. Zwraca plik do pobrania (typ MIME application/pdf). Nazwa pliku: harmonogram_{widok}_{data}.pdf (np. harmonogram_lista_2026-02-26_123456.pdf).

Klasy: ScheduleController::exportPdfTraitControllerSchedule::exportSchedulePdfControllerExportSchedulePdfController::__invoke

Role wymagane: admin, supervisor, employee

Parametry zapytania:

Parametr Typ Wymagany Opis
view_type string Tak Typ widoku: month, week, list
month string Nie Miesiąc w formacie YYYY-MM (domyślnie: bieżący miesiąc)
include_filters_info boolean Nie Czy dołączyć nagłówek z informacją o filtrach (domyślnie: true)
orientation string Nie Orientacja strony: portrait, landscape (domyślnie: landscape dla month/week, portrait dla list)
hide_client_data boolean Nie Ukryj dane klientów w PDF (imiona, nazwiska)
filter[item_id] string (UUID) Warunkowo Filtruj po obiekcie (wymagany co najmniej jeden filtr)
filter[client_id] string (UUID) Warunkowo Filtruj po kliencie (wymagany co najmniej jeden filtr)
filter[activity_id] string (UUID) Warunkowo Filtruj po zajęciach (wymagany co najmniej jeden filtr)
filter[hide_client_data] boolean Nie Alternatywna lokalizacja parametru ukrycia danych klientów
filter[dates_between] string Nie Zakres dat YYYY-MM-DD,YYYY-MM-DD (max 31 dni)

Reguła filtrów: Co najmniej jeden z filtrów filter[item_id], filter[client_id] lub filter[activity_id] jest wymagany. Jeśli żaden nie jest podany, walidacja zwraca błąd filter_required dla wszystkich trzech pól.

Reguła zakresu dat: Filtr filter[dates_between] ograniczony do 31 dni (klasa DateRangeBetweenRule i ValidateDateRange).

Priorytet zakresu dat: 1. filter[dates_between] — jeśli podany 2. month — jeśli podany (oblicza pełny miesiąc) 3. Domyślnie: bieżący miesiąc

Szablony Blade (barryvdh/laravel-dompdf):

view_type Plik szablonu
month schedule.pdf.month
week schedule.pdf.week
list schedule.pdf.list

Format pliku PDF: - Papier: A4 - Czcionka: DejaVu Sans (obsługa polskich znaków) - Orientacja: konfigurowalna


4. Logika biznesowa

4.1 Główne procesy

Pobieranie listy harmonogramów (IndexScheduleController)

flowchart TD
    A[GET /api/schedules] --> B[Walidacja parametrów\nIndexScheduleControllerRequest]
    B --> C[IndexScheduleController::__invoke]
    C --> D[Budowanie QueryBuilder\nz filtrami i sortowaniem]
    D --> E[ApplyScheduleAggregatesOrm\ndefaultSort + eager loading]
    E --> F{Filtr dates_between?}
    F -->|Tak| G[ScopeDatesBetweenFunc\nwhere start_at <= to\nAND end_at >= from]
    F -->|Nie| H[Brak filtra dat]
    G --> I{Niezalogowany\ni > 365 dni?}
    I -->|Tak| J[DateRangeExceedsLimitException]
    I -->|Nie| K[Zwrot ScheduleResource]
    H --> K

Filtry dostępne w IndexScheduleController:

Filtr Implementacja Opis
id AllowedFilter::exact('id') Dokładne dopasowanie UUID
item_id AllowedFilter::exact('item_id') Dokładne dopasowanie ID obiektu
item_with_hierarchy AllowedFilter::callbackScopeItemWithHierarchyFunc Rozszerzone dopasowanie po hierarchii obiektów
dates_between AllowedFilter::scope('dates_between')ScopeDatesBetweenFunc Zakres dat (nakładające się zdarzenia)

Logika ScopeItemWithHierarchyFunc: Gdy podany jest item_id, system wyszukuje: 1. Sam podany obiekt 2. Jego obiekt nadrzędny (jeśli parent_id nie jest null) 3. Wszystkie jego obiekty podrzędne (where parent_id = item_id)

Następnie filtruje schedule po wszystkich znalezionych ID.

Generowanie eksportu PDF (ExportSchedulePdfController)

flowchart TD
    A[GET /api/schedules/export-pdf] --> B[Walidacja\nExportSchedulePdfControllerRequest]
    B --> C[ParseScheduleExportRequest\nrozparsowanie parametrów]
    C --> D[ValidateDateRange\nmaks. 31 dni]
    D --> E{Podano activity_id?}
    E -->|Tak| F[FetchActivityItemsForExport\nAND ConvertActivityItemsToScheduleFormat]
    E -->|Nie| G[FetchSchedulesForExport\nschedules + opcjonalnie activity_items dla client_id]
    F --> H[BuildSchedulePdfFilterInfo\nnagłówek PDF]
    G --> H
    H --> I[BuildSchedulePdfData]
    I --> J{view_type}
    J -->|month| K[BuildMonthViewPdfData\nsiatkowy kalendarz]
    J -->|week| L[BuildWeekViewMinuteBasedPdfData\noparty na minutach]
    J -->|list| M[BuildListViewPdfData\ntabela + statystyki]
    K --> N[GenerateSchedulePdf\nDomPDF → plik .pdf]
    L --> N
    M --> N

Logika FetchSchedulesForExport: Pobiera harmonogramy z tabeli schedules. Gdy podano client_id, dodatkowo pobiera ActivityItem powiązane z rezerwacjami klienta i konwertuje je na obiekty Schedule (poprzez ConvertActivityItemsToScheduleFormat), a następnie scala obie kolekcje i sortuje po start_at.

Konwersja ActivityItem do formatu Schedule

ConvertActivityItemsToScheduleFormat tworzy tymczasowe obiekty Schedule z danych ActivityItem bez zapisu do bazy danych: - schedule->id = activityItem->id - schedule->start_at = data + start_time - schedule->end_at = data + end_time - schedule->scheduleable_type = ActivityItem::class - schedule->scheduleable_id = activityItem->id - Relacja organization = pobrana z activityItem->activity->organization

4.2 Reguły biznesowe

  1. Harmonogram jest tylko do odczytu. Nie istnieją endpointy POST, PUT, PATCH ani DELETE dla domeny Schedule. Wszystkie wpisy są tworzone przez inne domeny.

  2. Zdarzenia WaitingReservationSlot są widoczne w harmonogramie (kolor pomarańczowy), ale nie są renderowane w eksporcie PDF. FormatScheduleEvent zwraca null dla scheduleable_type === WaitingReservationSlot::class.

  3. Sesje zajęć (ActivityItem) nie mają wpisów w tabeli schedules. Są pobierane bezpośrednio z tabeli activity_items i konwertowane na format Schedule w locie podczas eksportu PDF.

  4. Widok tygodniowy PDF obejmuje całą dobę (00:00–24:00). Konfiguracja WeekViewPdfConfig: DAY_START_HOUR = 0, DAY_END_HOUR = 24, MINUTES_PER_PAGE = 1440. Jeden dzień = jedna strona PDF.

  5. Eksport PDF wymaga filtra. Co najmniej jeden z filter[item_id], filter[client_id] lub filter[activity_id] musi być podany.

  6. Eksport PDF ograniczony do 31 dni. Walidacja odbywa się na dwóch poziomach: DateRangeBetweenRule (walidacja requesta) i ValidateDateRange (walidacja w kontrolerze akcji).

  7. Kolory zdarzeń są obliczane przez GetSchedulableColorAttr:

  8. ReservationSlot → kolor klienta (reservation.client.color) lub domyślny #248bc7
  9. WaitingReservationSlot → stały #F59E0B (pomarańczowy)
  10. Inne (w tym ItemBlock) → stały #EF4444 (czerwony)

  11. Nakładające się zdarzenia w widoku tygodniowym są obsługiwane przez DetectOverlapsAndLayout, który przydziela kolumny (column, total_columns) do każdego zdarzenia, aby uniknąć wizualnego nakrywania.

4.3 Walidacje

IndexScheduleControllerRequest

Korzysta z paginationDefaultRequestRules() (paginacja) i publicFilterRequestRules(Schedule::class) (filtry: id, item_id, organization_id, dates_between). Dostępna publicznie (authorize(): true).

ExportSchedulePdfControllerRequest

Pole Reguła Opis
view_type required\|string\|in:month,week,list Wymagany typ widoku
month nullable\|string\|regex:/^\d{4}-\d{2}$/ Opcjonalny miesiąc w formacie YYYY-MM
include_filters_info nullable\|boolean Czy dołączyć sekcję filtrów w PDF
orientation nullable\|string\|in:portrait,landscape Orientacja strony
hide_client_data nullable\|boolean Ukryj dane klientów
filter.item_id nullable\|string\|exists:items,id ID obiektu
filter.client_id nullable\|string\|exists:clients,id ID klienta
filter.activity_id nullable\|string\|exists:activities,id ID zajęć
filter.hide_client_data nullable\|boolean Alternatywna lokalizacja hide_client_data
filter.dates_between nullable\|string\|regex:.../DateRangeBetweenRule(31) Zakres dat maks. 31 dni

Domyślne wartości (prepareForValidation): - orientation: landscape jeśli view_type === 'month', w przeciwnym wypadku portrait - include_filters_info: true

Reguła niestandardowa DateRangeBetweenRule: - Sprawdza, że zakres dat nie przekracza $maxDays (domyślnie 31) - Sprawdza, że data końcowa jest po dacie początkowej - Format wejściowy: YYYY-MM-DD,YYYY-MM-DD


5. Autoryzacja i uprawnienia

Policy SchedulePolicy

Metoda Warunek Opis
view(User, Schedule) belongsToOrganization($user, $schedule->organization_id) Widoczność tylko dla użytkowników organizacji
create(User) false Tworzenie harmonogramów jest wyłączone
update(User, Schedule) false Modyfikacja harmonogramów jest wyłączona

Dostęp do endpointów

Endpoint Niezalogowany Zalogowany (dowolna rola) admin / supervisor / employee
GET /api/schedules (no-auth) Tak (ograniczony) Tak Tak
GET /api/schedules (auth) Nie Tak Tak
GET /api/schedules/export-pdf Nie Nie Tak

Ograniczenia dla niezalogowanych (ScheduleResource): - Pola scheduleable, scheduleable_id, can są usuwane z odpowiedzi (stała UNALLOWED_NO_AUTH_KEYS). - Filtr dates_between ograniczony do 365 dni (klasa ScopeDatesBetweenFunc).


6. Eventy i efekty uboczne

Observer ScheduleObserver

Klasa ScheduleObserver (powiązana przez atrybut #[ObservedBy(ScheduleObserver::class)]) jest aktualnie pusta — nie zawiera żadnych hooków cyklu życia modelu.

Brak własnych eventów

Domena Schedule nie publikuje własnych eventów domenowych. Wpisy w harmonogramie są tworzone i usuwane przez inne domeny (Reservation, Item) jako efekt uboczny ich operacji.

Tworzenie wpisów (przez inne domeny)

Wpisy Schedule są tworzone automatycznie gdy: - Tworzona jest ReservationSlot — morficznie powiązana jako scheduleable - Tworzony jest WaitingReservationSlot — tymczasowa blokada podczas procesu rezerwacji - Tworzony jest ItemBlock — blokada obiektu przez pracownika

Usuwanie wpisów Schedule następuje kaskadowo przez usunięcie powiązanego Item (FK z ON DELETE CASCADE).


7. Notyfikacje

Domena Schedule nie generuje własnych notyfikacji. Jest domeną read-only skoncentrowaną na prezentacji danych i eksporcie.


8. Powiązania z innymi domenami

graph LR
    SCH[Schedule]

    RES[Reservation]
    RS[ReservationSlot]
    WRS[WaitingReservationSlot]
    ITM[Item]
    IB[ItemBlock]
    ACT[Activity]
    AI[ActivityItem]
    CLI[Client]
    ORG[Organization]
    TRN[Trainer]

    RS -->|morphOne| SCH
    WRS -->|morphOne| SCH
    IB -->|morphOne| SCH
    ITM -->|item_id| SCH
    ORG -->|organization_id| SCH

    RES --> RS
    ITM --> IB
    ACT --> AI
    AI -.->|konwersja w locie\ndo eksportu PDF| SCH

    CLI -.->|filtr client_id\nw eksporcie| SCH
    TRN -.->|dane w\nFormatScheduleEvent| SCH
Domena Typ powiązania Opis
Reservation Pośrednie przez ReservationSlot Każdy slot rezerwacji tworzy wpis w harmonogramie
Item Bezpośrednie (item_id) Harmonogram zawsze dotyczy konkretnego obiektu; cascade delete
Item (ItemBlock) Polimorficzne (scheduleable) Blokady obiektów są jednym z typów zdarzeń
Activity (ActivityItem) Wirtualne (konwersja w locie) Sesje zajęć pobierane z activity_items i konwertowane dla eksportu
Client Pośrednie przez ReservationSlot.reservation.client Dane klienta w kolorach i eksporcie PDF; filtr client_id
Organization Bezpośrednie (organization_id) Harmonogram należy do organizacji
Trainer Pośrednie przez ActivityItem.trainer Nazwisko trenera w eksporcie PDF jako "prowadzący"

9. Konfiguracja

Enum WeekViewPdfConfig (Domain\Schedule\Enum\WeekViewPdfConfig)

Konfiguruje okno czasowe dla widoku tygodniowego PDF:

Stała Wartość Opis
DAY_START_HOUR 0 Godzina rozpoczęcia dnia (00:00)
DAY_START_MINUTE 0 Minuta rozpoczęcia dnia
DAY_END_HOUR 24 Godzina zakończenia dnia (24:00 = północ)
DAY_END_MINUTE 0 Minuta zakończenia dnia
MINUTES_PER_PAGE 1440 Liczba minut na jedną stronę PDF (1440 = cała doba)

Wynikowy efekt: Widok tygodniowy PDF generuje jedną stronę na każdy dzień obejmującą całą dobę (00:00–24:00). Paginacja stron obliczana jest jako ceil(total_minutes / MINUTES_PER_PAGE).

Ograniczenia zakresu dat

Kontekst Maksymalny zakres Klasa
Eksport PDF 31 dni ValidateDateRange, DateRangeBetweenRule
Pobieranie listy (niezalogowany) 365 dni ScopeDatesBetweenFunc::MAX_DAYS_FOR_GUEST
Pobieranie listy (zalogowany) Brak ograniczenia

Kolory zdarzeń

Typ Źródło koloru Wartość domyślna
ReservationSlot reservation.client.color #248bc7
WaitingReservationSlot Stały #F59E0B
ItemBlock i inne Stały #EF4444
Zajęcia (ActivityItem) w PDF activity.category.color #6B7280

10. Znane ograniczenia i TODO

Ograniczenia

  1. Brak endpointów zarządzania. Harmonogram jest wyłącznie do odczytu — wpisy można tylko przeglądać i eksportować. Tworzenie/edycja odbywa się wyłącznie przez domeny Reservation i Item.

  2. ActivityItem nie jest w tabeli schedules. Sesje zajęć grupowych są pobierane i konwertowane w locie. Oznacza to, że nie mają przypisanego item_id ani organization_id w strukturze Schedule, co może powodować null w tych polach podczas eksportu PDF.

  3. Widok tygodniowy PDF = cała doba. WeekViewPdfConfig nie jest konfigurowalny przez parametry API — zakres godzin 00:00–24:00 jest zakodowany na stałe w klasie enum.

  4. Brak filtra organization_id w IndexScheduleController. Pole organization_id jest w $publicFilters modelu, ale nie jest zarejestrowane w AllowedFilter kontrolera akcji IndexScheduleController (dostępny jest tylko przez publicFilterRequestRules na poziomie walidacji requesta, nie jako AllowedFilter QueryBuilder).

  5. Limit eksportu PDF (31 dni) jest stały. Wartość MAX_DAYS = 31 jest zakodowana na stałe w klasie ValidateDateRange i nie jest konfigurowalna.

  6. ScheduleObserver jest pusty. Klasa obserwatora jest zarejestrowana (#[ObservedBy]), ale nie zawiera żadnych hooków — potencjalne miejsce na przyszłą logikę.

  7. ScheduleEnumService jest pusty. Enum serwis nie zawiera żadnych wartości.

Historia zmian

Data Typ Opis
22 lut 2026 Naprawa FormatScheduleEvent: wykluczenie rekordów WaitingReservationSlotFormatScheduleEvent::__invoke zwraca null dla scheduleable_type === WaitingReservationSlot::class. Rekordy soft-lock nie są renderowane w widokach PDF/tygodniowym/miesięcznym. Zaktualizowane klasy: FormatScheduleEvent, ProcessScheduleEvent, BuildMonthViewPdfData, BuildListViewPdfData
20 lut 2026 Funkcjonalność Soft-lock: WaitingReservationSlot w harmonogramie — Nowy model WaitingReservationSlot z relacją morphOne do Schedule. Tymczasowe blokady (kolor #F59E0B) dla niezalogowanych użytkowników podczas składania rezerwacji. Usuwane przez CRON reservation:cleanup-waiting
18 lut 2026 Optymalizacja Fix N+1: creator.rolescreator.role w ApplyScheduleAggregatesOrm — Korekta eager loading relacji HasOneThrough role (singular) zamiast MorphToMany roles
18 lut 2026 Optymalizacja Eliminacja N+1 query w GET /api/schedules (faza 2) — Rozszerzono eager loading w ApplyScheduleAggregatesOrm: ReservationSlot['refunds', 'reservation.client', 'reservation.latestNote'], ItemBlock['creator.role']. Dodano item.childrens do IndexScheduleController
11 lut 2026 Optymalizacja Eliminacja N+1 query w GET /api/schedules (faza 1) — Utworzono ApplyScheduleAggregatesOrm z eager loading polimorficznym via morphWith. Przeniesiono defaultSort z inline closure do dedykowanej klasy