# Beállítások — tenant-konfiguráció

**Dokumentum-típus:** feature-spec (admin felület)
**Modul:** Manager felület → Beállítások (kategóriák, csoportok, általános
tenant-alapadatok)
**Státusz:** munkadokumentum — átnézésre kész
**Verzió:** v1.0
**Dátum:** 2026.05.20
**Cél olvasó:** senior fejlesztő + Claude Code
**Érintett URL-ek:** `/beallitasok` (redirect), `/beallitasok/kategoriak`,
`/beallitasok/csoportok`, `/beallitasok/altalanos`

> **Mire való ez a fájl.** A Beállítások modul három aloldalának
> (`/beallitasok/kategoriak`, `/beallitasok/csoportok`,
> `/beallitasok/altalanos`) a fejlesztői specifikációja. A funkcionális tervet
> (`50_konfig_v2.md`) **fejlesztői mélységgel tölti ki**: domain-modell
> kiegészítések, API-szerződések, mezők, állapotgépek (ahol vannak),
> validáció, jogosultság, acceptance criteriumok.
>
> **Mi NEM ez.** Nem írja újra a funkcionális tervet — arra hivatkozik. Nem
> ismétli a `BaseController` / `TableStateConfig` / `[validationForm]`
> standard mintáit — azokra építve csak az eltéréseket dokumentálja
> (`01_kozos_mintak.md` 6.4 elv).
>
> **A negyedik aloldal — Felhasználók.** A `/beallitasok/felhasznalok` aloldal
> feature-specje a `20_felhasznalokezeles.md`-ben van; ez a feature-spec azt
> nem ismétli, csak hivatkozza.
>
> **A `40_riport.md` SD-57 fogyasztói várakozása.** A riport-feature-spec egy
> olvasó-végpontot várt el a tenant aktuális riport-címzett-listájához
> (`40_riport.md` 8.2 feltételezés 3). Ez a feature-spec **formálisan
> teljesíti** — új végpont nélkül, a `GET /v1/weekly-report-recipients`
> standard CRUD listájával (4.3.3, 4.5, AC-R1.3).

---

## 0. Funkcionális alap

### 0.1 Mely funkcionális dokumentumok fedik ezt a feature-t

| Dokumentum | Szakasz | Mit ad |
|---|---|---|
| `50_konfig_v2.md` | 1.–7. (teljes) | A Beállítások lenyíló almenü négy aloldala (Kategóriák, Csoportok, Felhasználók, Általános); a kategória-fa kétszintű terméktulajdon-határa, a csoport mint címke+taglista, a felhasználó-CRUD pilot-szintje, az "Általános" tenant-alapadatok és a heti riport címzett-listája |
| `00_architektura_v4.md` | 3.2 (főmenü), 3.3 (URL-struktúra), 7.2 (fejléc), 8.2/8.4 (kategória-kérdések) | A Beállítások mint lenyíló almenü a meglévő admin-mintázatban; a `Tenant.name` a fejlécben |
| `05_jogosultsagok_v2.md` | 2.8 | A teljes Beállítások **vezető-only** (`tenant_manager`); az "Új gyökér-kategória **termékesített készletből**" mint külön akció |
| `90_sitemap_v3.md` | 2.6 | A `/beallitasok/*` négy aloldal nézet-leltára: `/kategoriak`, `/csoportok`, `/felhasznalok`, `/altalanos` |
| `40_riport.md` | 3.3 (R-2/R-3), 4.4 ("Címzettek" szekció), 8.2/3 (feltételezés) | A riport-feature-spec konkrét fogyasztói elvárása: olvasó-végpont a tenant aktuális címzett-listájához (SD-57) |
| `00_domain_model.md` | 2.1 (`Category`), 2.2 (`Group`), 3.2 (`Tenant`) | A három fő érintett entitás mezőtáblája — két új mezőcsoport (logó, kontakt, riport-címzettek) hiányzik a `Tenant`-ról; lásd 2. szakasz |
| `01_kozos_mintak.md` | 1.6 (`Tenant` regisztrum-séma, SD-4), 1.3 (Tenant DB tartalma), 4.2 (kategória-katalógus) | A `Tenant` Core DB-ben; a `Category`/`Group` Tenant DB-ben |
| `20_felhasznalokezeles.md` | 1.–8. (teljes) | A felhasználó-kezelő aloldal (`/beallitasok/felhasznalok`) már önálló feature-specként kifejtésre került — ezt a feature-spec nem ismétli, csak hivatkozza |

### 0.2 Mely kanonikus döntések (K-xxx) érintettek

- **K-007** — egységes Urbino-brand: a tenant a nevét és logóját állítja,
  **nem** brandet. Az "Általános" határa innen jön.
- **K-008** — multi-tenant fegyelem: szabad szöveges új gyökér-kategória nincs;
  a gyökér terméktulajdon.
- **K-016** — alapszint, nem konfigurációs motor: nincs SLA-szerkesztő, nincs
  finomszemcsés jogosultság, nincs csoport-hierarchia.
- **K-024** — super-admin elválasztása: tenant-billing, tenant-státusz,
  feature-flag NEM itt; az Urbino-admin felület hatásköre.
- **K-025** — implicit unió-modell: több szerepkör egy felhasználón. (A
  felhasználó-feature dolga.)
- **K-031** — kategória-prioritás-határidő keret: a kategória-fa felhasználása.
- **K-036** — kategória-fa kétszintű; gyökér = terméktulajdon, alkategória =
  tenant-tulajdon. **Ez a feature érvényesíti.**
- **K-037** — a tenant tartalmat konfigurál, nem brandet. **Ez a feature
  érvényesíti.**

### 0.3 Mit döntött már el a funkcionális réteg

Hivatkozással, nem ismétlésként:

1. **Négy aloldal lenyíló almenü alatt**, mind önálló URL —
   `50_konfig_v2.md` 1.1; `00_architektura_v4.md` 3.2.
2. **Vezető-only** — `05_jogosultsagok_v2.md` 2.8.
3. **Kategória-fa kétszintű, gyökér terméktulajdon** — `50_konfig_v2.md` 2.2;
   K-036.
4. **Kategóriát törölni nem lehet, csak deaktiválni** — `50_konfig_v2.md` 2.4;
   `00_domain_model.md` 2.1.
5. **Csoport = címke + taglista**, csoport-hierarchia nincs —
   `50_konfig_v2.md` 3.2.
6. **Tenant-kontakt egyetlen forrásból** (manager Beállítások → polgári app
   olvassa) — `50_konfig_v2.md` 6.3 (javaslat, modulközi).
7. **Riport-címzett ≠ szerepkör** — `50_konfig_v2.md` 5.2; `40_riport.md` 4.4.
8. **Riport-feature konkrét várakozása:** olvasó-végpont a `tenant_manager`
   számára a tenant aktuális címzett-listájához, a `/riportok/details/<id>`
   "Címzettek" szekciójához (SD-57) és a `resend` megerősítő párbeszédhez.
9. **Csoport-tagság a csoport-adatlapról szerkeszthető**, a
   felhasználó-adatlapon olvasásra — `50_konfig_v2.md` 6.2;
   `20_felhasznalokezeles.md` 4.
10. **Az "Új gyökér-kategória termékesített készletből"** mint külön akció —
    `05_jogosultsagok_v2.md` 2.8.

### 0.4 Mit tölt ki EZ a specifikáció

A funkcionális rétegen a következő fejlesztői mélységeket adja hozzá:

1. **A `Category` CRUD eltérései a `BaseController`-mintától** — gyökér-tulajdon
   érvényesítése (csak default-katalógusból új gyökér), kétszintű fa-mélység
   invariáns, delete-guard, inaktiválás vs. törlés API-szintű elkülönítése, a
   `Category`-reorder szülő-szinten belül.
2. **A `Group` CRUD eltérései** — taglista mint `[ManyToManyConnection]`,
   delete-guard (a `Category` mintájának kiterjesztése).
3. **A `Tenant`-alapadatok új mezői** (logó, kontakt) — a `00_domain_model.md`
   `Tenant` entitásába visszavezetve; új entitás (`WeeklyReportRecipient`) a
   riport-címzett-listához.
4. **Az olvasó-végpont a riport-címzett-listához** — a `40_riport.md` 8.2 (3)
   feltételezésének formális kielégítése: route, response-DTO, jogosultság.
5. **A logó-feltöltés mechanikája** — dedikált multipart-végpont (új minta:
   "egyedi-fájl-mező").
6. **A három aloldal `TableStateConfig`/űrlap vázlatai** és a négy URL
   route-szintű leképezése.
7. **i18n kulcsok, hibakódok** a három aloldalra.
8. **Acceptance criteria** Given/When/Then formában, blokkokra bontva.
9. **Megtartott hiányok és nyitott kérdések** explicit listája.

---

## 1. Cél és hatókör

A Beállítások modul a tenant-szintű konfigurációs felület: a vezető (Béla, a
beolvadt tenant-admin) itt kezeli a kategória-fát, a terepi munkacsoportokat,
a saját tenantja alapadatait (név, logó, kontakt) és a heti PDF-riport
címzett-listáját. A felhasználók kezelése (`/beallitasok/felhasznalok`)
külön feature-specben (`20_felhasznalokezeles.md`).

**Üzleti érték:** a vezető önállósága — a város a saját tenantját kezeli
(K-008), kategóriát rendez, csoportot szervez, riport-címzettet vesz fel,
anélkül hogy az Urbino csapatához kellene fordulnia. A K-007 egységes brand
mellett a tenant tartalom-paramétereket állít (név, logó, kontakt), nem
brand-paramétereket (téma, szín, app-név) — ezt a feature érvényesíti
(SD-58, SD-67).

**Hatókörön kívül ennél a feature-nél:**

- Felhasználó-kezelés (`/beallitasok/felhasznalok`) — `20_felhasznalokezeles.md`.
- Tenant-provisioning, új tenant felvétele — `urbino_admin`-hatáskör (K-024).
- A `Tenant.status` állapotgép-átmenetei (`Setup → Active → Suspended →
  Archived`) — `urbino_admin`-hatáskör.
- A `Tenant.timeZone`, `code`, `displayPrefix` szerkesztése — pilotra
  `urbino_admin`-hatáskör.
- Csoport-hierarchia, csoport-szintű jogosultság, csoport-szintű SLA — K-016.
- Konfigurálható munkafolyamatok, SLA-mátrix — K-016.
- Default-katalógus futás közbeni propagálása élő tenantokba —
  `01_kozos_mintak.md` 4.4 elhalasztva.
- A polgári app olvasói felülete a tenant-kontaktra — polgári adatigény-spec
  hatásköre (NY-K2).

---

## 2. Domain-modell

A feature két helyen bővíti a domain-modellt: a `Tenant` entitás négy új
mezőt kap, és egy új entitás (`WeeklyReportRecipient`) keletkezik a Tenant
DB-ben. A `Category` és `Group` entitások mezőtáblája változatlan — a
feature rajtuk csak végpont-szintű eltéréseket vezet be.

### 2.1 A `Tenant` entitás kibővítése (SD-58)

**DB-szint:** Core DB
**Származik:** `AuditableEntity`

A meglévő hét mezőhöz (`id`, `code`, `name`, `displayPrefix`,
`dbConnectionRef`, `status`, `timeZone`) négy új mező csatlakozik:

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `logoFileRef` | `string` | O | A tenant logójának S3-objektum-hivatkozása (az `Attachment.fileRef`-mintával konzisztens, SD-15). `null`, ha nincs feltöltve. Max 500 karakter |
| `contactAddress` | `string` | O | A tenant fizikai címe (pl. `"8220 Balatonalmádi, Széchenyi sétány 1."`). Max 300 karakter |
| `contactPhone` | `string` | O | Kapcsolattartási telefonszám. Max 50 karakter. Formátum-validáció nincs |
| `contactEmail` | `string` | O | Nyilvános kapcsolattartási e-mail. Max 200 karakter, e-mail formátum-validációval |

Mind a négy mező **opcionális** — a tenant pilotba lépését nem blokkolja a
kontakt-adat üressége. A `Tenant.name` változatlan kötelező; a logó
kifejezetten a fejlécben (`00_architektura_v4.md` 7.2) és a heti
PDF-fejlécében (`40_riport.md` 4.2) megjelenő képet jelenti.

> **A `contactEmail` és a `WeeklyReportRecipient.email` szétválasztása.** A
> `Tenant.contactEmail` a tenant *nyilvános* kapcsolattartási címe — a
> polgári app "Ügyfélszolgálat" szekciójának forrása. A
> `WeeklyReportRecipient.email` (2.2) a heti riport *belső* kézbesítési
> listája — polgármester, alpolgármester, hivatali titkárság. A kettő
> tartalmilag átfedhet, de céljában különböző; az adatmodellben különálló
> mezők.

> **A logó tárolási elve.** A `logoFileRef` egy S3-objektum-hivatkozás (mint
> az `Attachment.fileRef`, `00_domain_model.md` 1.4), **nem** `Attachment`-
> rekord. A polgári app és a manager felület ugyanazt a presigned-URL-mintát
> használja a megjelenítéshez (SD-15 konzisztencia) — de a `Tenant.logoFileRef`
> egy mező, nem entitás-hivatkozás.

### 2.2 Új entitás: `WeeklyReportRecipient` (SD-59)

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó döntések:** SD-59 (az entitás bevezetése), SD-67 (a
`/beallitasok/altalanos` aloldal beágyazott listája), `40_riport.md` SD-57 (a
riport-adatlap és `resend` fogyasztja)

A `WeeklyReportRecipient` a tenant heti riport-címzett-listájának
rekord-szintű reprezentációja. Egy bejegyzés egy címzettet jelent (név +
e-mail), aki a heti PDF-riportot e-mailben megkapja (`40_riport.md` 3.6). A
lista pilotra tipikusan 1–3 elemű (`50_konfig_v2.md` 5.2).

