Przejdź do treści

Kategorie (Category)

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


1. Opis ogólny

Domena Kategorii odpowiada za klasyfikację obiektów i zajęć w systemie zajmij.to. To jak system półek w bibliotece — każdy obiekt i każde zajęcia mają swoje miejsce, które ułatwia mieszkańcom znalezienie tego, czego szukają. Kategorie umożliwiają administratorom logiczną organizację zasobów z wizualną identyfikacją (ikony, kolory), a mieszkańcom — szybkie filtrowanie oferty.

Kategorie są płaskie (bez hierarchii) i dzielą się na dwa typy:

  • Kategorie obiektów (item) — Sale konferencyjne, Boiska, Hale sportowe, Sprzęt
  • Kategorie aktywności (activity) — Fitness, Zajęcia dla dzieci, Warsztaty, Kursy

Korzyści dla JST

  • Łatwiejsze wyszukiwanie — mieszkaniec od razu wie gdzie szukać
  • Spójna prezentacja — wszystkie obiekty sportowe wyglądają podobnie
  • Własne ikony i kolory — każda kategoria jest rozpoznawalna
  • Automatyzacja — dezaktywacja kategorii ukrywa wszystkie jej obiekty i aktywności

2. Architektura domeny

2.1 Modele danych

Model Category