A címzett **nem `User`** — nem lép be a manager felületre, csak e-mailt kap
(`50_konfig_v2.md` 5.2, `40_riport_v1.md` 4.4). A polgármester
`WeeklyReportRecipient`-ként szerepel, nem `tenant_manager` szerepkörrel.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `name` | `string` | O | A címzett megjelenő neve (pl. `"Dr. Polgár Péter — polgármester"`). Max 200 karakter. Opcionális — ha üres, a felület és a `resend`-megerősítő-dialógus az `email`-t mutatja |
| `email` | `string` | K | A címzett e-mail címe. Max 200 karakter, e-mail formátum-validációval. **Egyedi a tenanton belül** |
| `isActive` | `bool` | K | Aktív-e (kap-e a következő heti riportból). **Default: `true`.** Inaktív címzett megmarad, de a generáló job nem küld neki |
| `createdAt`/`createdBy`/`updatedAt`/`updatedBy` | — | K | `AuditableEntity` automatikus — címzettenkénti audit |

> **Az induló állapot.** A pilot-tenant felvételekor a vezető (Béla)
> **nem kerül be automatikusan** `WeeklyReportRecipient`-ként. A funkcionális
> réteg ("a vezető alapból rajta van", `50_konfig_v2.md` 5.2) ezt
> UI-megfogalmazás formájában oldja meg: az első megnyitáskor informatív
> kártya mutatja, hogy a lista üres, és a vezető egy kattintással felveheti
> magát. A `User.email` automatikus ajánlása nincs — a vezető e-mailje nem
> feltétlenül a kívánt riport-cím (külön hivatali levelezőlista lehet).

> **A lista mint olvasó-forrás a riport-feature-nek.** A `40_riport.md` 4.4
> ("Címzettek" szekció a riport-adatlapon) és a `resend`-megerősítő-dialógus
> (`40_riport.md` SD-57) ezt az entitást olvassa a `GET
> /v1/weekly-report-recipients?isActive=true` standard CRUD végponttal.
> **Külön olvasó-végpont nem keletkezik** (SD-66).

> **Nincs FK-kapcsolat a `WeeklyReport`-tal.** A pilotra címzettenkénti
> kézbesítés-státusz nem létezik (`40_riport.md` SD-56 — összesített
> `deliveryStatus`). A generáló job a riport keletkezésekor lekéri az
> aktuális aktív címzetteket, kiküldi a PDF-et, és csak az összesített
> `deliveryStatus`-t és `recipientCount`-ot írja a `WeeklyReport`-ra. A
> `WeeklyReportRecipient` és a `WeeklyReport` közt **logikai fogyasztás van,
> nem FK** — entitás-térkép: 2.4.

### 2.3 Érintett entitások változatlanul

A `Category` (`00_domain_model.md` 2.1) és a `Group` (2.2) entitások
**mezőtáblája nem változik** — a feature-spec rajtuk csak végpont-szintű
eltéréseket vezet be (3.1, 3.2).

A `DefaultCategoryCatalog` (Core DB) entitás-megerősítés: a `00_domain_model.md`
4. blokkja a default-katalógust megemlíti, de **formális mezőtáblát nem ad
hozzá**. A `from-default`-végpont (3.1.4) igényli az alábbi mezőket: legalább
`id`, `name`, `iconRef`, `parentId`. **Megtartott hiány — NY-K1** (8.4).

### 2.4 Entitás-térkép frissítése (`00_domain_model.md` 5.1)

A meglévő mermaid-diagram két helyen frissül: a `Tenant`-mezőbővítés (SD-58)
**nem érinti a kapcsolat-vonalakat**, csak új entitás-csomópont van:
`WeeklyReportRecipient` a Tenant DB-szekcióban.

```mermaid
erDiagram
    %% ===== Core DB =====
    User ||--o{ UserTenantRole : "szerepkörök"
    Tenant ||--o{ UserTenantRole : "tagok"
    User ||--o{ UserInvitation : "meghívók"
    Tenant ||--o{ UserInvitation : "meghívó-kontextus"
    DefaultCategoryCatalog ||--o{ DefaultCategoryCatalog : "parentId-fa"

    %% ===== Tenant DB =====
    Ticket ||--o{ Attachment : "csatolmányok"
    Ticket ||--o{ ActivityLog : "esemeny-naplo"
    Ticket ||--o{ TicketNote : "belso-jegyzetek"
    Ticket }o--|| Category : "categoryId"
    Ticket }o--o| Category : "citizenSuggestedCategoryId"
    Ticket }o--o| Group : "assignedGroupId"
    Ticket }o--o| TenantUser : "assignedUserId"
    Ticket }o--o| Ticket : "originalTicketId-duplikacio"
    Category ||--o{ Category : "parentId-fa"
    Group }o--o{ TenantUser : "tagsag-n-n"
    ActivityLog }o--o| TenantUser : "actorId"
    TicketNote }o--|| TenantUser : "authorId"
    WeeklyReportRecipient }|..|{ WeeklyReport : "logikai-fogyasztas-nem-FK"

    %% ===== Core - Tenant szinkron (NEM FK) =====
    User ||--o{ TenantUser : "szinkron-replikacio-SD-3"
    DefaultCategoryCatalog ||--o{ Category : "provisioning-masolas-SD-8"
```

**A `WeeklyReportRecipient }|..|{ WeeklyReport` él:** "logikai sokat-sokhoz,
opcionális, FK nélkül". A `WeeklyReportRecipient`-eket a `WeeklyReport`
generálási folyamata olvassa fogyasztóként, de nem FK-kötés; a riport-
keletkezés pillanatában *pillanatfelvétel* megy az aktív címzetteknek
(`40_riport.md` SD-56). A jelölés (`..` szaggatott) hasonló a Core ↔ Tenant
szinkron-replikációhoz — emlékeztető, hogy ez nem hagyományos FK.

### 2.5 Állapotgép — nem releváns

Sem a `Category`, sem a `Group`, sem a `Tenant` (a vezető szempontjából),
sem a `WeeklyReportRecipient` **nem hordoz állapotgépet** a feature-spec
hatókörén belül. Az `isActive` egyszerű boolean mind a háromnál; a
`Tenant.status` állapotgépe (SD-5) `urbino_admin`-hatáskör — nem ennek a
feature-specnek a része. A `02_globalis_allapotgep.md`-t nem érinti.

### 2.6 Lookup-ok

A feature **két lookupot termel** és **egy speciális olvasó-végpontot
publikál**:

- **Termelt — `categories`**: a kategória-fa a polgári app
  bejelentés-flow-jához és az admin triage-dropdown-hoz. Standard
  `ILookupProvider` (`CLAUDE.md`). Az `isActive == false` rekordok nem
  kerülnek a lookupba; minden `Category`-CRUD-művelet a lookup-cache
  invalidálását eredményezi.
- **Termelt — `groups`**: a csoport-lista a triage felelős-választójához.
  Ugyanaz a minta; az `isActive == false` rekordok nem kerülnek a lookupba.
- **Speciális olvasó — `available-default-roots`**: nem általános lookup,
  hanem a kategória-importálás flow-jához használt cross-DB olvasó-végpont
  (3.1.5). Nem `/v1/lookup/{name}`-en, hanem dedikált végponton.

A `WeeklyReportRecipient` és a `Tenant`-alapadatok **nem termelnek lookupot**.

---

## 3. Szerver — API és logika

A négy aloldal API-szintű leírása. A standard részt egy sorban hivatkozzuk;
a hangsúly az **eltéréseken** van. A felhasználó-aloldal API-ja a
`20_felhasznalokezeles.md` 3. szakaszában — itt nem ismétlem.

### 3.1 Kategóriák — `/v1/categories` család

A `Category` a `BaseController<Category, CategoryDto, CategoryListDto,
CategoryListResponse>` mintát követi. **Öt eltérés és két új végpont**
különbözteti meg a feature-t a standard CRUD-tól.

#### 3.1.1 `GET /v1/categories` — lista

**Standard `BaseController` list**, offset-lapozott, szűrhető, rendezhető. A
lista a fa kétszintű szerkezetét **lapos formában** adja vissza — minden
`Category`-sor egy rekord, a `parentId`-val. A kliens építi össze a fát
megjelenítésre (5.2).

A `CategoryListDto` mezői:

| Mező | Forrás | Megjegyzés |
|---|---|---|
| `id` | `Category.id` | — |
| `parentId` | `Category.parentId` | `null` gyökérnél |
| `name` | `Category.name` | — |
| `iconRef` | `Category.iconRef` | — |
| `isActive` | `Category.isActive` | — |
| `sortOrder` | `Category.sortOrder` | — |
| `sourceCatalogId` | `Category.sourceCatalogId` | `null` a tenant-egyedi alkategóriákon |
| `ticketCount` | denormalizált | A nyitott (`status IN (New, Assigned, InProgress)`) `Ticket`-ek száma, amelyek közvetlenül ezt a kategóriát hivatkozzák |
| `hasActiveChildren` | denormalizált | Csak gyökereknél értelmes — van-e aktív alkategória |

> **Eltérés a mintától — denormalizált aggregátum-mezők.** A standard
> `BaseController` list-DTO csak az entitás-mezőket tükrözi. A `ticketCount`
> és `hasActiveChildren` aggregátumok — a kontroller `ListAsync`-jában külön
> lekérdezések adják. **Indok:** a kliensnek ne kelljen N kategóriához
> N `count`-hívást indítani csak azért, hogy lássa, melyiket lehet törölni.
> A `ticketCount` csak a nyitott `status`-okat számolja — a delete-guard
> szempontjából ez releváns, és gyorsabb a query.

**Default rendezés:** `parentId ASC NULLS FIRST, sortOrder ASC`.

**Szűrők:** `isActive` (kapcsolható), `parentId` (egy adott szülő alá tartozó
alkategóriák). Standard `FilterQuery`.

#### 3.1.2 `GET /v1/categories/{id}` — egy kategória

**Standard `BaseController` get.** A `CategoryDto` a list-DTO mezőin felül
nem hordoz extra mezőt. Eltérés-jelölés nincs.

#### 3.1.3 `POST /v1/categories` — alkategória létrehozása (SD-62)

> **Eltérés a mintától.** A standard `POST` az entitás teljes mezőkészletét
> fogadja. Itt a `POST` **csak alkategória létrehozására engedett** — a
> `parentId` kötelező, kitöltött, és gyökérre mutat. Gyökér-kategória
> létrehozása a `from-default`-végpontra korlátozódik (3.1.4) — K-008
> érvényesítése.

**Request — `CreateCategoryRequest`:**

| Mező | Típus | Köt. | Validáció |
|---|---|---|---|
| `parentId` | `long` | K | Létezik, gyökér-`Category` (`parentId == null`), aktív (`isActive == true`) |
| `name` | `string` | K | Max 100, nem üres / whitespace |
| `iconRef` | `string` | O | Max 50; a kulcs-lista nem szerver-oldali validált (frontend-oldali konstans) |

A `sortOrder`, `isActive`, `sourceCatalogId` mezőket a kérés nem fogadja:

- `sortOrder` — szerver-oldalon, a szülő alatti utolsó pozícióra automatikusan
- `isActive` — szerver-oldalon `true`-ra
- `sourceCatalogId` — szerver-oldalon `null` (tenant-egyedi alkategória)

**Hibakódok:**

| Status | `reason` | Mikor |
|---|---|---|
| `400` | `fieldErrors` | Validációs hiba |
| `409` | `root_must_come_from_default` | `parentId == null` — szabad szöveges gyökér tilos |
| `409` | `category_depth_exceeded` | `parentId` egy alkategória — három szintű fa tilos |
| `422` | `parent_inactive` | `parentId` inaktív gyökérre mutat |

**Response — `201 Created`** + a létrejött `CategoryDto`.

#### 3.1.4 `POST /v1/categories/from-default` — gyökér importálása (SD-62)

> **Eltérés a mintától — új végpont.** Nem-standard írás-művelet: a Core DB
> `DefaultCategoryCatalog`-jából importál egy gyökeret (és minden
> alkategóriáját) a tenant DB-be.

**Request — `ImportRootCategoryRequest`:**

| Mező | Típus | Köt. | Validáció |
|---|---|---|---|
| `defaultCategoryId` | `long` | K | Létezik Core `DefaultCategoryCatalog`-ban, gyökér (`parentId == null`) |

**A workflow lépései (egy tranzakcióban):**

1. **Default-feloldás.** A Core DB-ből a `defaultCategoryId` rekord és
   minden közvetlen leszármazottja lekérve.
2. **Idempotencia-ellenőrzés.** Ha a tenant DB-ben már létezik `Category`
   ezzel a `sourceCatalogId`-val → `409 default_category_already_imported`.
3. **Másolás.** A gyökér-rekord létrejön a tenant DB-ben: `parentId = null`,
   `name`, `iconRef`, `sortOrder` a Core-rekordból, `isActive = true`,
   `sourceCatalogId = <Core id>`. Minden alkategória ugyanezzel:
   `parentId = <új gyökér tenant-id>`, `sourceCatalogId` kitöltve.
4. **Sortolás.** Az importált gyökér a `max(sortOrder) + 1` pozícióra kerül
   a gyökerek között.

**Hibakódok:**

| Status | `reason` | Mikor |
|---|---|---|
| `404` | — | A `defaultCategoryId` nem létezik a Core-ban |
| `422` | `not_a_root_default` | A `defaultCategoryId` alkategória a Core-ban |
| `409` | `default_category_already_imported` | A tenant DB-ben már van ilyen `sourceCatalogId` |

**Response — `201 Created`** + az importált gyökér `CategoryDto`-ja, plusz
a leszármazottak listája a `CategoryDto.children` mezőben.

> **Eltérés a mintától.** A standard `POST` egy entitást ad vissza; ez a
> végpont a fát (gyökér + alkategóriák) — a kliens egyetlen UI-frissítéssel
> zárhat.

#### 3.1.5 `GET /v1/categories/available-default-roots` — importálható gyökerek (SD-62)

> **Eltérés a mintától — új olvasó-végpont, cross-DB.** A Core
> `DefaultCategoryCatalog` gyökerei közül azok, amelyekhez a tenant DB-ben
> még nincs `Category` rekord.

**Request:** nincs body, nincs query-paraméter. A `Tenant` header adja a
tenant-azonosítást.

**Response — `200`** + lista: `AvailableDefaultRootDto[]`

| Mező | Megjegyzés |
|---|---|
| `defaultCategoryId` | `DefaultCategoryCatalog.id` — a `from-default`-végpont input-mezője |
| `name` | A default gyökér neve |
| `iconRef` | A default gyökér ikonja |
| `childCount` | Hány alkategóriát tartalmaz a default — "ezzel együtt N alkategóriát is importálsz" |

> **Cross-DB olvasás — lokális elv-enyhítés.** A `01_kozos_mintak.md` 1.4
> SD-2 elve ("cross-DB join a működési útvonalon nincs") **a működési
> útvonalra** szól (lista, adatlap, Dashboard). Ez a végpont **ritka
> konfigurációs aktus**, vezető-only, nem része a bejelentés-folyamatnak. A
> két szekvenciális `SELECT` (Core: gyökerek; tenant: `sourceCatalogId`-
> listák) a pilot-volumenen elhanyagolható. A feature-spec ezt **lokálisan**
> dokumentálja — nem a `01_kozos_mintak.md` általános szabályának módosítása.
> Lásd nyitott kérdés 8.4 (NY-K1 mellett).

#### 3.1.6 `PUT /v1/categories/{id}` — szerkesztés

**Standard `BaseController` update**, egyetlen feature-megjegyzéssel: a
szerkeszthető mezők szűkítettek.

**Request — `UpdateCategoryRequest`:**

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `name` | `string` | K | Max 100, nem üres |
| `iconRef` | `string` | O | Max 50 |
| `isActive` | `bool` | K | A gyökér aktiválás/deaktiválás itt megy, mezős úton |
| `expectedUpdatedAt` | `DateTime` | K | Optimista konkurrencia |

Nem szerkeszthető a PUT-tal: `parentId` (a fa-szerkezet stabil),
`sourceCatalogId` (rendszer-mező), `sortOrder` (a reorder-végpont állítja).
Ha a kérés mégis tartalmaz eltérő értéket ezekre → `400 fieldErrors[mezo] =
"field_not_editable"`.

**Hibakódok:** `400` (validáció), `404` (nincs `id`), `409 stale` (konkurrencia).

**Response — `200`** + a frissített `CategoryDto`.

#### 3.1.7 `DELETE /v1/categories/{id}` — törlés delete-guard mögött (SD-63)

> **Eltérés a mintától — `BeforeDeleteAsync` guard.** A standard delete csak
> az entitást törli; itt a guard ellenőrzi a `Ticket`-hivatkozásokat és a
> leszármazott-fát.

**A guard logikája:**

1. **Saját hivatkozás.** Van-e `Ticket`, ahol `categoryId == {id}` **OR**
   `citizenSuggestedCategoryId == {id}`?
2. **Leszármazott-fa.** Ha a `{id}` gyökér: van-e bármely alkategória,
   amelyre érvényes az 1. pont?
3. Ha bármelyik igaz → `409 category_referenced`, a `details` mezővel:

```json
{ "reason": "category_referenced",
  "details": {
    "directTicketCount": 5,
    "descendantsWithTickets": [{ "categoryId": 12, "ticketCount": 3 }]
  }
}
```

4. Ha tiszta → a delete kaszkádol: a gyökér törlése törli az alkategóriáit
   is.

**Egyéb hibakódok:** `404`, `409 stale`.

**Response — `204 No Content`**.

> **A UI dolga:** a felületen a "Törlés" akció kebab-menüben rejtett (a
> sor-szintű elsődleges akció a "Deaktiválás"); a vezető megerősítő-
> dialógusban látja a delete-guard-eredményt, ha a szerver `409`-cel zár.

#### 3.1.8 `POST /v1/categories/reorder` — szülő-hatókörben (SD-64)

> **Eltérés a mintától.** A standard reorder lapos listát rendez; itt a
> `parentId`-hatókör paraméterezett.

**Request — `ReorderCategoriesRequest`:**

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `parentId` | `long?` | F | `null` gyökerek rendezésekor; gyökér-id egy szülőn belüli alkategóriák rendezésekor |
| `ids` | `long[]` | K | A rendezett sorrend |

**Validáció:** Minden `id` létezik, és `parentId`-ja egyezik a kérés
`parentId`-jával; duplikátummentes.

**Workflow:** a szerver `sortOrder = 0, 1, 2, ...` értékeket ír a tömb
sorrendjében.

**Hibakódok:** `400` (validáció, `wrong_scope` egy nem-megfelelő szülőjű
elemre), `409 stale` (opcionális — fejlesztői döntés).

**Response — `200`** + a frissített `CategoryDto[]` lista (csak az érintett
szinten).

### 3.2 Csoportok — `/v1/groups` család (SD-65)

A `Group` a `BaseController<Group, GroupDto, GroupListDto,
GroupListResponse>` mintát követi — **standard CRUD a `BaseController`
szerint**, egyetlen megerősítéssel: a `members` mező a
`[ManyToManyConnection]` auto-mapping mintával (`00_domain_model.md` 2.2).

#### 3.2.1 `GET /v1/groups` — lista

**Standard.** A `GroupListDto`:

| Mező | Forrás | Megjegyzés |
|---|---|---|
| `id` | `Group.id` | — |
| `name` | `Group.name` | — |
| `isActive` | `Group.isActive` | — |
| `memberCount` | denormalizált | A `Group.members`-tagok száma |

> **Eltérés a mintától — `memberCount` aggregátum.** Ugyanaz a logika, mint
> a `Category.ticketCount`-nál (3.1.1).

A `members` lista nem része a `GroupListDto`-nak — a tagság a
`GET /v1/groups/{id}`-ben.

**Default rendezés:** `name ASC`.

#### 3.2.2 `GET /v1/groups/{id}` — egy csoport tagsággal

**Standard.** A `GroupDto`:

| Mező | Forrás | Megjegyzés |
|---|---|---|
| `id`, `name`, `isActive` | `Group` | — |
| `tenantUserIds` | a `[ManyToManyConnection]` auto-mapping | Taglista ID-tömbként |
| `members` | denormalizált | A taglista megjelenítő-DTO-ja: `[{id, displayName, status, roles}]` a `TenantUser`-ből |

> **Eltérés a mintától — denormalizált `members` lista.** A standard
> `[ManyToManyConnection]` csak ID-tömböt adna; a feature a megjelenítő-
> DTO-t is hozzáfűzi, hogy a csoport-adatlap egyetlen hívással rajzolható
> legyen. (Konzisztens a `20_felhasznalokezeles.md` 4. mintájával.)

#### 3.2.3 `POST /v1/groups`, `PUT /v1/groups/{id}` — létrehozás, szerkesztés

**Standard `BaseController` write.** A `name`, `isActive`, `tenantUserIds`
mind része a request-nek. A `[ManyToManyConnection]` auto-mappeli a
join-tábla-műveleteket.

**Validáció:**

- `name` — kötelező, max 100, nem üres.
- `tenantUserIds` — opcionális; ha jelen, minden ID létező és **aktív**
  (`status == Active`) `TenantUser`. `Invited` / `Disabled` `TenantUser` nem
  rendelhető csoportba — `400 fieldErrors[tenantUserIds]`.

**Hibakódok:** standard.

#### 3.2.4 `DELETE /v1/groups/{id}` — törlés delete-guard mögött

> **Eltérés a mintától — a `Category` delete-guard mintájának kiterjesztése
> (SD-63 lábjegyzete).** A `Group`-spec `BeforeDeleteAsync`-ja ellenőrzi: ha
> van `Ticket`, ahol `assignedGroupId == {id}` → `409 group_referenced`.

**Indok:** szimmetria a `Category`-val; az adatintegritás védelme; a
deaktiválás mint UI-elsődleges út a `Group`-nál ugyanaz az elv, mint a
`Category`-nál.

**Egyéb:** `404`, `409 stale`.

**Response — `204 No Content`**.

#### 3.2.5 `POST /v1/groups/reorder` — nincs

A `Group` nem hordoz `sortOrder` mezőt (`00_domain_model.md` 2.2); a
reorder-műveletet a controller **nem teszi közzé**. A lista alapértelmezetten
`name ASC`. Ha később felmerül az igény, a `sortOrder` hozzáadása és a
reorder publikálása iterációs elem (8.2).

### 3.3 Általános — három alszekció

A `/beallitasok/altalanos` aloldal (SD-67) **három logikai szekció** három
különálló API-szerződéssel: tenant-settings, logó, riport-címzettek. A
három együtt egy képernyő, szerver-oldalon külön végpontok.

#### 3.3.1 Tenant-settings — `GET`/`PUT /v1/tenant-settings` (SD-61)

> **Eltérés a mintától — dedikált `TenantSettingsController`.** Nem a
> standard `Tenant` CRUD: a vezető nem szerkeszti az egész `Tenant`-rekordot,
> csak a "tenant-tulajdon" mezőket. A controller a Core `DbContext`-et
> használja, és a `Tenant` headerből kiolvasott `code`-ra explicit szűr —
> analóg minta: `UserManagementController` (`20_felhasznalokezeles.md` 3.1,
> SD-36).

**`GET /v1/tenant-settings`**

**Request:** nincs paraméter. A `Tenant` header adja a tenant-kódot.

**Response — `200`** + `TenantSettingsDto`:

| Mező | Forrás | Szerk. | Megjegyzés |
|---|---|---|---|
| `code` | `Tenant.code` | — | Olvasásra |
| `name` | `Tenant.name` | ✓ | `PUT`-tal szerkeszthető |
| `displayPrefix` | `Tenant.displayPrefix` | — | Olvasásra |
| `timeZone` | `Tenant.timeZone` | — | Olvasásra |
| `logoFileRef` | `Tenant.logoFileRef` | — (a logó-végpontok írják, 3.3.2) | — |
| `logoUrl` | denormalizált | — | A `logoFileRef`-ből generált presigned-URL (rövid TTL); `null` ha `logoFileRef` `null` |
| `contactAddress` | `Tenant.contactAddress` | ✓ | `PUT`-tal szerkeszthető |
| `contactPhone` | `Tenant.contactPhone` | ✓ | `PUT`-tal szerkeszthető |
| `contactEmail` | `Tenant.contactEmail` | ✓ | `PUT`-tal szerkeszthető |
| `updatedAt`/`updatedBy` | `Tenant` | — | Audit |

A `TenantSettingsDto` **nem tartalmaz**: `dbConnectionRef`, `status`, `id`
mezőt — biztonsági okból.

**`PUT /v1/tenant-settings`**

**Request — `UpdateTenantSettingsRequest`:**

| Mező | Típus | Köt. | Validáció |
|---|---|---|---|
| `name` | `string` | K | Max 200, nem üres |
| `contactAddress` | `string` | O | Max 300, `null` elfogadott (törlés) |
| `contactPhone` | `string` | O | Max 50, `null` elfogadott |
| `contactEmail` | `string` | O | Max 200, e-mail formátum (ha nem `null`), `null` elfogadott |
| `expectedUpdatedAt` | `DateTime` | K | Optimista konkurrencia |

**A tiltott mezők**: ha a `code`, `displayPrefix`, `dbConnectionRef`,
`status`, `timeZone`, `logoFileRef` mezők közül **bármelyik szerepel nem
`null`-ként vagy az eredetitől eltérően** → `400 fieldErrors[mezo] =
"field_not_editable"`.

**Workflow:**

1. A `Tenant` headerből `code` lekéve.
2. Core DB-ben a `Tenant`-rekord kikeresve.
3. Optimista konkurrencia: ha `updatedAt != expectedUpdatedAt` → `409 stale`.
4. Csak a négy szerkeszthető mező frissül.
5. `AuditableEntity.updatedBy` automatikus.

**Hibakódok:** `400` (validáció, `field_not_editable`), `404` (a tenant nem
létezik — pilotra nem fordul elő), `409 stale`.

**Response — `200`** + a frissített `TenantSettingsDto`.

#### 3.3.2 Logó — `POST`/`DELETE /v1/tenant-settings/logo` (SD-60)

> **Eltérés a mintától — két új végpont, multipart upload, új mintázat
> ("egyedi-fájl-mező").** A standard `BaseController` JSON-CRUD-ot ad; a logó
> multipart-bináris.

**`POST /v1/tenant-settings/logo` — feltöltés**

**Request:** `multipart/form-data`, egyetlen `file` mező a bináris képpel.

**Validáció:**

- MIME-típus: `image/png`, `image/jpeg`, `image/svg+xml` — `Content-Type`-ban
  **és** magic bytes-ban (a header hazudhat).
- Méret-limit **2 MB**, stream-szinten ellenőrizve.

**Workflow:**

1. Korábbi `Tenant.logoFileRef` lekérve.
2. Új S3-objektum feltöltése. Kulcs-konvenció:
   `tenants/{tenantCode}/logo/{guid}.{ext}` — egyedi minden feltöltésnél
   (cache-busting). A backend belső megvalósítása direkt-stream vagy
   presigned-URL-séma — fejlesztői döntés, a route-szerződés mindkettőre
   transzparens.