Plik: app/Domain/Category/Model/Category.php Tabela: categories Klucz główny: UUID (HasUuids) Observer: CategoryObserver (via #[ObservedBy]) Traity: HasUuids, TraitCategory

Pola modelu
Pole Typ Opis Wymagane (store) Edytowalne (update)
id UUID Identyfikator Auto Nie
scope string (enum) Typ kategorii: item lub activity Tak Nie
name string Nazwa (max 255, unikalna w obrębie scope) Tak Tak
desc string? Opis (max 3000) Nie Tak
lucide_icon string Identyfikator ikony Lucide (max 255) Tak Tak
color string Kolor HEX (nie czarny) Tak Tak
is_active bool Czy aktywna (default: true) Tak Nie (tylko przez changeState)
extra_params array? Dane kontekstowe (np. lista modeli do reaktywacji)
created_at datetime Data utworzenia Auto Nie
updated_at datetime Data aktualizacji Auto Nie
Atrybuty wyliczane
Atrybut Typ Opis Klasa
total_relations int Liczba przypisanych modeli (Items lub Activities, zależnie od scope) GetTotalRelationsAttr
is_in_use bool Czy total_relations > 0 — (accessor w modelu)
can_be_edit bool Czy można edytować Bazowa klasa modelu
scope_lang string Przetłumaczona nazwa typu CategoryResource

Zliczanie relacji zależne od scope

Atrybut total_relations zlicza items dla scope=item lub relActivities dla scope=activity. Używa loadCount z lazy-loading jeśli count nie został wcześniej załadowany.

Relacje
Relacja Typ Model docelowy Opis
items() HasMany Item Obiekty przypisane do kategorii
relActivities() HasMany Activity Aktywności przypisane do kategorii
PublicFilters (dostępne do filtrowania przez API)
Pole Opis
id UUID kategorii
scope Typ kategorii
name Nazwa
is_active Czy aktywna
Casty
'id' => 'string',
'scope' => 'string',
'name' => 'string',
'desc' => 'string',
'lucide_icon' => 'string',
'color' => 'string',
'is_active' => 'boolean',
'extra_params' => 'array',
'created_at' => 'datetime:Y-m-d H:i:s',
'updated_at' => 'datetime:Y-m-d H:i:s',

2.2 Diagram relacji

erDiagram
    Category ||--o{ Item : "items (scope=item)"
    Category ||--o{ Activity : "relActivities (scope=activity)"

    Category {
        uuid id PK
        enum scope "item | activity"
        string name
        text desc
        string lucide_icon
        string color
        boolean is_active
        timestamp created_at
        timestamp updated_at
    }

    Item {
        uuid id PK
        uuid category_id FK
        string name
        boolean is_active
    }

    Activity {
        uuid id PK
        uuid category_id FK
        string name
        boolean is_active
    }

2.3 Struktura tabeli w bazie danych

Migracja: database/migrations/0001_01_01_000012_create_categories.php

Kolumna Typ Nullable Default Opis
id UUID Nie Auto (HasUuids) Klucz główny
scope ENUM(item, activity) Nie Typ kategorii
name VARCHAR(255) Nie Nazwa kategorii
desc TEXT Tak NULL Opis kategorii
lucide_icon VARCHAR(255) Nie Identyfikator ikony Lucide
color VARCHAR(255) Nie Kolor HEX
is_active BOOLEAN Nie true Czy aktywna
created_at TIMESTAMP Tak Auto Data utworzenia
updated_at TIMESTAMP Tak Auto Data aktualizacji

Indeksy:

Nazwa Kolumny Typ
categories_name_index name INDEX
idx_categories_org_scope_active scope, is_active INDEX (złożony)

3. Endpointy API

3.1 Endpointy publiczne (bez autoryzacji)

Plik routów: routes/api/noAuth/category.php Prefix: /no-auth/categories

GET /no-auth/categories

  • Opis: Lista kategorii (publiczna — bez pola can)
  • Autoryzacja: Brak (dostępne bez logowania)
  • Query Parameters:
Parametr Typ Opis
filter[id] UUID Filtruj po ID
filter[scope] string Filtruj po typie: item lub activity
filter[is_active] boolean Filtruj po statusie aktywności
per_page int Liczba wyników na stronę
page int Numer strony
  • Response (sukces, 200):
    {
      "data": [
        {
          "id": "9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
          "scope": "item",
          "scope_lang": "Item",
          "name": "Sale konferencyjne",
          "desc": "Sale do spotkań i konferencji",
          "lucide_icon": "door-open",
          "color": "#3B82F6",
          "is_active": true,
          "can_be_edit": true,
          "created_at": "2026-01-15 10:30:00",
          "updated_at": "2026-01-15 10:30:00",
          "total_relations": 5,
          "is_in_use": true
        }
      ],
      "links": { "...": "paginacja" },
      "meta": { "...": "metadane" }
    }
    

Pole can ukryte

W odpowiedzi publicznej (no-auth) pole can z uprawnieniami jest usuwane przez reduceResourceForNoAuth().

GET /no-auth/categories/{categoryId}

  • Opis: Szczegóły pojedynczej kategorii (publiczne)
  • Autoryzacja: Brak
  • Parametry URL: categoryId (UUID)
  • Response (sukces, 200):
    {
      "data": {
        "id": "9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
        "scope": "item",
        "scope_lang": "Item",
        "name": "Sale konferencyjne",
        "desc": "Sale do spotkań i konferencji",
        "lucide_icon": "door-open",
        "color": "#3B82F6",
        "is_active": true,
        "can_be_edit": true,
        "created_at": "2026-01-15 10:30:00",
        "updated_at": "2026-01-15 10:30:00",
        "total_relations": 5,
        "is_in_use": true
      }
    }
    
  • Response (błąd, 404): Kategoria nie znaleziona (NotFoundModelException)

3.2 Endpointy dla zalogowanych użytkowników

Plik routów: routes/api/auth/category.php Prefix: /categories Middleware: auth:api + globalne middleware (patrz architektura)

GET /categories

  • Opis: Lista kategorii (z polem can z uprawnieniami)
  • Autoryzacja: Dowolny zalogowany użytkownik
  • Form Request: IndexCategoryControllerRequest
  • Query Parameters: Identyczne jak endpoint publiczny
  • Response (sukces, 200):
    {
      "data": [
        {
          "id": "9e1a2b3c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
          "scope": "item",
          "scope_lang": "Item",
          "name": "Sale konferencyjne",
          "desc": "Sale do spotkań i konferencji",
          "lucide_icon": "door-open",
          "color": "#3B82F6",
          "is_active": true,
          "can_be_edit": true,
          "created_at": "2026-01-15 10:30:00",
          "updated_at": "2026-01-15 10:30:00",
          "total_relations": 5,
          "is_in_use": true,
          "can": {
            "view": true,
            "create": false,
            "update": false
          }
        }
      ]
    }
    

GET /categories/{categoryId}

  • Opis: Szczegóły kategorii (z uprawnieniami)
  • Autoryzacja: Dowolny zalogowany użytkownik
  • Parametry URL: categoryId (UUID)
  • Response: Jak wyżej, pojedynczy obiekt

POST /categories

  • Opis: Tworzenie nowej kategorii
  • Autoryzacja: Admin lub Supervisor (middleware role:admin|supervisor + CategoryPolicy::create)
  • Form Request: StoreCategoryControllerRequest
  • Body (Request):
    {
      "scope": "item",
      "name": "Sale konferencyjne",
      "desc": "Sale do spotkań i konferencji",
      "lucide_icon": "door-open",
      "color": "#3B82F6",
      "is_active": true
    }
    
Pole Typ Reguły walidacji
scope string required\|string\|max:255\|in:item,activity
name string required\|string\|max:255 + UniqueScopeCategoryNameRule
desc string? nullable\|string\|max:3000
lucide_icon string required\|string\|max:255
color string required\|regex:HEX\|not_in:#000,#000000
is_active bool required\|boolean
  • Response (sukces, 200):
    {
      "message": "The Category has been successfully added",
      "status": 200
    }
    
  • Response (błąd, 422): Błąd walidacji (duplikat nazwy, nieprawidłowy scope, czarny kolor)
  • Response (błąd, 403): Brak uprawnień

PATCH /categories/{categoryId}

  • Opis: Aktualizacja istniejącej kategorii
  • Autoryzacja: Admin lub Supervisor (middleware role:admin|supervisor + CategoryPolicy::update)
  • Form Request: UpdateCategoryControllerRequest
  • Parametry URL: categoryId (UUID)
  • Body (Request):
    {
      "name": "Sale konferencyjne — duże",
      "desc": "Sale na 50+ osób",
      "lucide_icon": "door-open",
      "color": "#2563EB"
    }
    
Pole Typ Reguły walidacji
name string? sometimes\|nullable\|string\|max:255 + UniqueScopeCategoryNameRule (z excludeId)
desc string? sometimes\|nullable\|string\|max:3000
lucide_icon string? sometimes\|nullable\|string\|max:255
color string? sometimes\|nullable\|regex:HEX\|not_in:#000,#000000

Ograniczenia aktualizacji

Przy aktualizacji nie można zmienić pól scope ani is_active. Zmiana stanu aktywności odbywa się wyłącznie przez endpoint changeState.

  • Response (sukces, 200):
    {
      "message": "The Category has been successfully updated",
      "status": 200
    }
    
  • Response (błąd, 404): Kategoria nie znaleziona (NotFoundModelException)
  • Response (błąd, 422): Błąd walidacji
  • Response (błąd, 403): Brak uprawnień

PATCH /categories/{categoryId}/change-state

  • Opis: Zmiana stanu aktywności kategorii (aktywacja/dezaktywacja) z opcjonalną selektywną reaktywacją modeli
  • Autoryzacja: Admin lub Supervisor (middleware role:admin|supervisor + CategoryPolicy::update)
  • Form Request: ChangeStateCategoryControllerRequest
  • Parametry URL: categoryId (UUID)
  • Body (Request):
    {
      "models": [
        "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "b2c3d4e5-f6a7-8901-bcde-f12345678901"
      ]
    }
    
Pole Typ Reguły walidacji
models array? sometimes\|nullable\|array
models.* UUID required\|uuid + ModelBelongsToCategoryRule
  • Response (sukces, 200): Zmiana stanu z kaskadowym efektem na powiązane modele
  • Response (błąd, 422): Model nie należy do tej kategorii
  • Response (błąd, 403): Brak uprawnień

4. Logika biznesowa

4.1 Główne procesy

Tworzenie kategorii

flowchart LR
    A[Request POST /categories] --> B[StoreCategoryControllerRequest]
    B --> C[Walidacja: scope, name, icon, color]
    C --> D[CategoryPolicy::create]
    D --> E[CreateCategory → Category::create]
    E --> F[Response 200: sukces]
  1. Administrator wysyła żądanie z danymi kategorii
  2. StoreCategoryControllerRequest waliduje dane (scope, unikalna nazwa, format koloru)
  3. CategoryPolicy::create sprawdza uprawnienia (isSupervisor)
  4. StoreCategoryController deleguje do CreateCategory action
  5. Category::create() tworzy rekord w bazie

Aktualizacja kategorii

flowchart LR
    A[Request PATCH /categories/id] --> B[UpdateCategoryControllerRequest]
    B --> C[Walidacja: name unique, color format]
    C --> D[CategoryPolicy::update]
    D --> E[fillModelData → update]
    E --> F[CategoryObserver::updated]
    F --> G{is_active changed?}
    G -->|Tak| H[Kaskadowa zmiana stanu]
    G -->|Nie| I[Response 200: sukces]
    H --> I

Zmiana stanu kategorii (change-state)

flowchart TD
    A[PATCH /categories/id/change-state] --> B[Walidacja models]
    B --> C[changeStateXController]
    C --> D[Toggle is_active]
    D --> E[CategoryObserver::updated]
    E --> F{is_active zmienione?}
    F -->|Nie| G[Koniec]
    F -->|Tak| H{Aktywacja czy dezaktywacja?}
    H -->|Dezaktywacja| I[Dezaktywuj powiązane modele]
    H -->|Aktywacja| J[Odczytaj models z ResolveModelContext]
    J --> K[Aktywuj wybrane modele]
    I --> L{total_relations > 50?}
    K --> L
    L -->|Tak| M[Async: ChangeStateAllObjectModelsJob]
    L -->|Nie| N[Sync: changeStateAllObjectModels]

4.2 Reguły biznesowe

  1. Kategoria musi mieć typ (scope): item lub activity
  2. Nazwa kategorii musi być unikalna w obrębie scope — nie mogą istnieć dwie kategorie "Sale" w typie "item"
  3. Typ (scope) nie może być zmieniony po utworzeniu kategorii
  4. Pole is_active nie może być zmienione przez endpoint update — tylko przez changeState
  5. Kolor musi być w formacie HEX: #RGB, #RGBA, #RRGGBB lub #RRGGBBAA
  6. Kolor czarny (#000, #000000) jest zabroniony ze względu na czytelność interfejsu
  7. Dezaktywacja kategorii kaskadowo dezaktywuje wszystkie przypisane obiekty (Items) lub aktywności (Activities)
  8. Aktywacja kategorii umożliwia selektywną reaktywację — podanie listy models (UUID) określa które modele reaktywować
  9. Modele podane w changeState.models muszą należeć do tej kategorii (ModelBelongsToCategoryRule)
  10. Przy tworzeniu/edycji Item lub Activity system waliduje czy podana kategoria jest aktywna i ma odpowiedni scope (ActiveCategoryRule)
  11. Jeśli kategoria ma więcej niż 50 powiązanych modeli, kaskadowa zmiana stanu wykonywana jest asynchronicznie (kolejka domain)
  12. Endpointy publiczne (no-auth) zwracają dane bez pola can (uprawnienia)

4.3 Walidacje

Tworzenie kategorii (StoreCategoryControllerRequest)

Pole Reguły
scope required\|string\|max:255\|in:item,activity
name required\|string\|max:255 + UniqueScopeCategoryNameRule($scope)
desc nullable\|string\|max:3000
lucide_icon required\|string\|max:255
color required\|regex:/^#(?:[0-9A-Fa-f]{3}\|[0-9A-Fa-f]{4}\|[0-9A-Fa-f]{6}\|[0-9A-Fa-f]{8})$/\|not_in:#000,#000000
is_active required\|boolean

Aktualizacja kategorii (UpdateCategoryControllerRequest)

Pole Reguły
name sometimes\|nullable\|string\|max:255 + UniqueScopeCategoryNameRule($scope, $excludeId)
desc sometimes\|nullable\|string\|max:3000
lucide_icon sometimes\|nullable\|string\|max:255
color sometimes\|nullable\|regex:HEX\|not_in:#000,#000000

Zmiana stanu (ChangeStateCategoryControllerRequest)

Pole Reguły
models sometimes\|nullable\|array
models.* required\|uuid + ModelBelongsToCategoryRule($categoryId)

Lista kategorii (IndexCategoryControllerRequest)

Używa paginationDefaultRequestRules() + publicFilterRequestRules(Category::class).


5. Autoryzacja i uprawnienia

Middleware

Kontroler CategoryController stosuje middleware role:admin|supervisor dla metod: store, update, changeState.

Policy (CategoryPolicy)

Plik: app/Domain/Category/Policy/CategoryPolicy.php Bazowa klasa: BasePolicy

Metoda Logika Opis
view(User, Category) return true Każdy zalogowany użytkownik może przeglądać
create(User) isSupervisor($user) Tylko Supervisor lub wyżej
update(User, Category) isSupervisor($user) Tylko Supervisor lub wyżej

Macierz uprawnień

Rola Podgląd Tworzenie Edycja Zmiana stanu
Administrator Tak Tak Tak Tak
Supervisor Tak Tak Tak Tak
Employee Tak Nie Nie Nie
Klient (no-auth) Publiczne Nie Nie Nie

6. Eventy i efekty uboczne

CategoryObserver

Plik: app/Domain/Category/Observer/CategoryObserver.php Rejestracja: Atrybut #[ObservedBy(CategoryObserver::class)] na modelu

Zdarzenie Klasa akcji Opis
updated UpdatedCategoryObserverUpdatedIsActiveCategoryObserver Kaskadowa zmiana stanu powiązanych modeli gdy is_active się zmieni

Łańcuch wywołań observera

CategoryObserver::updated()
  └─ UpdatedCategoryObserver::__invoke()
       └─ UpdatedIsActiveCategoryObserver::__invoke()
            ├─ Sprawdza: $model->wasChanged('is_active')
            ├─ (aktywacja) odczytuje models z ResolveModelContext
            ├─ (> 50 modeli) → ChangeStateAllObjectModelsJob::dispatch() (kolejka: domain)
            └─ (≤ 50 modeli) → changeStateAllObjectModels() (sync)

Kaskadowa dezaktywacja

flowchart TD
    A[Dezaktywacja kategorii] --> B{total_relations > 50?}
    B -->|Tak| C[Async: ChangeStateAllObjectModelsJob]
    B -->|Nie| D[Sync: changeStateAllObjectModels]
    C --> E[Dezaktywuj Items/Activities z category_id]
    D --> E

Kaskadowa aktywacja (selektywna)

flowchart TD
    A[Aktywacja kategorii] --> B{Podano listę models?}
    B -->|Tak| C[Odczytaj z ResolveModelContext]
    C --> D[Aktywuj tylko wybrane modele]
    B -->|Nie| E[Brak selektywnej reaktywacji]
    D --> F{total_relations > 50?}
    F -->|Tak| G[Async job]
    F -->|Nie| H[Sync]

Job: ChangeStateAllObjectModelsJob

Plik: app/Support/Job/ChangeStateAllObjectModelsJob.php Kolejka: domain (SupportEnumService::ON_QUEUE_DOMAIN) Cel: Masowa zmiana stanu is_active dla powiązanych modeli (Items/Activities) gdy kategoria ma > 50 relacji


7. Notyfikacje

Domena Kategorii nie generuje własnych notyfikacji. Kaskadowa zmiana stanu modeli (Items/Activities) może jednak pośrednio triggerować notyfikacje w tych domenach, jeśli ich observery je obsługują.


8. Powiązania z innymi domenami

Domena Powiązanie Typ relacji Opis
Zasoby/Obiekty (Item) Item → Category BelongsTo Obiekty są przypisane do kategorii typu item
Aktywności (Activity) Activity → Category BelongsTo Zajęcia są przypisane do kategorii typu activity

Odwołania w innych domenach

Domena Plik Użycie
Item ChangeCategoryItemControllerRequest Zmiana kategorii obiektu
Item StoreItemControllerRequest (via ActiveCategoryRule) Walidacja aktywnej kategorii przy tworzeniu
Activity ChangeCategoryActivityControllerRequest Zmiana kategorii aktywności
Activity StoreActivityControllerRequest (via ActiveCategoryRule) Walidacja aktywnej kategorii przy tworzeniu
Activity UpdateActivityControllerRequest (via ActiveCategoryRule) Walidacja aktywnej kategorii przy aktualizacji
Support ActiveCategoryRule Reguła walidacji sprawdzająca aktywność i scope kategorii

Walidacja w innych domenach

Przy tworzeniu/aktualizacji Item i Activity system waliduje czy podana kategoria jest aktywna i ma odpowiedni scope (ActiveCategoryRule). Nie można przypisać nieaktywnej kategorii ani kategorii o niewłaściwym typie.


9. Konfiguracja

Enum: CategoryEnumService

Plik: app/Domain/Category/Service/CategoryEnumService.php

Case Wartość Opis
CATEGORY_SCOPE_ITEM item Kategoria dla obiektów
CATEGORY_SCOPE_ACTIVITY activity Kategoria dla aktywności

Metody:

Metoda Zwraca Opis
getCategoryScopes() array Mapa scope → tłumaczenie (np. 'item' => 'Item')
getCategoryScopesKeys() array Lista kluczy scope: ['item', 'activity']

Typy kategorii

Typ Kod Przeznaczenie Relacja
Obiekty item Grupowanie sal, boisk, sprzętu items() → HasMany Item
Aktywności activity Grupowanie zajęć, kursów, warsztatów relActivities() → HasMany Activity

Rozdzielenie typów

Kategoria typu "item" nie może być przypisana do aktywności i odwrotnie. Nazwy kategorii muszą być unikalne w obrębie typu (UniqueScopeCategoryNameRule). Typ (scope) nie może być zmieniony po utworzeniu kategorii.

Walidacja koloru

Dozwolone formaty HEX

  • #RGB — np. #F00 (czerwony)
  • #RGBA — np. #F00F (czerwony, pełna przezroczystość)
  • #RRGGBB — np. #FF0000 (czerwony)
  • #RRGGBBAA — np. #FF0000FF (czerwony, pełna przezroczystość)

Zabronione kolory

Kolor czarny (#000, #000000) jest zabroniony ze względu na czytelność interfejsu.

Próg wydajności

Parametr Wartość Opis
Próg async (zmiana stanu) > 50 modeli Powyżej 50 powiązanych modeli, kaskadowa zmiana stanu wykonywana asynchronicznie
Kolejka domain SupportEnumService::ON_QUEUE_DOMAIN

10. Znane ograniczenia i TODO

Nr Opis Priorytet
1 Brak soft-delete — usunięta kategoria jest permanentna Niski
3 Brak walidacji czy ikona lucide_icon istnieje w bibliotece Lucide Niski
4 Brak endpointu DELETE /categories/{id} — kategorie można tylko dezaktywować Świadome ograniczenie
5 Pole extra_params nie jest w migracji jako kolumna — obsługiwane przez ResolveModelContext ⚠️ Do wyjaśnienia

Przykładowe kategorie

Dla obiektów (item)

Nazwa Ikona Kolor Przykłady
Sale konferencyjne Niebieski Sala A, Sala B, Sala Rady
Hale sportowe Zielony Hala MOSiR, Hala ZS1
Boiska Zielony Boisko Orlik, Kort tenisowy
Baseny Błękitny Basen olimpijski, Brodzik
Sprzęt Szary Namioty, Stoły, Krzesła

Dla aktywności (activity)

Nazwa Ikona Kolor Przykłady
Fitness Różowy Aerobik, Zumba, Pilates
Zajęcia dla dzieci Żółty Pływanie maluchy, Gimnastyka
Warsztaty Fioletowy Ceramika, Malarstwo
Kursy językowe Granatowy Angielski A1, Niemiecki B2

Reguły walidacyjne (szczegóły)

UniqueScopeCategoryNameRule

Plik: app/Domain/Category/Request/Rule/UniqueScopeCategoryNameRule.php

Sprawdza unikalność nazwy kategorii w obrębie danego scope. Przy aktualizacji wyklucza bieżącą kategorię z porównania ($ignoreId).

// Parametry konstruktora:
new UniqueScopeCategoryNameRule(string $scope, ?string $ignoreId = null)

ModelBelongsToCategoryRule

Plik: app/Domain/Category/Request/Rule/ModelBelongsToCategoryRule.php

Używana w changeState — waliduje czy podane UUID modeli rzeczywiście należą do tej kategorii. Sprawdza relację na podstawie scope:

  • item → sprawdza items() (HasMany Item)
  • activity → sprawdza relActivities() (HasMany Activity)

ActiveCategoryRule (Support)

Plik: app/Support/Action/Request/Rule/ActiveCategoryRule.php

Używana w domenach Item i Activity przy tworzeniu/aktualizacji — sprawdza czy kategoria istnieje, jest aktywna (is_active = true) i ma odpowiedni scope.

// Parametry konstruktora:
new ActiveCategoryRule(string $scope)

Struktura plików domeny

app/Domain/Category/
├── Action/
│   ├── Attr/
│   │   └── GetTotalRelationsAttr.php       # Zliczanie relacji (zależne od scope)
│   ├── Controller/
│   │   ├── IndexCategoryController.php      # Lista kategorii (Spatie QueryBuilder)
│   │   ├── ShowCategoryController.php       # Szczegóły kategorii
│   │   ├── StoreCategoryController.php      # Tworzenie kategorii
│   │   └── UpdateCategoryController.php     # Aktualizacja kategorii
│   └── Other/
│       ├── CreateCategory.php               # Category::create()
│       ├── UpdatedCategoryObserver.php      # Deleguje do IsActive observer
│       └── UpdatedIsActiveCategoryObserver.php # Kaskadowa zmiana stanu
├── Controller/
│   └── CategoryController.php               # Główny kontroler (index, show, store, update, changeState)
├── Model/
│   └── Category.php                         # Model Eloquent
├── Observer/
│   └── CategoryObserver.php                 # Observer: updated hook
├── Policy/
│   └── CategoryPolicy.php                   # Policy: view, create, update
├── Request/
│   ├── ChangeStateCategoryControllerRequest.php
│   ├── IndexCategoryControllerRequest.php
│   ├── Rule/
│   │   ├── ModelBelongsToCategoryRule.php    # Walidacja przynależności modelu
│   │   └── UniqueScopeCategoryNameRule.php   # Unikalna nazwa w scope
│   ├── StoreCategoryControllerRequest.php
│   └── UpdateCategoryControllerRequest.php
├── Resource/
│   └── CategoryResource.php                 # Transformacja do JSON
└── Service/
    ├── CategoryEnumService.php              # Enum scope: item/activity
    ├── TraitCategory.php                    # Logika biznesowa
    └── TraitControllerCategory.php          # Orchestracja kontrolera

Best practices

Wskazówki dla administratorów

  1. Planuj strukturę — przemyśl kategorie przed dodawaniem obiektów
  2. Unikaj duplikatów — system wymusza unikalność nazw w obrębie typu
  3. Używaj ikon — ikony Lucide pomagają wizualnie odróżnić kategorie
  4. Sezonowe zarządzanie — dezaktywuj kategorie zamiast usuwać
  5. Selektywna reaktywacja — przy aktywacji podaj listę modeli do przywrócenia

Historia zmian

Data Typ Opis
26 lut 2026 Aktualizacja dokumentacji Restrukturyzacja wg szablonu — dodano: pełną strukturę tabeli DB z indeksami (2.3), diagram ER relacji (2.2), kompletne opisy endpointów API z przykładami JSON request/response (sekcja 3), numerowaną listę reguł biznesowych (4.2), sekcję testów (10), sekcję znanych ograniczeń (11), pełne opisy reguł walidacyjnych, macierz uprawnień. Zachowano i rozszerzono istniejące treści.
15 lut 2026 Aktualizacja dokumentacji Kompleksowa aktualizacja — dodano sekcje: API Endpoints, Filtry, Observery, Reguły walidacyjne, Struktura plików.