3. `Tenant.logoFileRef` frissítése; `AuditableEntity.updatedBy` automatikus.
4. Korábbi S3-objektum törlésre kerül (`AfterSaveAsync`); ha meghiúsul,
   csak naplózódik (árva objektum, NY-K3).

**Hibakódok:**

| Status | `reason` | Mikor |
|---|---|---|
| `400` | `unsupported_media_type` | MIME-szűrés sikertelen |
| `400` | `file_too_large` | > 2 MB |
| `400` | `fieldErrors` | A `file` mező hiányzik |

**Response — `200`** + a frissített `TenantSettingsDto`.

**`DELETE /v1/tenant-settings/logo` — törlés**

**Request:** nincs body.

**Workflow:**

1. Ha `Tenant.logoFileRef == null` → `204 No Content` (idempotens).
2. S3-objektum törlése (ha meghiúsul: naplózás).
3. `Tenant.logoFileRef = null`.

**Response — `204 No Content`**.

#### 3.3.3 Riport-címzettek — `/v1/weekly-report-recipients` család (SD-59)

A `WeeklyReportRecipient` a `BaseController<WeeklyReportRecipient,
WeeklyReportRecipientDto, ...>` mintát követi — **standard CRUD a
`BaseController` szerint**. A `40_riport.md` SD-57 fogyasztói várakozása ezen
keresztül teljesül.

**`GET /v1/weekly-report-recipients` — lista**

**Standard `BaseController` list.** A `WeeklyReportRecipientListDto`:

| Mező | Forrás | Megjegyzés |
|---|---|---|
| `id` | — | — |
| `name` | `WeeklyReportRecipient.name` | `null` lehet |
| `email` | `WeeklyReportRecipient.email` | — |
| `isActive` | `WeeklyReportRecipient.isActive` | — |
| `createdAt`/`updatedAt` | — | Audit |

**Szűrők:** `isActive` (standard `FilterQuery`).

**Default rendezés:** `createdAt ASC`.

> **A `40_riport.md` SD-57 fogyasztása.** A riport-adatlap "Címzettek"
> szekciója és a `resend`-megerősítő-dialógus a
> `GET /v1/weekly-report-recipients?isActive=true` szűrt verzióját hívja. **Ez
> ugyanaz a végpont**, mint a `/beallitasok/altalanos` Címzettek szekciója —
> külön olvasó-végpont nem keletkezik (SD-66). A `40_riport.md` 8.2 (3)
> feltételezés ezzel formálisan teljesül.

**`POST /v1/weekly-report-recipients` — új címzett**

**Request — `CreateWeeklyReportRecipientRequest`:**

| Mező | Típus | Köt. | Validáció |
|---|---|---|---|
| `name` | `string` | O | Max 200, `null` elfogadott |
| `email` | `string` | K | Max 200, e-mail formátum, **egyedi a tenanton** |

**Hibakódok:**

| Status | `reason` | Mikor |
|---|---|---|
| `400` | `fieldErrors` | Validáció |
| `409` | `recipient_email_duplicate` | Már létezik ilyen `email` |

**Response — `201 Created`**. `isActive` automatikusan `true`.

**`PUT /v1/weekly-report-recipients/{id}` — szerkesztés**

**Standard update.** A `name`, `email`, `isActive` szerkeszthető. Optimista
konkurrencia (`expectedUpdatedAt`). Az `email`-egyediség az update-en is
érvényesül (`409 recipient_email_duplicate`).

**`DELETE /v1/weekly-report-recipients/{id}` — törlés**

**Standard delete**, guard nélkül. `204 No Content`.

### 3.4 Validáció — gyűjtött FluentValidation-szabályok

A mezőméreteket a `00_domain_model.md` rögzíti; itt a FluentValidation-
szabályok listája.

**`CreateCategoryRequest`:** `parentId` kötelező, létezik, gyökér, aktív;
`name` kötelező, nem üres, max 100; `iconRef` opcionális, max 50.

**`UpdateCategoryRequest`:** `name` kötelező, nem üres, max 100; `iconRef`
opcionális, max 50; `isActive` kötelező (bool); `expectedUpdatedAt`
kötelező. A `parentId`, `sourceCatalogId`, `sortOrder` szerepeltetése →
`field_not_editable`.

**`ImportRootCategoryRequest`:** `defaultCategoryId` kötelező, létezik
Core-ban, gyökér.

**`ReorderCategoriesRequest`:** `parentId` opcionális (`null` a gyökerekre);
`ids` kötelező, nem üres, duplikátummentes, minden elem létező `Category` a
megadott `parentId`-jű szülő alatt.

**`CreateGroupRequest` / `UpdateGroupRequest`:** `name` kötelező, nem üres,
max 100; `tenantUserIds` opcionális, ha jelen, minden elem létező, **aktív**
(`status == Active`) `TenantUser`; `expectedUpdatedAt` (update-en) kötelező.

**`UpdateTenantSettingsRequest`:** `name` kötelező, nem üres, max 200;
`contactAddress` opcionális, max 300; `contactPhone` opcionális, max 50;
`contactEmail` opcionális, max 200, e-mail formátum (ha nem `null`);
`expectedUpdatedAt` kötelező. A tiltott mezők szerepeltetése →
`field_not_editable`.

**Logó-feltöltés:** `file` mező kötelező; MIME-típus + magic bytes
`image/png`/`image/jpeg`/`image/svg+xml`; méret ≤ 2 MB.

**`CreateWeeklyReportRecipientRequest` /
`UpdateWeeklyReportRecipientRequest`:** `name` opcionális, max 200,
`null` elfogadott; `email` kötelező, nem üres, max 200, e-mail formátum,
egyedi a tenanton; `isActive` (update-en) kötelező; `expectedUpdatedAt`
(update-en) kötelező.

### 3.5 Jogosultság — `authorization.json` (SD-66)

A négy aloldal API-jának teljes route-listája, minden végpont
`tenant_manager` szerepkörrel (a tenant-prefix futásidőben kiegészül a
`Tenant` header kódjával).

```
Route                                          Method      Roles
/v1/categories                                 GET         tenant_manager
/v1/categories/{id}                            GET         tenant_manager
/v1/categories                                 POST        tenant_manager
/v1/categories/from-default                    POST        tenant_manager
/v1/categories/{id}                            PUT         tenant_manager
/v1/categories/{id}                            DELETE      tenant_manager
/v1/categories/reorder                         POST        tenant_manager
/v1/categories/available-default-roots         GET         tenant_manager

/v1/groups                                     GET         tenant_manager
/v1/groups/{id}                                GET         tenant_manager
/v1/groups                                     POST        tenant_manager
/v1/groups/{id}                                PUT         tenant_manager
/v1/groups/{id}                                DELETE      tenant_manager

/v1/tenant-settings                            GET         tenant_manager
/v1/tenant-settings                            PUT         tenant_manager
/v1/tenant-settings/logo                       POST        tenant_manager
/v1/tenant-settings/logo                       DELETE      tenant_manager

/v1/weekly-report-recipients                   GET         tenant_manager
/v1/weekly-report-recipients/{id}              GET         tenant_manager
/v1/weekly-report-recipients                   POST        tenant_manager
/v1/weekly-report-recipients/{id}              PUT         tenant_manager
/v1/weekly-report-recipients/{id}              DELETE      tenant_manager
```

A `tenant_dispatcher`, `tenant_content_manager`, `tenant_field_worker` egyik
route-ra sem fér rá — `403`-mal zár (`05_jogosultsagok_v2.md` 4.3:
explicit nem-felsorolás = elérhetetlen).

### 3.6 Multi-tenancy érvényesítés

| Entitás | DB-szint | Tenant-szűrés |
|---|---|---|
| `Category` | Tenant DB | Szerkezeti — automatikus `TenantDbContext` (SD-6) |
| `Group` | Tenant DB | Szerkezeti — ugyanúgy |
| `WeeklyReportRecipient` | Tenant DB | Szerkezeti — ugyanúgy |
| `Tenant` (a `TenantSettingsController`-en át) | Core DB | Logikai — a `Tenant` header `code`-jára szűr a controller (analóg `UserManagementController`, SD-36) |
| `DefaultCategoryCatalog` (cross-DB-olvasáshoz) | Core DB | Tenant-független — közös olvasható forrás minden tenantra |

**Cross-tenant kísérlet:** a JWT a felhasználó tenant-jogosultságait
hordozza; a `TenantAccessMiddleware` (`project_backend_v2.md` Tenant
Database) `403`-mal zár, ha a kért tenant nem szerepel benne. A kísérlet
naplózott (`01_kozos_mintak.md` 7.3, SD-13).

### 3.7 Audit és naplózás

A négy érintett entitás mind `AuditableEntity` (`createdAt/By`,
`updatedAt/By` automatikus — `01_kozos_mintak.md` 7.1). Külön esemény-napló
(dedikált audit-tábla) ezekre az entitásokra a pilotra **nincs** — ritka
művelet, `AuditableEntity` szintje elég. Konzisztens a
`20_felhasznalokezeles.md` 3.9 megtartott hiányával. **Megtartott hiány —
NY-K4** (8.4).

A logó-feltöltésnél a `Tenant.updatedBy` és `updatedAt` rögzíti, ki és mikor
töltötte fel. A korábbi S3-objektum törlése nem audit-esemény; az
S3-objektum-kulcs felhasználói azonosítót nem tartalmaz. A teljes feltöltő-
történet a `Tenant`-rekord audit-mezőin keresztül csak "az utolsó feltöltő"-
szintű — a teljes történet hiánya megtartott hiány (NY-K4 része).

---

## 4. Admin felület

A négy aloldal felületi specifikációja. A standard admin-minta
(`TableStateConfig`, `[validationForm]`, `<form-field>`, `LookupLoaderService`,
`NavStore`) készen adja a kereteket.

### 4.1 Beállítások mint navigációs keret

**Érintett képernyők — sitemap-leképezés (`90_sitemap_v3.md` 2.6):**

| Nézet | URL | Komponens-típus |
|---|---|---|
| Kategóriák | `/beallitasok/kategoriak` | Lista + szerkesztő-modal |
| Csoportok | `/beallitasok/csoportok` | Lista + adatlap + szerkesztő |
| Felhasználók | `/beallitasok/felhasznalok` | `20_felhasznalokezeles.md` 4. fedi |
| Általános | `/beallitasok/altalanos` | Egyetlen szerkesztő-űrlap három szekcióval (SD-67) |

A `/beallitasok` önmagában nem oldal — Angular router-szinten **redirect**
`/beallitasok/kategoriak`-ra. A főnavigáció "Beállítások" tételére kattintva
lenyíló almenü, négy aloldal-linkkel — a tétel maga nem navigál.

**`NavStore` — page title és breadcrumb:**

| Aloldal | Page title (`hu.json` kulcs) | Breadcrumb |
|---|---|---|
| Kategóriák | `settings.categories.title` → "Kategóriák" | "Beállítások › Kategóriák" |
| Csoportok | `settings.groups.title` → "Csoportok" | "Beállítások › Csoportok" |
| Felhasználók | `settings.users.title` → "Felhasználók" | "Beállítások › Felhasználók" |
| Általános | `settings.general.title` → "Általános" | "Beállítások › Általános" |

A "Beállítások" breadcrumb-elem nem klikkelhető link (nincs `/beallitasok`
oldal), csak címke.

### 4.2 Kategóriák — `/beallitasok/kategoriak`

#### 4.2.1 A nézet alakja — csoportosított fa-lista

A `/beallitasok/kategoriak` egy kétszintű fa-listát mutat. A
**csoportosított struktúrát** választjuk (gyökér mint csoport-fejléc,
alkategóriák alatta) a lapos, `parentId`-behúzott alternatíva helyett.

> **Tervezési döntés — csoportosított lista a lapos helyett (nem önálló SD,
> SD-64 lábjegyzete).** A csoportosított jobb a vezető átlátásához: a hét
> gyökér-kategória természetes csoportosító. A `sortOrder` szülő-hatókör
> (SD-64) UI-megfelelője: a vezető a csoport-fejlécen drag-and-drop-pal
> rendezheti a gyökereket, a csoporton belül az alkategóriákat. Megvalósítás:
> a meglévő admin-minta PrimeNG-komponensei (`p-panel`, `p-orderList` vagy
> `p-table` group-rendezéssel) támogatják.

#### 4.2.2 Megjelenített tartalom

A nézet **nem teljes `TableStateConfig`** — a csoportosított struktúra miatt
a pagináció, oszlop-választó kevésbé jellemző. Egyszerűbb formában:

| Megjelenítés | Forrás | Megjegyzés |
|---|---|---|
| Ikon | `Category.iconRef` | Az ikon-key alapján |
| Név | `Category.name` | Inline-szerkesztés (K-026) |
| Aktív | `Category.isActive` | Toggle |
| Bejelentések száma | `CategoryListDto.ticketCount` | "5 nyitott ügy" |
| Forrás | `Category.sourceCatalogId != null` | Badge: "Termékesített" / "Tenant-egyedi" |

**Lista-szintű akciók:**

- **"Új gyökér-kategória"** gomb — default-katalógus-választó dialógust nyit
  (4.2.4).
- **"Új alkategória"** gomb minden gyökér-csoport fejlécén — modal-űrlap.

**Sor-szintű akciók** (kebab-menü):

- Szerkesztés (modal-űrlap).
- Sorrend változtatása (drag handle).
- Törlés (rejtett, másodlagosan; megerősítő-dialógussal a delete-guard-
  eredménnyel).

**Bulk-műveletek:** nincsenek. **Eltérés a mintától:** konzisztens a
`20_felhasznalokezeles.md` 4.2 megegyező döntésével.

#### 4.2.3 Inline-szerkesztés és toggle

A `name` mező **inline szerkeszthető** (K-026, ugyanaz a minta mint a
triage-sávon, `10_bejelentes_lista_es_adatlap.md` 4.3.1): kattintás
aktiválja, Enter ment, Escape megszakít. Optimistikus frissítés.

Az `isActive` toggle azonnali — kattintásra optimistikus átkapcsolás, hiba
esetén visszagördül.

#### 4.2.4 Új gyökér — default-katalógus-választó dialógus

A "Új gyökér-kategória" gomb modal-dialógust nyit:

1. **Magyarázó szöveg:** "Új gyökér-kategória csak a termékesített készletből
   választható. Az alábbi gyökerek érhetők el. Ha hiányzik valami, vedd fel
   a kapcsolatot az Urbino csapatával."
2. **Lista** a `GET /v1/categories/available-default-roots`-ból: ikon, név,
   alkategória-darabszám.
3. **"Importálás" gomb** — `POST /v1/categories/from-default`.
4. **"Mégse" gomb.**

**Üres állapot** (ha minden importálva van): "Minden gyökér-kategória már
importálva van. Az alkategóriák szabadon kezelhetők a gyökerek alatt."

#### 4.2.5 Új alkategória — modal-űrlap

A gyökér-csoport fejlécén az "Új alkategória" gomb egyszerű modal-űrlapot
nyit:

- **Mezők:** Név (kötelező, max 100), Ikon (opcionális — ikon-választó UI).
- **A `parentId`** kontextusból, nem szerkeszthető.
- **Mentés gomb** — `POST /v1/categories`.

A `[validationForm]` minta adja a szerver-oldali field-error megjelenítését.

#### 4.2.6 Inaktív kategóriák megjelenítése

Az inaktív kategóriák halványan látszanak (`opacity: 0.5`), "inaktív" badge.
Lista-szintű toggle: "Inaktív kategóriák mutatása" (alapesetben be).

#### 4.2.7 Mezősablon — a `name`-mező

```
- Mező megjelenő neve: Kategória neve
- i18n kulcs: settings.categories.field.name
- Technikai név (API, camelCase): name
- Adatmodell-megfeleltetés: Category.name
- Típus és méret: string, max 100
- Kötelezőség: kötelező — nem üres, nem whitespace
- Validáció (kliens): legfeljebb 100 karakter (live counter)
- Validáció (FluentValidation szerver): kötelező, nem üres, max 100
- Default érték: nincs (új létrehozáskor üres)
- Láthatóság: mindig
- Szerkeszthetőség: inline-szerkesztés a listán; modal-űrlapon új létrehozásnál
- Interakció: inline — kattintásra szerkeszthető, Enter ment, Escape megszakít
- Tenant-szinten konfigurálható: nem (de a vezető szerkeszti — ő a tenant)
- Eredet: 00_domain_model.md 2.1 (Category.name)
- Megjegyzés: a gyökér-kategória átnevezése is megengedett (50_konfig_v2.md 2.2)
```

### 4.3 Csoportok — `/beallitasok/csoportok`

#### 4.3.1 Lista-oldal — `TableStateConfig` vázlat

Standard `TableStateConfig`-minta. **Oszlopok:**

| Oszlop | Forrás | Szűrhető | Rendezhető | Megjegyzés |
|---|---|---|---|---|
| Név | `GroupListDto.name` | igen (szöveg) | igen | — |
| Aktív | `GroupListDto.isActive` | igen (kapcsoló) | igen | "Aktív" / "Inaktív" badge |
| Tagok száma | `GroupListDto.memberCount` | nem | igen | "3 tag" |
| Létrehozva | `GroupListDto.createdAt` | nem | igen | Tenant-időzónában |

**Default rendezés:** `name ASC`.

**Sor-akciók:** megnyitás (adatlap), szerkesztés, deaktiválás/reaktiválás,
törlés (delete-guard mögött).

**Lista-szintű akció:** "Új csoport létrehozása" — modal-űrlap (Név mező,
üres taglista).

**Bulk-műveletek:** nincsenek.

#### 4.3.2 Csoport-adatlap — `/beallitasok/csoportok/details/<id>`

**Alapadatok szekció:** `name` olvasásra (a "Szerkesztés" gomb modal-űrlapba
nyit), `isActive` badge, `Létrehozva`/`Módosítva` audit.

**Taglista szekció:**

- Multiselect-választó (a `TenantUser` aktív felhasználói közül) — chip-
  szerű megjelenítés.
- Új tag: dropdown autocomplete (`LookupLoaderService`).
- Tag eltávolítása: chip × jellel.
- Üres állapot: "Még nincs tag ebben a csoportban."

A taglista-szerkesztés és a `name`-szerkesztés **egy mentés alá** kerül
(`PUT /v1/groups/{id}`), a `[validationForm]` minta szerint.

> **Eltérés a mintától — a taglista a `members` denormalizált megjelenítő-
> DTO-ból rajzol** (3.2.2 részletezve).

#### 4.3.3 Csoporttagság a felhasználó-adatlapon

A `20_felhasznalokezeles.md` 4.3 már rögzítette: a felhasználó-adatlapon a
csoporttagság **csak olvasásra** jelenik meg; a szerkesztés a csoport-
adatlapról megy. Ez a feature-spec ezt nem ismétli — a `50_konfig_v2.md`
6.2 és `20_felhasznalokezeles.md` 4.3 az autoritatív hely.

#### 4.3.4 Mezősablon — a `tenantUserIds` taglista

```
- Mező megjelenő neve: Tagok
- i18n kulcs: settings.groups.field.members
- Technikai név (API, camelCase): tenantUserIds (input); members (response-only megjelenítő DTO)
- Adatmodell-megfeleltetés: Group.Members (n-n a TenantUser-rel, [ManyToManyConnection])
- Típus és méret: long[]
- Kötelezőség: opcionális (üres csoport is létrehozható)
- Validáció (kliens): legfeljebb 50 elem
- Validáció (FluentValidation szerver): minden ID létező, aktív (Active) TenantUser; duplikátummentes
- Default érték: üres tömb
- Láthatóság: mindig
- Szerkeszthetőség: csak a csoport-adatlap szerkesztő-űrlapján
- Interakció: multiselect chip-szerű választó (LookupLoaderService autocomplete, csak Active TenantUser)
- Tenant-szinten konfigurálható: nem (a felhasználó-listából választ)
- Eredet: 50_konfig_v2.md 3.2, 00_domain_model.md 2.2
- Megjegyzés: a felhasználó-adatlapon csak olvasásra (20_felhasznalokezeles.md 4.3)
```

### 4.4 Általános — `/beallitasok/altalanos`

A `/beallitasok/altalanos` (SD-67) **egyetlen szerkesztő-oldal**, négy
logikai szekcióval:

1. Tenant-alapadatok
2. Logó
3. Kontakt-adatok
4. Heti riport címzettjei

#### 4.4.1 Általános elrendezés

A négy szekció egymás alatt, scrollozható. Asztalin két oszlop is
elképzelhető (alapadatok+kontakt egy oszlop, logó+címzettek a másik) — UI-
finomság, `frontend-design`-skill dolga. A spec a szekciók sorrendjét
rögzíti, az oszlopos elrendezés szabad fejlesztői döntés.

#### 4.4.2 Tenant-alapadatok szekció

A `GET /v1/tenant-settings`-ből töltve. `[validationForm]`-minta a négy
mezőre. Egyetlen mentés-gomb az alapadatok+kontakt szekciók végén.

| Mező | Megjelenő név | Szerk. | Megjegyzés |
|---|---|---|---|
| `code` | Kód | — | Olvasásra: "almadi" |
| `displayPrefix` | Bejelentés-prefix | — | Olvasásra: "ALM" |
| `timeZone` | Időzóna | — | Olvasásra: "Europe/Budapest" |
| `name` | Tenant neve | ✓ | Max 200, kötelező |

A `code`, `displayPrefix`, `timeZone` olvasásra-megjelenítését info-tooltip
kíséri: "Ez a beállítás csak az Urbino csapatán keresztül módosítható."

#### 4.4.3 Logó szekció

**Független** a fő `[validationForm]`-tól (külön végpontokon, SD-60).

**Megjelenítés:**

- Ha van logó: kép-előnézet (a `logoUrl` presigned-URL-ből), ~120×120 px.
- Ha nincs: placeholder ikon + "Még nincs logó feltöltve" szöveg.

**Akciók:**

- **"Logó feltöltése" / "Csere"** gomb — fájl-választó,
  `POST /v1/tenant-settings/logo`. Sikeres feltöltéskor azonnali frissítés.
- **"Eltávolítás"** gomb (csak ha van logó) — megerősítő-dialógus,
  `DELETE /v1/tenant-settings/logo`.

**Validáció-megjelenítés:**

- MIME-hiba → "Csak PNG, JPEG vagy SVG kép tölthető fel."
- Méret-hiba → "A logó mérete nem lehet nagyobb 2 MB-nál."

**Tooltip:** "A logó megjelenik a felület fejlécében és a heti PDF-riport
fejlécében."

#### 4.4.4 Kontakt-adatok szekció

Az alapadatok-szekció `[validationForm]`-jának folytatása:

| Mező | Megjelenő név | Megjegyzés |
|---|---|---|
| `contactAddress` | Cím | Egy-soros input, max 300 |
| `contactPhone` | Telefon | Egy-soros input, max 50 |
| `contactEmail` | E-mail | E-mail formátum, max 200 |

Mind opcionális. A `[validationForm]` az alapadatokkal együtt menti egyetlen
`PUT`-tal.

**Magyarázó tooltip:** "A kontakt-adatok megjelenhetnek a polgári app
Ügyfélszolgálat szekciójában." *(NY-K2, 8.4.)*

#### 4.4.5 Heti riport címzettjei szekció

A `WeeklyReportRecipient` lista beágyazott mini-listaként. **Külön** az
alapadatok-`[validationForm]`-tól — soronként mentődik.

**Megjelenítés:**

| Név | E-mail | Aktív | Akciók |
|---|---|---|---|
| Dr. Polgár Péter | peter@almadi.hu | toggle | szerk \| törlés |
| | titkarsag@almadi.hu | toggle | szerk \| törlés |

**Akciók:**

- **"Új címzett hozzáadása"** gomb — inline-form vagy modal (fejlesztői
  döntés). Mezők: Név (opcionális), E-mail (kötelező). →
  `POST /v1/weekly-report-recipients`.
- **Szerkesztés** (kebab-menü vagy ikon) — ugyanaz az inline/modal.
- **Törlés** — megerősítő-dialógus, `DELETE`.

**Üres állapot:** "Még nincs címzett a heti riport-listán. Vedd fel az első
címzettet — például a saját e-mail-címedet, ha kérni szeretnéd a riportot."

#### 4.4.6 Egyéb finomságok

- **Mentés-állapot:** a "Mentés" gomb csak dirty állapotban aktív; mentés
  után toast: "Mentés sikeres".
- **Mentetlen módosítások figyelmeztetése:** böngésző-szintű warning
  navigációkor (csak az alapadatok+kontakt szekciókra értelmes).
- **Optimista konkurrencia ütközés:** ha `409 stale`, a felület újratölti
  az oldalt + figyelmeztetés: "Egy másik felhasználó időközben módosította a
  beállításokat. Frissítettük a felületet — kérlek, ellenőrizd és mentsd
  újra."

### 4.5 Közös elemek

#### 4.5.1 Jogosultság — szerepkör-érzékenység

A teljes Beállítások vezető-only (`05_jogosultsagok_v2.md` 2.8). Az Angular
route-guard a `/beallitasok/*` útvonalakat a `tenant_manager` szerepkörhöz
köti; a szerver kétrétegű érvényesítéssel `403`-mal zár (`01_kozos_mintak.md`
3.5, `05_jogosultsagok_v2.md` 4.3). A "Beállítások" főmenü-tétel **nem
jelenik meg** a többi szerepkör számára.

#### 4.5.2 Üres / betöltési / hibaállapot

| Aloldal | Üres | Betöltési | Hiba |
|---|---|---|---|
| Kategóriák | "Még nincs kategória ezen a tenanton. Importálj gyökér-kategóriákat a 'Új gyökér-kategória' gombbal." | skeleton/spinner | standard error |
| Csoportok | "Még nincs csoport. Hozz létre egyet a 'Új csoport létrehozása' gombbal." | standard | standard |
| Általános | — (mindig van `Tenant`-rekord) | form-skeleton | standard |
| WeeklyReportRecipient | "Még nincs címzett a heti riport-listán." (4.4.5) | — | standard |

#### 4.5.3 i18n kulcsok

Minden UI-szöveg kulcs-alapú (`hu.json`, `01_kozos_mintak.md` 5.3). A
felület tegez (5.4). A `TableStateConfig.translationPrefix`
auto-generálja az oszlopfejléceket.

**Kulcs-csoportok:**

- `settings.nav.*` — almenü-címkék (`categories`, `groups`, `users`,
  `general`, `mainMenu`).
- `settings.categories.*` — title, description; field.name/iconRef/isActive/
  parentId/ticketCount/sourceCatalogId; action.createRoot/createChild/import/
  delete/deactivate/reactivate/reorder; badge.productOwned/tenantOwned/
  inactive; dialog.importTitle/importIntro/importEmpty/importChildCount;
  error.rootMustComeFromDefault/categoryDepthExceeded/categoryReferenced/
  parentInactive/notARootDefault/defaultCategoryAlreadyImported;
  confirm.deleteTitle/deleteBody/deactivateBody.
- `settings.groups.*` — title; field.name/isActive/members/memberCount/
  createdAt; action.create/delete/deactivate/reactivate/addMember/
  removeMember; error.groupReferenced; confirm.deleteTitle/deleteBody;
  emptyMembers; emptyList.
- `settings.general.*` — title; section.basics/logo/contact/recipients;
  field.code/name/displayPrefix/timeZone/contactAddress/contactPhone/
  contactEmail; logo.upload/replace/remove/empty/tooltip;
  logo.error.unsupportedMediaType/fileTooLarge; tooltip.codeReadonly/
  prefixReadonly/timeZoneReadonly/contactExposed; action.save/saveSuccess/
  unsavedChanges; error.fieldNotEditable/staleConfig.
- `settings.recipients.*` — field.name/email/isActive; action.add/edit/
  delete/toggleActive; error.emailDuplicate/emailInvalid;
  confirm.deleteTitle/deleteBody; emptyList.

**Hibakód → i18n-kulcs leképezés (kötelező):**

| `reason` | i18n-kulcs |
|---|---|
| `root_must_come_from_default` | `settings.categories.error.rootMustComeFromDefault` |
| `category_depth_exceeded` | `settings.categories.error.categoryDepthExceeded` |
| `category_referenced` | `settings.categories.error.categoryReferenced` |
| `parent_inactive` | `settings.categories.error.parentInactive` |
| `not_a_root_default` | `settings.categories.error.notARootDefault` |
| `default_category_already_imported` | `settings.categories.error.defaultCategoryAlreadyImported` |
| `group_referenced` | `settings.groups.error.groupReferenced` |
| `field_not_editable` | `settings.general.error.fieldNotEditable` |
| `stale` (általános) | `common.error.stale` |
| `recipient_email_duplicate` | `settings.recipients.error.emailDuplicate` |
| `unsupported_media_type` | `settings.general.logo.error.unsupportedMediaType` |
| `file_too_large` | `settings.general.logo.error.fileTooLarge` |

#### 4.5.4 Esemény-napló — nincs

A négy aloldal egyik entitása sem hordoz `ActivityLog`-szerű esemény-naplót
(3.7). A felületen csak `AuditableEntity` "Létrehozva" / "Módosítva" sorok
(csoport-adatlap, Általános oldal audit-sora).

#### 4.5.5 Konfliktus- és státusz-banner-ek

| Esemény | Banner |
|---|---|
| Mentés sikeres | Toast: "Mentés sikeres" (zöld, 3 mp) |
| `409 stale` | Inline figyelmeztetés a `[validationForm]` tetején: "A beállítások időközben módosultak — frissítettük a felületet. Kérlek, ellenőrizd és mentsd újra." |
| `403` | Standard admin-minta hozzáférés-megtagadva-oldal — Dashboardra |
| Hálózati hiba | Toast: "Nem sikerült menteni — ellenőrizd az internetkapcsolatot." |

---

## 5. Polgári mobilapp adatigénye

A `50_konfig` négy aloldalának nagy része *belső* admin-konfiguráció, amit a
polgári app nem hív közvetlenül. Két érintkezési pont van.

### 5.1 Érintett Flutter képernyők

| Képernyő | Mire használja | Adat-forrás |
|---|---|---|
| `screen_uj_bejelentes_2.PNG` | Új bejelentés — gyökér-kategória-csempék | `Category` (gyökerek), `iconRef` |
| `screen_tovabbi_menu.PNG` | "Továbbiak" → Ügyfélszolgálat | `Tenant.contactAddress`/`contactPhone`/`contactEmail` (NY-K2) |
| `screen_kezdooldal_*.PNG` | Fejléc — önkormányzati név + ikon | `Tenant.name`, potenciálisan `logoFileRef` (NY-K2) |

### 5.2 A kategória-fa olvasása — megerősítés

A polgári app a gyökér-kategóriákat csempe-formában mutatja. Az alkategória-
választás a polgári oldalon **nem kötelező** (SD-18, `00_domain_model.md`
2.1) — a polgár dönthet úgy, hogy csak gyökér-szintig kategorizál.

**Adatáramlás-irány:** olvasás (a polgár nem ír a `Category` táblába).

**API-hívás (potenciális):** `GET /v1/lookup/categories` vagy a polgári app
saját kategória-lookupja. A pontos végpont a **polgári adatigény-spec**
dolga.

**A `50_konfig` szerepe:** a `Category` rekordokat a vezető megfelelően
kezeli — aktív/inaktív, sorrendezett, ikonos. A polgári app csak az aktív
kategóriákat látja, `sortOrder` szerint rendezve, `iconRef`-fel. A
`50_konfig` ezt biztosítja; a polgári app oldali szerződést nem köti.

**Megerősítés, nem új adatigény.**

### 5.3 A tenant-kontakt mint közös adatforrás — nyitott

A `50_konfig_v2.md` 6.3 javaslat-szintű döntést hozott: "egy adat, egy hely"
— a tenant kontakt-adatai a manager Beállításokban élnek, és a polgári app
onnan olvassa. A `screen_tovabbi_menu.PNG` "Ügyfélszolgálat" menüpontja a
potenciális olvasói pont.

**A feature-spec szerver-oldali felkészültsége:**

- A `Tenant.contactAddress`/`contactPhone`/`contactEmail` mezők elérhetők
  (SD-58, 3.3.1).
- A `GET /v1/tenant-settings` végpont **csak `tenant_manager`-nek** szól —
  a polgári app nem éri el ezzel a route-tal.
- A polgári app számára **külön, publikus (vagy citizen-auth-os) olvasó-
  végpontra** lesz szükség (pl. `GET /v1/public/tenant-info` vagy
  `GET /v1/citizen/tenant-info`), amely a `Tenant.name`, `logoFileRef`/
  `logoUrl`, `contactAddress`, `contactPhone`, `contactEmail` mezőket adja
  vissza — érzékeny mezők (`code`, `dbConnectionRef`, `status`,
  `displayPrefix`, `timeZone`) **nélkül**.

**Mi marad nyitva:** a pontos route, az auth-modell, a logó-tartalmazás
ténye. **Nyitott kérdés — NY-K2** (8.4).

### 5.4 Mit nem érint a polgári app

- Az admin-konfigurációs CRUD-ot semmilyen formában.
- A `Group` entitást egyáltalán nem ismeri.
- A `WeeklyReportRecipient`-listát nem ismeri.
- A `DefaultCategoryCatalog`-ot nem ismeri.

---

## 6. Acceptance criteria

44 kritérium, hét blokkban. Mindegyik gépiesen tesztelhető — Given állapot,
When akció, Then megfigyelhető eredmény.

### AC-K — Kategóriák

#### AC-K1 — Lista

**AC-K1.1** — *Given* a tenant DB-ben N gyökér- és M alkategória-rekord,
*When* a vezető a `GET /v1/categories`-t hívja, *Then* a válasz az N+M
rekordot lapos formában tartalmazza, mindegyiken `parentId` egyértelmű
(gyökérnél `null`, alkategóriánál a szülő-id).

**AC-K1.2** — *Given* aktív és inaktív rekordok keverve, *When* a
`GET /v1/categories?isActive=true`-t hívja, *Then* a válasz kizárólag az
aktív rekordokat tartalmazza.

**AC-K1.3** — *Given* egy gyökér-kategória öt nyitott (`status IN (New,
Assigned, InProgress)`) és három lezárt (`Resolved`/`Rejected`) `Ticket`-tel,
*When* a `GET /v1/categories`-t hívja, *Then* a sor `ticketCount` mezője
`5`.

**AC-K1.4** — *Given* egy gyökér két aktív és egy inaktív alkategóriával,
*When* a `GET`-et hívja, *Then* a gyökér-sor `hasActiveChildren` `true`.

**AC-K1.5** — *Given* default rendezés, *When* a `GET`-et hívja `sort`
paraméter nélkül, *Then* a sorrend: gyökerek `sortOrder ASC`, és minden
gyökér alatt az alkategóriák szintén `sortOrder ASC`.

#### AC-K2 — Alkategória létrehozása

**AC-K2.1** — *Given* egy aktív gyökér `parentId = 5`, *When* a vezető
`POST /v1/categories`-t hív érvényes `parentId = 5`, `name = "kátyú"`-vel,
*Then* új `Category` jön létre `parentId = 5`, `isActive = true`,
`sourceCatalogId = null`, válasz `201`.

**AC-K2.2** — *Given* `POST`-hívás `parentId = null`-lal, *When* feldolgozza,
*Then* válasz `409`, `reason = "root_must_come_from_default"`.

**AC-K2.3** — *Given* `POST` `parentId` alkategóriára mutat, *When*
feldolgozza, *Then* `409 category_depth_exceeded`.

**AC-K2.4** — *Given* `POST` `parentId` inaktív gyökérre, *When* feldolgozza,
*Then* `422 parent_inactive`.

**AC-K2.5** — *Given* `POST` üres/whitespace `name`-mel, *When* feldolgozza,
*Then* `400`, `fieldErrors.name`.

#### AC-K3 — Gyökér importálása

**AC-K3.1** — *Given* Core `DefaultCategoryCatalog` gyökér `id = 10` három
alkategóriával, és a tenant DB-ben még nincs `sourceCatalogId = 10`, *When*
`POST /v1/categories/from-default { defaultCategoryId: 10 }`, *Then* a tenant
DB-ben létrejön a gyökér `sourceCatalogId = 10`-zel, és mind a három
alkategória `sourceCatalogId`-val a Core-forrás-id-jaikra; válasz `201` a
gyökérrel és `children`-listával.

**AC-K3.2** — *Given* nem létező `defaultCategoryId`, *When* `POST
/from-default`, *Then* `404`.

**AC-K3.3** — *Given* `defaultCategoryId` alkategóriára, *When* `POST`, *Then*
`422 not_a_root_default`.

**AC-K3.4** — *Given* tenant DB-ben már létezik `sourceCatalogId = <id>`,
*When* újra importálni próbálja, *Then* `409 default_category_already_imported`.

**AC-K3.5** — *Given* sikeres import, *When* a tenant DB lekérdezhető,
*Then* az importált gyökér `sortOrder`-je `max(sortOrder) + 1`.

#### AC-K4 — Importálható gyökerek

**AC-K4.1** — *Given* Core-ban öt default-gyökér, a tenant DB-ben kettőre
már `Category`-rekord, *When* `GET /v1/categories/available-default-roots`,
*Then* a válasz három még nem importált gyökeret tartalmaz.

**AC-K4.2** — *Given* minden default importálva, *When* `GET
/available-default-roots`, *Then* üres lista, `200 OK`.

**AC-K4.3** — *Given* default-gyökér három alkategóriával, *When* a válaszban
szerepel, *Then* `childCount = 3`.

#### AC-K5 — Szerkesztés

**AC-K5.1** — *Given* aktív `Category`, *When* `PUT /v1/categories/{id}`
érvényes `name`, `iconRef`, `isActive`, helyes `expectedUpdatedAt`-tel, *Then*
mezők frissülnek, válasz `200`.

**AC-K5.2** — *Given* `PUT`-kérés tartalmaz `parentId` mezőt (eltérő az
eredetitől), *When* feldolgozza, *Then* `400`, `fieldErrors.parentId =
"field_not_editable"`.

**AC-K5.3** — *Given* `PUT`-kérés `sourceCatalogId` mezővel, *When*
feldolgozza, *Then* `400`, `fieldErrors.sourceCatalogId = "field_not_editable"`.

**AC-K5.4** — *Given* két párhuzamos szerkesztés, *When* a második régi
`expectedUpdatedAt`-tel megy, *Then* a második `409 stale`.

**AC-K5.5** — *Given* gyökér-kategória (akár termékesített, akár tenant-
egyedi), *When* a vezető átnevezi, *Then* `200` (a gyökér átnevezése
megengedett).

#### AC-K6 — Törlés delete-guard

**AC-K6.1** — *Given* `Category`-ra mutat legalább egy `Ticket.categoryId`,
*When* `DELETE`, *Then* `409 category_referenced`, `details.directTicketCount > 0`.

**AC-K6.2** — *Given* gyökér, amelyre közvetlenül nincs `Ticket`, de
alkategóriájára van, *When* `DELETE` a gyökérre, *Then* `409 category_referenced`,
`details.descendantsWithTickets` az érintett alkategóriával.

**AC-K6.3** — *Given* sem közvetlen, sem leszármazott `Ticket`-hivatkozás
nincs, *When* `DELETE`, *Then* a rekord (és gyökérnél: alkategóriái is)
törlődnek, válasz `204`.

**AC-K6.4** — *Given* `Category`-ra mutat `Ticket.citizenSuggestedCategoryId`
(a polgár javasolta, de a triage felülírta), *When* `DELETE`, *Then* `409
category_referenced` (a `citizenSuggestedCategoryId`-hivatkozás is védi).

#### AC-K7 — Reorder

**AC-K7.1** — *Given* négy gyökér `sortOrder = [0,1,2,3]`, *When*
`POST /reorder { parentId: null, ids: [3,1,2,0] }`, *Then* a négy gyökér
`sortOrder`-je az `ids` sorrendje szerint frissül `[0,1,2,3]`-ra.

**AC-K7.2** — *Given* `reorder`-kérés `parentId = 5`-tel és `ids = [10,11,99]`-
cel, ahol a `99` más gyökérhez tartozik, *When* feldolgozza, *Then* `400`,
`fieldErrors.ids[2] = "wrong_scope"`.

**AC-K7.3** — *Given* duplikált `ids`-tömb, *When* feldolgozza, *Then* `400`,
`fieldErrors.ids`.

### AC-G — Csoportok

**AC-G1.1** — *Given* három `Group` különböző tagsággal, *When*
`GET /v1/groups`, *Then* minden sor `memberCount`-ja a taglista méretét
tükrözi.

**AC-G1.2** — *Given* egy `Group`, *When* `GET /v1/groups/{id}`, *Then* a
`GroupDto.members` a tagok `displayName`, `id`, `status`, `roles` mezőit
tartalmazza.

**AC-G2.1** — *Given* érvényes `CreateGroupRequest` üres `tenantUserIds`-zel,
*When* a vezető hívja, *Then* új `Group` `isActive = true`-val, üres
taglistával, válasz `201`.

**AC-G2.2** — *Given* `PUT` három aktív `TenantUser`-rel a `tenantUserIds`-
ben, *When* feldolgozza, *Then* a `Group.Members` három sort tartalmaz, `200`.

**AC-G2.3** — *Given* `PUT` `Invited` vagy `Disabled` `TenantUser` ID-val,
*When* feldolgozza, *Then* `400`, `fieldErrors.tenantUserIds[i]`.

**AC-G3.1** — *Given* `Group`-ra `Ticket.assignedGroupId`-val mutat ügy,
*When* `DELETE /v1/groups/{id}`, *Then* `409 group_referenced`.

**AC-G3.2** — *Given* nincs `Ticket`-hivatkozás, *When* `DELETE`, *Then* a
rekord és join-sorok törlődnek, `204`.

**AC-G4.1** — *Given* `Group.isActive = false`, *When* a triage-flow a
`groups` lookup-ot kéri, *Then* az inaktív csoport nem szerepel.

**AC-G4.2** — *Given* `Ticket.assignedGroupId` egy deaktivált `Group`-ra,
*When* a `Ticket` adatlapja megnyílik, *Then* a felelős-mező az inaktív
csoport nevét mutatja, nem `null`-ra állítódik.

### AC-T — Tenant-settings

**AC-T1.1** — *Given* `Tenant`-rekord Core DB-ben, *When* `GET
/v1/tenant-settings` a `Tenant: almadi` headerrel, érvényes JWT-vel, *Then*
`200` + `TenantSettingsDto` (`code`, `name`, `displayPrefix`, `timeZone`,
`logoFileRef`, `logoUrl`, `contactAddress`, `contactPhone`, `contactEmail`,
`updatedAt`, `updatedBy`).

**AC-T1.2** — *Given* a `GET` válasza, *When* megvizsgáljuk, *Then* a DTO
**nem** tartalmaz `dbConnectionRef`, `status`, `id` mezőt.

**AC-T1.3** — *Given* `Tenant.logoFileRef = null`, *When* `GET`, *Then*
`logoUrl` is `null`.

**AC-T2.1** — *Given* érvényes `UpdateTenantSettingsRequest` négy mezővel,
helyes `expectedUpdatedAt`-tel, *When* `PUT`, *Then* a négy mező frissül,
`200`.

**AC-T2.2** — *Given* `PUT` `contactEmail = null`-lal, *When* feldolgozza,
*Then* a `Tenant.contactEmail` `null`-ra áll, `200`.

**AC-T2.3** — *Given* `PUT` érvénytelen e-mail formátummal, *When*
feldolgozza, *Then* `400`, `fieldErrors.contactEmail`.

**AC-T3.1** — *Given* `PUT` `code` mezővel, amely eltér az eredetitől, *When*
feldolgozza, *Then* `400`, `fieldErrors.code = "field_not_editable"`.

**AC-T3.2** — *Given* `PUT` `displayPrefix`, `timeZone`, `dbConnectionRef`,
`status` vagy `logoFileRef` mezővel (bármelyikkel, az eredetitől eltérően),
*When* feldolgozza, *Then* `400`, `fieldErrors.<mezo> = "field_not_editable"`.

**AC-T3.3** — *Given* `PUT`, ahol a tiltott mezők értéke egyezik az
eredetivel, *When* feldolgozza, *Then* `200` (idempotens).

**AC-T4.1** — *Given* két párhuzamos szerkesztés, *When* a második régi
`expectedUpdatedAt`-tel, *Then* `409 stale`.

**AC-T5.1** — *Given* `tenant_manager_almadi` szerepkörű vezető, *When*
`Tenant: praga` headerrel `GET /v1/tenant-settings`-t hív, *Then* `403` a
`TenantAccessMiddleware`-en.

### AC-L — Logó

**AC-L1.1** — *Given* érvényes 500 KB PNG, *When* `POST /v1/tenant-settings/logo`
a `file` mezőben, *Then* S3-ban új objektum `tenants/almadi/logo/{guid}.png`
kulccsal, `Tenant.logoFileRef` az új kulcsra, válasz `200`.

**AC-L1.2** — *Given* korábbi logó és új feltöltés, *When* az új sikeres,
*Then* az új kulcs a `logoFileRef`-ben, a korábbi S3-objektum törlésre kerül
`AfterSaveAsync`-ben (a válasz ettől nem függ).

**AC-L1.3** — *Given* korábbi S3-törlés meghiúsul, *When* az új logó
egyébként sikeres, *Then* válasz `200` (a kérés sikere nem függ a törléstől),
a hiba naplózott.

**AC-L2.1** — *Given* 3 MB fájl, *When* `POST`, *Then* `400 file_too_large`.

**AC-L2.2** — *Given* GIF vagy WebP, *When* `POST`, *Then* `400
unsupported_media_type`.

**AC-L2.3** — *Given* PNG-deklarált `Content-Type`, de a magic bytes JPEG,
*When* `POST`, *Then* `400 unsupported_media_type`.

**AC-L2.4** — *Given* érvényes SVG, *When* `POST`, *Then* `200`.

**AC-L3.1** — *Given* `Tenant.logoFileRef != null`, *When* `DELETE
/v1/tenant-settings/logo`, *Then* S3-objektum törölve, `logoFileRef = null`,
válasz `204` (vagy `200` a DTO-val — fejlesztői döntés).

**AC-L3.2** — *Given* `logoFileRef = null`, *When* `DELETE`, *Then* `204`
(idempotens).

### AC-R — Riport-címzettek

**AC-R1.1** — *Given* három `WeeklyReportRecipient`, *When*
`GET /v1/weekly-report-recipients`, *Then* a válasz a három rekordot
tartalmazza `createdAt ASC` rendezésben.

**AC-R1.2** — *Given* aktív és inaktív címzettek keverve, *When*
`GET ...?isActive=true`, *Then* csak az aktívak.

**AC-R1.3** — *Given* `40_riport.md` SD-57 fogyasztás: a riport-adatlap a
`GET /v1/weekly-report-recipients?isActive=true`-t hívja a `tenant_manager`
jogosultsággal, *When* a Címzettek szekciót rendereli, *Then* a megjelenített
címzettek pontosan a `50_konfig` aktuális (`isActive == true`) listájával
egyezőek (SD-57 megerősítés, új végpont nélkül).

**AC-R2.1** — *Given* érvényes `CreateWeeklyReportRecipientRequest` `name =
"Dr. Polgár Péter"`, `email = "peter@almadi.hu"`, *When* a vezető hívja,
*Then* új rekord, `isActive = true`, válasz `201`.

**AC-R2.2** — *Given* `CreateWeeklyReportRecipientRequest` `name = null`,
*When* hívja, *Then* rekord létrejön `name = null`, `201`.

**AC-R2.3** — *Given* már létezik `email = "peter@almadi.hu"`, *When*
ugyanezt újra próbálja, *Then* `409 recipient_email_duplicate`.

**AC-R2.4** — *Given* érvénytelen e-mail formátum, *When* hívja, *Then*
`400`, `fieldErrors.email`.

**AC-R3.1** — *Given* `WeeklyReportRecipient`, *When* `PUT` érvényes új
`email`-lel és helyes `expectedUpdatedAt`-tel, *Then* frissül, `200`.

**AC-R3.2** — *Given* `PUT` az `email`-t egy másik létező címzett e-mailjére
állítaná, *When* hívja, *Then* `409 recipient_email_duplicate`.

**AC-R3.3** — *Given* `WeeklyReportRecipient`, *When* `DELETE`, *Then* a
rekord törlődik, `204` — guard nem aktiválódik.

**AC-R3.4** — *Given* `WeeklyReportRecipient` `isActive = false`-ra, *When*
a következő heti riport-generálás (`40_riport.md` 3.6), *Then* ez a címzett
nem kap e-mailt.

### AC-J — Jogosultság és tenant-szigetelés

**AC-J1.1** — *Given* az `authorization.json` 3.5 szakasz szerinti
bejegyzései, *When* a route-szabályok kiértékelődnek, *Then* minden `50_konfig`
végpont kizárólag a `tenant_manager` (a `Tenant` header kódjával kiegészített)
szerepkört engedi.

**AC-J1.2** — *Given* `tenant_dispatcher_almadi` szerepkörű felhasználó,
*When* bármely `50_konfig`-védett végpontot hív, *Then* `403`.

**AC-J1.3** — *Given* `tenant_content_manager_almadi` szerepkörű felhasználó,
*When* `GET /v1/categories`-t hív, *Then* `403`.

**AC-J1.4** — *Given* `tenant_field_worker_almadi` szerepkörű felhasználó,
*When* bármely `50_konfig`-végpontot hív, *Then* `403`.

**AC-J2.1** — *Given* két tenant `almadi` és `praga` saját rekordokkal,
*When* a `tenant_manager_almadi` vezető a `Tenant: almadi` headerrel
`GET /v1/categories`-t hív, *Then* a válasz kizárólag almadi-tenant DB
rekordokat (szerkezeti szigetelés).

**AC-J2.2** — *Given* a vezető saját tenantja `Category`-id-jával hív
(létezik), *When* `GET /v1/categories/{id}`, *Then* `200`.

**AC-J2.3** — *Given* `tenant_manager_almadi` szerepkörű, *When* `Tenant:
praga` headerrel hív, *Then* `403` a `TenantAccessMiddleware`-en.

**AC-J2.4** — *Given* a cross-tenant kísérlet, *When* `403`-mal zár, *Then*
biztonsági eseményként naplózódik (SD-13).

**AC-J3.1** — *Given* a vezető `GET /v1/categories/available-default-roots`-
ot hív, *When* a végpont feldolgozza, *Then* két lekérdezés: Core
(`DefaultCategoryCatalog` gyökerek) és tenant (`Category.sourceCatalogId`-
listák); a különbség a válasz.

**AC-J3.2** — *Given* a `available-default-roots` jogosultsága, *When* hív,
*Then* csak `tenant_manager`-nek engedélyezett — más szerepkör `403`.

### AC-N — Navigáció

**AC-N1.1** — *Given* a manager főnavigáció, *When* a vezető a "Beállítások"-
ra kattint, *Then* lenyíló almenü négy aloldal-linkkel.

**AC-N1.2** — *Given* `/beallitasok` URL-re navigálás, *When* a router
feldolgozza, *Then* redirect `/beallitasok/kategoriak`-ra.

**AC-N1.3** — *Given* nem-`tenant_manager` felhasználó, *When* a manager
felület betöltődik, *Then* "Beállítások" főmenü-tétel **nem jelenik meg**.

**AC-N2.1** — *Given* a vezető `/beallitasok/kategoriak`-on, *When* a
felület betöltődik, *Then* breadcrumb "Beállítások › Kategóriák", ahol
"Beállítások" nem klikkelhető link.

**AC-N2.2** — *Given* a vezető `/beallitasok/csoportok/details/<id>`-en,
*When* a felület betöltődik, *Then* breadcrumb "Beállítások › Csoportok ›
<csoport neve>".

**AC-N3.1** — *Given* a vezető a négy aloldal egyikén, *When* a felület
betöltődik, *Then* a page title `settings.<aloldal>.title` i18n-kulcsra
képeződik le (4.1).

---

## 7. Keresztmetszeti

### 7.1 i18n

**Érdemi.** A négy aloldal teljes UI-szöveg-készlete kulcs-alapú
(`hu.json`), 4.5.3 részletes listával. A `[validationForm]`-minta a szerver-
oldali field error-okat magyarra fordítja az i18n-kulcsokon keresztül; a
hibakódok és `reason`-kódok explicit kulcsra képeződnek. A felület tegez.

Két paraméteres kulcs különös figyelmet érdemel:

- `settings.categories.error.categoryReferenced` — `{ticketCount}`
  paraméterrel a delete-guard kimenetelét közvetíti.
- `settings.recipients.confirm.deleteBody` — a címzett `email`-jét és/vagy
  `name`-jét tartalmazza.

### 7.2 Időzóna

**Érdemi, de nem feature-specifikus.** A négy érintett entitás `createdAt`/
`updatedAt` mezei UTC-ben tárolódnak (SD-9), tenant-időzónában jelennek meg
(`Tenant.timeZone`, default `Europe/Budapest`, SD-10). Platform-szintű minta
(`01_kozos_mintak.md` 5.2); a feature-spec **fogyasztja**.

### 7.3 Teljesítmény

**Megjegyzés-szinten releváns.** A négy aloldal API-ja kis volumenű (egy
tenant: ~20 kategória, 2–4 csoport, 1 `Tenant`-rekord, 1–3 címzett). Az
offset-paginálás (SD-11) bőven elég.

Egy kiemelendő pont: a `GET /v1/categories/available-default-roots` **cross-
DB olvasás** a működési útvonalon kívül. A két szekvenciális `SELECT` a
pilot-volumenen elhanyagolható késleltetést ad.

### 7.4 Biztonság

**Érdemi.** Több kétrétegű érvényesítés:

- **Tenant-szigetelés** — Tenant DB-ben szerkezeti, Core `Tenant`-on
  logikai (3.6).
- **Cross-tenant kísérlet naplózott** — SD-13.
- **Tiltott mezők védelme** — a `Tenant`-PUT-on (SD-61) a hat tiltott mezőre
  `field_not_editable`-lel zár.
- **Logó-feltöltés MIME + magic bytes** — header-hazudás ellen.
- **Logó-méret-limit** — DoS-jellegű terhelés-támadás ellen.

### 7.5 Reszponzivitás

**Nem releváns érdemben.** A `/beallitasok/*` aloldalak admin-felületi,
asztali használatra optimalizáltak; a triage- és bejelentés-feature
reszponzivitási követelménye (`10_bejelentes_lista_es_adatlap.md` 4.7, Wow
#6) nem terjed ki ide. Pilotra nem cél; iterációba (8.2).

---

## 8. Lezárás

### 8.1 Első kiadás (MVP) — a piloton (T0)

- `/beallitasok` lenyíló almenü redirect-tel a `/beallitasok/kategoriak`-ra.
- `/beallitasok/kategoriak` aloldal: csoportosított fa-lista (SD-64 szülő-
  hatókör), inline-szerkesztés (név, `isActive` toggle), gyökér-importálás
  dialógus a default-katalógusból (SD-62), új alkategória modal,
  sorrendezés drag-and-drop, delete-guard (SD-63).
- `/beallitasok/csoportok` aloldal: lista + adatlap + szerkesztő, taglista
  mint `[ManyToManyConnection]` denormalizált megjelenítő-DTO-val (SD-65),
  delete-guard.
- `/beallitasok/felhasznalok` aloldal: a `20_felhasznalokezeles.md` 4.
  szakasz fedi.
- `/beallitasok/altalanos` aloldal (SD-67): egyetlen szerkesztő-űrlap négy
  szekcióval — tenant-alapadatok (`name` szerkeszthető; `code`,
  `displayPrefix`, `timeZone` olvasásra), logó (feltöltés/csere/eltávolítás,
  SD-60), kontakt-adatok, riport-címzettek (`WeeklyReportRecipient` mini-
  lista, SD-59).
- A `40_riport.md` SD-57 fogyasztói várakozás teljesítése: `GET
  /v1/weekly-report-recipients?isActive=true` mind a `50_konfig` Címzettek
  szekciójának, mind a riport-adatlap Címzettek szekciójának forrása.
- Teljes vezető-only jogosultság (SD-66) a 22 új route-on.
- Tenant-szigetelés szerkezeti (Tenant DB) és logikai (`TenantSettingsController`)
  szinten.

### 8.2 Következő iterációk (listázva, nem specifikálva)

- **`Group.sortOrder` és `POST /v1/groups/reorder`** — ha a csoport-lista
  bővül és a sorrend értelmessé válik.
- **Bulk-műveletek** a kategória- és csoport-listán.
- **Konfigurációs esemény-napló** (`ConfigurationAuditLog`) — ha a hivatali
  nyomvonal-igény kívánja (NY-K4).
- **S3-objektum-clean-up job** az árva logókra (NY-K3).
- **Háromszintű kategória-fa** — pilotra K-036 érvényesít.
- **Reszponzivitás a `/beallitasok/*` aloldalakra.**
- **Default-katalógus futás közbeni propagálása élő tenantokba** —
  `01_kozos_mintak.md` 4.4 elhalasztva.
- **A `Tenant.timeZone` tenant-vezető szintű szerkesztése** — pilotra
  `urbino_admin`-hatáskör.
- **Több-tagú kontakt-modell** (külön ügyfélszolgálati cím vs. levelezési
  cím) — SD-58 alternatívájaként.
- **Konfigurálható munkafolyamatok, SLA-mátrix** — K-016.
- **Finomszemcsés (mezőnkénti) jogosultság** — K-016.

### 8.3 Feltételezések (explicit lista)

1. A multi-tenancy modell zárt — SD-1 érvényes; a Tenant DB-ben élő
   entitások szerkezeti tenant-szűréssel, a Core `Tenant` logikai szűréssel.
2. A `BaseController` minta és a `[validationForm]` admin-minta működő
   keret — a feature-spec ezekre épít.
3. A `[ManyToManyConnection]` attribútum a `Group.Members` n-n kapcsolaton
   standard auto-mapping.
4. A `DefaultCategoryCatalog` Core DB-ben él, provisioning-időben másolódik
   a tenant DB-be, a `sourceCatalogId` hivatkozik a forrásra (SD-8). **Lásd
   NY-K1.**
5. Az S3 mint blob-storage az `Attachment.fileRef`-mintával konzisztens
   (SD-15) — a logó `tenants/{code}/logo/{guid}.{ext}` kulcsséma fejlesztői
   döntés.
6. A polgári mobilapp tenant-resolution-je külön kérdés — polgári adatigény-
   spec.
7. A felhasználó-aloldal feature-spec külön dokumentumban —
   `20_felhasznalokezeles.md`.
8. A `Tenant.status` állapotgép-átmenetek `urbino_admin`-hatáskörűek
   (K-024) — a feature-spec nem érinti.
9. A `40_riport.md` 8.2 (3) feltételezése formálisan teljesül a
   `GET /v1/weekly-report-recipients` standard CRUD listájával — külön
   olvasó-végpont nem keletkezik (SD-66).
10. A logó-feltöltés implementációs mintája (direkt-stream vagy presigned-
    URL) fejlesztői döntés — a route-szerződés köti, a belső megvalósítás
    szabad.
11. A pilot-volumen kicsi — az offset-paginálás (SD-11) és a denormalizált
    aggregátum-mezők ehhez méretezve elég.

### 8.4 Nyitott kérdések — köztük a megtartott hiányok

| # | Tárgy | Típus | Hatáskör |
|---|---|---|---|
| **NY-K1** | A `DefaultCategoryCatalog` formális mezőtáblája hiányzik a `00_domain_model.md` 4. blokkjából. A feature-spec API-szerződései az alapmezőkre (`id`, `name`, `iconRef`, `parentId`) építenek, amelyek a `01_kozos_mintak.md` 4. szakaszából egyértelműek. **Nem blokkol:** az API-szerződések egyértelműek; a domain-modell egy későbbi pontosítása (a `60_tartalom`-spec mellett vagy után) rögzíti. | Megtartott hiány | A `00_domain_model.md` 4. blokk frissítése |
| **NY-K2** | A polgári mobilapp olvasói szerződése a tenant-kontakt-adatokra (és potenciálisan a logóra). A `Tenant.contactAddress`/`contactPhone`/`contactEmail` mezőket egy publikus vagy citizen-auth-os olvasó-végpontnak el kell érhetővé tennie. **Nem blokkol:** a mezők rendelkezésre állnak (SD-58); a polgári-oldali route, auth-modell, és a kiküldött szűkített DTO formája a polgári adatigény-spec hatásköre. | Nyitott kérdés | Polgári adatigény-spec |
| **NY-K3** | Az S3-objektum-clean-up árva logók takarítására. A `Tenant.logoFileRef` cseréjekor az `AfterSaveAsync` törli a régi S3-objektumot; meghiúsulás esetén árva marad. Egy időszakos clean-up job a megoldás. **Nem blokkol:** árva S3-objektumok nem zavarják a működést, csak helyet foglalnak. | Megtartott hiány | Üzemeltetési / iterációs |
| **NY-K4** | Konfigurációs esemény-napló (`ConfigurationAuditLog`). A négy érintett entitás `AuditableEntity` "ki/mikor utoljára"-szinten audit-olt; mezőnkénti változás-történet nincs. **Nem blokkol:** a pilot-igényre az `AuditableEntity` elég (a `20_felhasznalokezeles.md` 3.9 ugyanezt rögzítette a felhasználó-oldalon). Ha a hivatali nyomvonal-igény kívánja, iterációs elem. | Megtartott hiány | Iteráció (6-12. hó) |

### 8.5 Új tervezési döntések a feature-spec hatókörében

| SD | Tárgy |
|---|---|
| **SD-58** | `Tenant` új mezői: `logoFileRef`, `contactAddress`, `contactPhone`, `contactEmail` |
| **SD-59** | Új entitás: `WeeklyReportRecipient` (Tenant DB, `AuditableEntity`) |
| **SD-60** | Logó dedikált `POST/DELETE /v1/tenant-settings/logo` végponton, nem `Attachment` — új minta ("egyedi-fájl-mező") |
| **SD-61** | `Tenant`-PUT korlátozott mezőkészletre, `TenantSettingsController` mint dedikált controller; "Core-mezőkre korlátozott tenant-szintű írás" minta |
| **SD-62** | Új gyökér: `POST /v1/categories/from-default`; standard `POST` csak alkategóriára; `GET /v1/categories/available-default-roots` cross-DB ritka konfig-végpont |
| **SD-63** | `Category` delete-guard (`409 category_referenced`); deaktiválás az elsődleges út. A `Group` delete-guard-ja ennek a mintájának kiterjesztése (nem önálló SD) |
| **SD-64** | `Category`-reorder szülő-hatókörben (`parentId` paraméter); a kategória-lista csoportosított UI-megfelelője |
| **SD-65** | `Group` standard CRUD, taglista `[ManyToManyConnection]`-nel — megerősítés |
| **SD-66** | `authorization.json` finomított akciókkal, `tenant_manager`-only; a riport-fogyasztás új végpont nélkül teljesül (`GET /v1/weekly-report-recipients`) |
| **SD-67** | "Általános" aloldal: egyetlen képernyő három mentési-modellel (`[validationForm]` + logó-azonnali + címzettek-soronkénti) |

### 8.6 Visszacsorgó jelzés

**VF-K1 — A `News.coverImage` (és más, jövőbeli egyedi-kép-mezők) az SD-60
mintáját követhetik.** Az SD-60 "egyedi-fájl-mező" mintája általánosítható a
`60_tartalom`-feature-spec borítókép-kezelésére is. **Nem ennek a feature-
specnek a dolga eldönteni** — a `60_tartalom`-spec dönt, az SD-60-at
modellként használhatja vagy az `Attachment`-általánosítást választhatja.
**Cél-dokumentum:** `60_tartalom`-feature-spec.

### 8.7 Hivatkozott dokumentumok

**Funkcionális réteg (kötelező bemenet):**

- `50_konfig_v2.md` — a feature funkcionális terve.
- `00_architektura_v4.md` — 3.2 (lenyíló almenü), 3.3 (URL-struktúra), 7.2
  (fejléc), 8.2/8.4 (kategória-kérdések).
- `05_jogosultsagok_v2.md` — 2.8 (vezető-only Beállítások), 4.3 (kétrétegű
  érvényesítés).
- `90_sitemap_v3.md` — 2.6 (a négy aloldal nézet-leltára), 5.4 (modulközi
  nyitott kérdések).
- `40_riport.md` — 3.3 (R-2/R-3), 4.4 (Címzettek szekció), 8.2 (3)
  (feltételezés), SD-57.
- `20_felhasznalokezeles.md` — a felhasználó-aloldal feature-specje.

**Domain-réteg:**

- `00_domain_model.md` — 2.1 (`Category`), 2.2 (`Group`), 3.2 (`Tenant`), 4.
  blokk (`DefaultCategoryCatalog` — hiányos, NY-K1).
- `00_terminologia.md` — tenant-fogalmak, kategória, csoport.

**Platform-réteg:**

- `01_kozos_mintak.md` — 1.3 (DB-szintek), 1.4 (cross-DB elv), 1.6 (`Tenant`-
  regisztrum, SD-4), 3.3 (route-prefix-minta), 4.2 (hibrid katalógus-modell,
  SD-8), 5.2 (időzóna), 5.3-5.5 (i18n), 6.3 (hibakezelés), 7.1 (audit).
- `CLAUDE.md` — `BaseController`, `[ManyToManyConnection]`,
  `LookupLoaderService`, `NavStore`, `TableStateConfig`.
- `project_backend_v2.md` — Tenant Database, `TenantAccessMiddleware`.
- `project_backend_client_angular.md` — `[validationForm]`, `<form-field>`,
  `ValidationInterceptor`.

**Kanonikus döntések:** K-007, K-008, K-016, K-024, K-025, K-031, K-036, K-037.

**Specifikációs döntések:** SD-1, SD-2, SD-3, SD-4, SD-5, SD-6, SD-7, SD-8,
SD-9, SD-10, SD-11, SD-12, SD-13, SD-15, SD-18, SD-36, SD-37, SD-49—SD-57
(riport-feature), SD-58—SD-67 (a feature-spec új döntései).

---

## Verziónapló

- **v1.0 (2026.05.20)** — Első kiadás. A Beállítások modul három aloldalának
  (`/beallitasok/kategoriak`, `/beallitasok/csoportok`,
  `/beallitasok/altalanos`) fejlesztői specifikációja. A negyedik aloldal
  (`/beallitasok/felhasznalok`) a `20_felhasznalokezeles.md`-ben. Tíz új
  tervezési döntés (SD-58—SD-67): a `Tenant` mezőbővítése, a
  `WeeklyReportRecipient` új entitás, a logó-feltöltés dedikált végponttal
  ("egyedi-fájl-mező"-minta), a `TenantSettingsController` korlátozott
  mezőkészlettel, a kategória `from-default`-import és cross-DB ritka
  olvasó-végpont, a `Category` delete-guard (a `Group`-ra kiterjedéssel) és
  szülő-hatókörű reorder, a `Group` standard CRUD-megerősítés, az
  `authorization.json` 22 új route, az "Általános" aloldal három mentési-
  modellel. A `40_riport.md` SD-57 fogyasztói várakozása új végpont nélkül
  teljesül (`GET /v1/weekly-report-recipients`). Négy nyitott kérdés /
  megtartott hiány (NY-K1 a `DefaultCategoryCatalog` formális mezőtáblája,
  NY-K2 a polgári-oldali tenant-info olvasó, NY-K3 az S3 árva-logó-takarítás,
  NY-K4 a konfigurációs esemény-napló). Egy visszacsorgó jelzés (VF-K1) a
  `60_tartalom`-spec felé az SD-60 mintázat felhasználásáról. 44 acceptance
  criterion hét blokkban (AC-K, AC-G, AC-T, AC-L, AC-R, AC-J, AC-N).
