# Domain-modell — Urbino Specifikációs projekt

**Dokumentum-típus:** domain-modell referencia (élő)
**Státusz:** **teljes** — mind az öt blokk véglegesítve; a domain-modell
adatszerkezeti szinten kész
**Verzió:** v1.8
**Dátum:** 2026.05.20
**Cél olvasó:** senior fejlesztő + Claude Code
**Épít:** `00_terminologia.md`, `01_kozos_mintak.md`, `kanonikus_donek.md`,
`80_modulok/manager_felulet/` funkcionális dokumentumok, a két `CLAUDE.md`

> **Mire való ez a fájl.** A teljes Urbino-domain **egy helyen**: entitások,
> mezők, kapcsolatok, állapotgépek. Minden feature-spec **ide vezeti be** az
> új/módosított elemeit, és **innen hivatkozik** a meglévőkre. Ez a Claude
> Code egyik fő kontextusa.
>
> **Mi NEM ez.** Nem feature-spec és nem API-szerződés. Az entitások itt
> *adatmodell-szinten* állnak (mezők, típusok, kapcsolatok, DB-szint). Az
> API-végpontok, a validáció és a workflow a feature-specekbe tartoznak. Az
> állapotgép formális átmenet-táblája a `02_globalis_allapotgep.md`-be — itt
> csak az állapot-mező és a hozzá tartozó `enum` rögzül.
>
> **A névsíkokról.** A `00_terminologia.md` szerint: az entitás- és
> mezőnevek **angolul** (PascalCase az entitásoknak, camelCase a mezőknek);
> a felületi szöveg magyarul. Ez a fájl a kódbeli neveket rögzíti.
>
> **DB-szint jelölés.** Minden entitásnál jelölve, hogy **Core DB** vagy
> **Tenant DB** — az SD-1 kétszintű modell szerint.

---

## 0. Olvasási térkép és építési sorrend

Ez a fájl **blokkonként** épült. A jelen verzió **mind az öt blokkot**
tartalmazza, véglegesített állapotban — a domain-modell adatszerkezeti
szinten teljes.

| Blokk | Tartalom | Státusz |
|---|---|---|
| 1 | A `Ticket` és közvetlen kísérői (`Attachment`, `ActivityLog`, `TicketNote`) | ✓ véglegesítve |
| 2 | Referencia- és önálló operatív entitások: `Category`, `Group`, `TenantUser`, `WeeklyReport`, `WeeklyReportRecipient` | ✓ véglegesítve |
| 3 | Core DB: `User`, `Tenant`, `UserTenantRole`, `UserInvitation`, `DefaultCategoryCatalog` | ✓ véglegesítve |
| 4 | Tartalmi entitások: `News`, `Event`, `CityInfo` | ✓ véglegesítve |
| 5 | Entitás-térkép (mermaid) és záró összegzés | ✓ véglegesítve |

> **Miért blokkonként.** A `03_elso_spec_chatek_sorrendje.md` figyelmeztet:
> "itt sok iteráció lesz, ne siettesd". A `Ticket` (1. blokk) hivatkozik a 2.
> és 3. blokk entitásaira (`Category`, `TenantUser`) — ezeket a `Ticket`-nél
> **előre-hivatkozással** kezeljük, és a 2-3. blokk tölti ki őket. Ahol egy
> `Ticket`-mező egy még nem definiált entitásra mutat, a típusnál jelölve:
> "→ 2. blokk" / "→ 3. blokk".

---

## Jelölés-konvenciók

A mező-táblák oszlopai:

- **Mező** — a kódbeli név (camelCase).
- **Típus** — a logikai adattípus. A konkrét C#/EF Core leképezés a
  fejlesztőé; a domain-modell a logikai típust adja (`string`, `int`,
  `DateTime`, `enum`, `FK → Entitás`, stb.).
- **Köt.** — kötelezőség: **K** kötelező (NOT NULL), **O** opcionális
  (nullable), **F** feltételes (a feltétel a megjegyzésben).
- **Megjegyzés** — méret, default, üzleti jelentés, forrás.

A **`DateTime` mezők mind UTC-ben tárolódnak**, `timestamptz` oszloptípussal
(SD-9) — ezt a táblákban külön nem ismételjük, az itt rögzített konvenció.

Az **`AuditableEntity`-mezők** (`createdAt`, `createdBy`, `updatedAt`,
`updatedBy`) — ahol egy entitás ebből származik, a tábla **nem ismétli**
őket, csak jelzi: "Származik: `AuditableEntity`". A `CLAUDE.md` szerint ezeket
az `AppDbContext` automatikusan tölti.

---

## 1. A `Ticket` és közvetlen kísérői

### 1.1 Áttekintés — a bejelentés-gerinc

A `Ticket` a projekt központi entitása (`00_terminologia.md` 0., K-005). Ez a
blokk négy entitást rögzít:

| Entitás | DB-szint | Szerep |
|---|---|---|
| `Ticket` | Tenant DB | A bejelentés maga — a gerinc |
| `Attachment` | Tenant DB | A bejelentéshez csatolt fájlok (polgári fotók, lezárás-dokumentáció) |
| `ActivityLog` | Tenant DB | A bejelentés életciklus-eseményeinek append-only naplója (J-2) |
| `TicketNote` | Tenant DB | A bejelentéshez fűzött belső jegyzetek (több bejegyzés, szerzővel és időponttal) — TD-10 |

Mind a négy a **Tenant DB**-ben él — a bejelentés-gerinc tisztán tenant-operatív
adat (SD-1). Egyik sem hivatkozik közvetlenül a Core DB-re: a felhasználó-jellegű
hivatkozások a tenant-oldali `TenantUser`-re mutatnak (SD-2, → 2. blokk).

### 1.2 A `Ticket` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity` (`createdAt/By`, `updatedAt/By` automatikus)
**Kapcsolódó kanonikus döntések:** K-005, K-028, K-029, K-030, K-031

#### 1.2.1 Azonosítás és alapadatok

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | Belső, auto-increment elsődleges kulcs. **A felhasználónak soha nem jelenik meg** — lásd `ticketNumber` |
| `ticketNumber` | `int` | K | Tenant-szinten folyamatos, 1-től induló sorszám. A felhasználónak megjelenő azonosító ennek és a `Tenant.displayPrefix`-nek (→ 3. blokk) az összefűzése — pl. `ALM-234`. **A bejelentés létrejöttekor dől el, onnantól nem változik** (összevonás/elutasítás után is megmarad). Lásd SD-16 |
| `title` | `string` | K | A bejelentés címe. Max 200 karakter. Felületi: "cím". A polgári app-ból érkezőnél a polgár adja; manuális nyitásnál a diszpécser |
| `description` | `string` (hosszú) | O | A probléma leírása. Egyetlen szabad szöveges mező a cím mellett — TD-2. Felületi: "leírás". Max 4000 karakter. *A polgári Flutter-app szintén egyetlen leírás-mezőre módosul (NY-1 lezárva) — a `screen_uj_bejelentes_2` korábbi két-mezős változata elavult* |

> **Tervezési döntés — a megjelenítési azonosító (SD-16).** A belső `id`
> (auto-increment PK) **nem** jelenik meg a felhasználónak: lyukas lehet, és
> egy belső auto-increment kiszivárogtatása rossz gyakorlat. Helyette a
> `Ticket` egy `ticketNumber` mezőt kap — tenant-szinten folyamatos sorszám.
> Mivel a `Ticket` a Tenant DB-ben él (SD-1), a sorszám természetesen
> tenantonként fut: két város `ticketNumber`-e ütközés nélkül lehet egyaránt
> 234. A felhasználónak megjelenő azonosító a `Tenant.displayPrefix` + `-` +
> `ticketNumber` (pl. `ALM-234`) — így a polgári app **városokon átnyúló**
> "Bejelentéseim" listájában (a polgár több városnál is bejelenthet) a két
> azonos sorszámú ügy vizuálisan elkülönül. A `ticketNumber` ütközésmentes,
> tenant-szintű kiosztásának konkrét mintája (külön szekvencia vagy zárolt
> számláló a `BeforeSaveAsync`-ben) fejlesztői döntés — a `Ticket`-spec
> rögzíti. *(Döntésnapló: SD-16.)*

#### 1.2.2 Besorolás — a triage négy döntése

A K-031 négy triage-mezője. Mind a négy az `Új` állapotban tipikusan üres
(vagy default), és a triage tölti ki.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `categoryId` | `FK → Category` | F | A bejelentés **megerősített** kategóriája — bármely szintű `Category`-ra mutathat (gyökér vagy alkategória, SD-18). **Feltételes:** `Új` állapotban lehet üres; a `Kiosztva`-ba lépéshez kötelező |
| `citizenSuggestedCategoryId` | `FK → Category` | O | A polgár által a bejelentéskor választott kategória — a triage előtti, *nyers* javaslat (TD-3). Tipikusan gyökér-szintű (a polgári app gyökér-csempéket mutat). A triage a `categoryId`-t tölti ki (megerősítve vagy felülírva); ez a mező **megőrzi a polgár eredeti választását**. Riport-input: ha sok a `citizenSuggestedCategoryId` ≠ `categoryId` eltérés, a polgári app kategória-szövegei félrevezetők (`10` 5.3). `Manual`-eredetnél üres |
| `priority` | `enum TicketPriority` | K | `Low` / `Normal` / `High`. **Default: `Normal`** (K-031). Felületi: "prioritás" — Alacsony/Normál/Magas |
| `dueDate` | `DateTime` | O | A belső munkahatáridő. A triage automatikusan javasolja (kategória+prioritás szabály, `10` 3.3), a diszpécser felülírhatja. **Belső mérce, nem polgári ígéret.** `Új` állapotban tipikusan üres |
| `assignedGroupId` | `FK → Group` | F | A felelős **csoport** (lásd 2.2). A triage felelős-mezője csoportra **vagy** személyre oszt (TD-4). `Kiosztva`+ állapotban az `assignedGroupId` és az `assignedUserId` közül **pontosan egy** kitöltött — FluentValidation-szabály, a `Ticket`-spec rögzíti |
| `assignedUserId` | `FK → TenantUser` | F | A felelős **személy** (lásd 2.3). A csoport/személy kizárólagosságról lásd az `assignedGroupId` sort |

#### 1.2.3 Állapot

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `status` | `enum TicketStatus` | K | `New` / `Assigned` / `InProgress` / `Resolved` / `Rejected` (K-029). **Default: `New`** — kivéve a manuális nyitást azonnali triage-dzsel, ami egyenest `Assigned` (`10` 4.3). Az átmenet-szabályok a `02_globalis_allapotgep.md`-ben |
| `rejectionReasonCode` | `enum RejectionReason` | F | Az elutasítás strukturált oka: `NotInScope` / `NotValid` / `Duplicate` / `PrivateProperty` (`10` 5.3). **Feltételes:** kötelező, ha `status = Rejected`; egyébként üres |
| `rejectionReasonText` | `string` | O | Az elutasítás szabad szöveges indoklása — a `rejectionReasonCode` **opcionális** kiegészítése (TD-5). Max 1000 karakter. A strukturált kód adja a kötelező minimumot (riport-aggregációhoz, `10` 5.3); a szöveg a polgári kommunikációhoz hasznos. **Kivétel:** ha `rejectionReasonCode = Duplicate`, a `rejectionReasonText` **üres marad** (`null`). A duplikáció-hivatkozást strukturáltan az `originalTicketId` / `originalTicketDisplayId` mezők hordozzák; a felhasználónak megjelenő "Duplikátuma a(z) ALM-X-nek" szöveget a kliens állítja össze i18n sablon-kulcsból (`ticket.rejection.duplicate`, paraméter: `originalDisplayId`). A K-034 "eredeti-ügy hivatkozás" követelményét a strukturált mezőpár teljesíti. Lásd SD-40 |
| `resolutionNote` | `string` (hosszú) | O | A lezárás szöveges dokumentációja. Max 2000 karakter. Mezőként **opcionális** — a `Folyamatban → Lezárt` átmenethez **legalább egy** lezárás-fotó **vagy** `resolutionNote` kötelező (`10` 4.2, TD-7), de ez **átmenet-feltétel**, nem mező-kötelezőség. A feltételt a `02_globalis_allapotgep.md` rögzíti |

> **A visszanyitás indoka — nincs külön `Ticket`-mező (TD-6).** A korrekciós
> visszanyitás (`Lezárt → Folyamatban`) kötelező indoklása **nem** a
> `Ticket`-en él mezőként, hanem az `ActivityLog` `Reopened`-eseményének
> `note`-jában. Indok: egy ügyet többször is visszanyithatnak; egy `Ticket`-mező
> csak az utolsó indokot tartaná, és félrevezető lenne. A napló a helyes hely
> — minden visszanyitás saját, megőrzött bejegyzés.

#### 1.2.4 Helyszín

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `latitude` | `decimal` | F | A bejelentés helyének földrajzi szélessége. **Feltételes:** a polgári app-ból érkező bejelentés mindig tartalmaz koordinátát; a manuálisan nyitott esetleg nem (`20` 2.4) |
| `longitude` | `decimal` | F | Földrajzi hosszúság. Ugyanaz a feltétel, mint a `latitude`-nál — a kettő együtt jár |
| `addressText` | `string` | O | A helyszín szöveges címe (utcanév, házszám). A polgári app reverse geocodingból tölti; manuális nyitásnál a diszpécser írja. Max 300 karakter |

> **A `latitude`/`longitude` páros.** A kettő mindig együtt van értve — vagy
> mindkettő kitöltött, vagy egyik sem. A duplikáció-heurisztika térbeli jele
> (K-032, `20` 2.4) csak akkor értékelhető, ha van koordináta; koordináta
> nélküli bejelentésre a rendszer nem ad duplikáció-gyanút. A
> `00_domain_model.md` ezt mezőszinten nem kényszeríti ki (két külön nullable
> mező); a "együtt jár" szabály FluentValidation-szinten érvényesül — a
> `Ticket`-spec rögzíti.

#### 1.2.5 Eredet és bejelentő

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `origin` | `enum TicketOrigin` | K | A bejelentés eredete: `CitizenApp` (polgári mobilappból) / `Manual` (diszpécser nyitotta telefonos/e-mailes ügyből). A pilotra csak ez a két érték (TD-8); a terepi mobilapp hatókörön kívül, egy későbbi `FieldApp` érték nem breaking change. Default nincs — a létrehozó útvonal adja |
| `reporterId` | `FK → (polgári felhasználó)` | F | A bejelentő polgár. **Feltételes:** `CitizenApp`-eredetnél kötelező; `Manual`-nál lehet üres. **A típusa nyitott** — a polgári felhasználó tenant-oldali modellezése a polgári adatigény-spec dolga (TD-9, NY-2). A `Ticket` annyit rögzít: van bejelentő-hivatkozás |
| `reporterContactText` | `string` | O | Manuális nyitásnál a bejelentő elérhetősége szabad szövegként (név, telefon), ha nem appfelhasználó. Max 200 karakter. A `10` 4.3 telefonos ügyét fedi |

#### 1.2.6 Duplikáció

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `originalTicketId` | `FK → Ticket` (self) | F | Ha ez a `Ticket` egy összevonás **duplikátuma**, ez a mező az **eredeti** `Ticket`-re mutat (K-034). **Feltételes:** csak akkor kitöltött, ha a `Ticket` duplikációként lett elutasítva. Önhivatkozó FK |

> **A duplikáció-kapcsolat iránya.** Az `originalTicketId` a duplikátumon él,
> és az eredetire mutat — egy duplikátumnak egy eredetije van. Az eredeti
> oldaláról a kapcsolat **fordított navigáció**: "mely `Ticket`-ek mutatnak
> rám duplikátumként" (`20` 4.2 — "1 duplikátum összevonva: #234"). Ez nem
> külön mező, hanem a self-FK fordított oldala. A `Category`-fa szülő-gyermek
> kapcsolata (→ 2. blokk) ugyanezt a self-FK mintát használja majd.

> **Az eredeti ügy értesítendőinek halmaza (SD-42, duplikáció-feature-spec).**
> K-034 előírja, hogy összevonáskor a duplikátum bejelentője az eredeti ügy
> értesítendői közé kerül. Ezt **nem külön entitás vagy mező** hordozza: az
> eredeti ügy értesítendőinek halmaza lekérdezésből áll elő —
> `{eredeti.reporterId} ∪ {d.reporterId | d.originalTicketId = eredeti.id}`.
> Az `originalTicketId` self-FK beírása az összevonáskor **önmagában** átköti
> a duplikátum bejelentőjét; nincs `TicketSubscriber`-féle kapcsoló-entitás.
> Az értesítendők materializált listája a pilotra nem indokolt (a duplikáció
> az egyetlen forrása több értesítendőnek). Az értesítés tényleges
> **kézbesítése** (csatorna, időzítés) nem a domain-modell tárgya.

> **Az `originalTicketDisplayId` nem tárolt mező (SD-48, duplikáció-feature-spec).**
> A detail-DTO-ban (`10` 3.2.2), a `similar`- és a merge-válaszban megjelenő
> `originalTicketDisplayId` a DTO-réteg **származtatott** értéke: az eredeti
> `Ticket` `displayId`-ja, azaz `Tenant.displayPrefix` + `-` + az eredeti
> `ticketNumber` (a `displayId` SD-16 szerinti előállításával). A `Ticket`
> egyetlen tárolt duplikáció-mezeje az `originalTicketId` self-FK; a
> `displayId`-vetület mindig feloldással keletkezik, sosem tárolódik. Az
> SD-40 "mezőpár" megfogalmazása ezt pontosítja: a hivatkozást strukturáltan
> az `originalTicketId` tárolja, az `originalTicketDisplayId` a belőle rajzolt
> DTO-érték.

#### 1.2.7 Belső jegyzetek

A belső jegyzet **nem `Ticket`-mező**, hanem külön entitás: egy bejelentéshez
**több** jegyzet fűzhető, mindegyik saját szerzővel és időponttal (TD-10). A
`Ticket`-nek tehát itt nincs mezője — a jegyzetek a `TicketNote` entitásban
élnek (lásd 1.5), és a `ticketId` FK-n keresztül kapcsolódnak.

> **Tervezési döntés — több belső jegyzet, külön entitás (TD-10, SD-17).** A
> `10` 3.2 a belső jegyzetet egyetlen mezőként mutatta, de a több-bejegyzéses
> jegyzet-történet hamar igénnyé válik (diszpécser és vezető is fűzhet
> megjegyzést, időrendben). Ezért a belső jegyzet már a pilotra **`TicketNote`
> entitás** — nem egy felülíródó `Ticket.internalNote` szövegmező. Mivel a
> `TicketNote` maga a jegyzet-történet (időrendbe rendezett bejegyzések),
> **nincs szükség** külön `NoteAdded` `ActivityLog`-eseményre — a jegyzetek a
> saját entitásukból olvashatók. Az `ActivityLog` így tisztán az
> életciklus-eseményeké marad (státusz, felelős, elutasítás, visszanyitás,
> összevonás). *(Döntésnapló: SD-17.)*

#### 1.2.8 Időbélyegek

A `createdAt` az `AuditableEntity`-ből jön (a beérkezés ideje). Ezen túl a
státusz-életciklus mérhetőségéhez (`40_riport` — átlagos lezárási idő)
néhány esemény-időpont **dedikált mezőként** is kell:

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `assignedAt` | `DateTime` | O | Az `Új → Kiosztva` átmenet időpontja (a triage befejezése). Üres, amíg `New` |
| `resolvedAt` | `DateTime` | O | A `Folyamatban → Lezárt` átmenet időpontja. Üres, amíg nem `Resolved` |

> **Tervezési döntés — esemény-időpontok dedikált mezőként.** Az átmenet-időpontok
> elvileg kiolvashatók az `ActivityLog`-ból is. De a riport-aggregáció
> (`40` 3.5 — átlagos lezárási idő = `resolvedAt − createdAt`) **gyakori
> lekérdezés**, és napló-aggregációból számolni minden riportnál drága. Ezért
> a két leggyakrabban kérdezett esemény-időpont (`assignedAt`, `resolvedAt`)
> denormalizáltan a `Ticket`-en is megjelenik, az `ActivityLog` mellett. Ez
> tudatos denormalizáció a riport-teljesítményért. *(Döntésnapló: SD-14.)*
> A többi esemény-időpont (pl. `InProgress`-be lépés) az `ActivityLog`-ban
> elég — azt a riport nem aggregálja gyakran. Ha a `40_riport`-spec mégis
> igényli, akkor bővíthető.

> **Tervezési döntés — az `updatedAt` mint optimistic-concurrency token
> (SD-32).** Az `updatedAt` (`AuditableEntity`-ből, `timestamptz`, SD-9) az
> audit-szerepén túl az inline-szerkesztés és az állapotgép-akciók
> **optimistic-concurrency tokenjeként** is szolgál. A bejelentés-adatlap
> betöltésekor a kliens megkapja az aktuális `updatedAt`-ot; az inline-mentő
> és az akció-végpontok hívásakor visszaküldi (`expectedUpdatedAt`). Ha a
> szerver-oldali `updatedAt` időközben megváltozott (más felhasználó mentett),
> a művelet `409 Conflict` (`reason: "stale"`) hibával elutasul. **Nincs külön
> `rowVersion` mező** — a rekord-szintű detektáláshoz (SD-24) az `updatedAt`
> elegendő. Ez **nem új mező** — az `updatedAt` már létezik, és az
> `AppDbContext` automatikusan tölti; a döntés a mező *szerepét* bővíti.
> A token-ellenőrzés **eltérés a `BaseController` standard update-mintájától**
> — a részletet a `20_admin_felulet/10_bejelentes_lista_es_adatlap.md` 3.3
> fejti ki. *(Döntésnapló: SD-32.)*

### 1.3 Az `Attachment` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Szerep:** a `Ticket`-hez csatolt fájlok — a polgári fotók és a
lezárás-dokumentáció fotói.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `ticketId` | `FK → Ticket` | K | Melyik bejelentéshez tartozik |
| `kind` | `enum AttachmentKind` | K | `ReportPhoto` (a polgár/diszpécser adta, a bejelentés rögzítésekor) / `ResolutionPhoto` (a lezárás-dokumentáció része) |
| `fileRef` | `string` | K | A tárolt fájl hivatkozása a blob storage-ba. **A fájl maga nem a DB-ben van** — lásd az SD-15 megjegyzést |
| `fileName` | `string` | O | Az eredeti fájlnév (megjelenítéshez) |
| `contentType` | `string` | O | MIME-típus (pl. `image/jpeg`) |
| `sortOrder` | `int` | O | A megjelenítési sorrend egy bejelentésen belül (a polgári app több fotót galériaként mutat) |

> **Tervezési döntés — az `Attachment` általános, és a fájltárolás (SD-15).**
> A funkcionális dokumentumok jelenleg csak **fotót** említenek (polgári fotó,
> lezárás-fotó). Az entitást mégis `Attachment` néven és `contentType`-pal
> modellezem, nem `Photo` néven — mert így egy későbbi iteráció (pl.
> dokumentum-csatolmány a lezáráshoz) nem igényel új entitást. A pilotra az
> `AttachmentKind` két értéke (`ReportPhoto`, `ResolutionPhoto`) és a
> kép-`contentType`-ok elegendők. A fájl bináris adata **nem a DB-ben** van:
> a kérdéslista 8. pontja szerint S3-kompatibilis blob storage tárolja, és az
> `Attachment.fileRef` egy hivatkozás oda. A feltöltési flow (presigned URL,
> tömörítés, méretkorlát) a bejelentés-feltöltés feature-spec dolga; a
> tenant-szegmentáció (külön bucket/prefix tenantonként) fejlesztői döntés, a
> `01_kozos_mintak` multi-tenancy elvével konzisztensen. *(Döntésnapló:
> SD-15.)*

### 1.4 Az `ActivityLog` entitás

**DB-szint:** Tenant DB
**Származik:** — (lásd lentebb; **nem** `AuditableEntity`)
**Szerep:** a `Ticket` életciklus-eseményeinek append-only naplója. A J-2
döntés szerint külön entitás, nem az `AuditableEntity`-re épül.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `ticketId` | `FK → Ticket` | K | Melyik bejelentés eseménye |
| `eventType` | `enum ActivityEventType` | K | Az esemény fajtája — lásd lent |
| `actorId` | `FK → TenantUser` | O | Ki váltotta ki az eseményt (lásd 2.3). **Opcionális**, mert lehet rendszer-esemény is (pl. automatikus duplikáció-jelzés — bár a pilotra a duplikáció nem automatikus). A polgári eredetű esemény (pl. visszanyitás-kiváltó polgári jelzés) `actorId`-ja szintén üres lehet |
| `occurredAt` | `DateTime` | K | Az esemény időpontja. Nincs `AuditableEntity` — az `ActivityLog` append-only, nincs "módosítás" |
| `fromValue` | `string` | O | A változás előtti érték (pl. a régi státusz vagy a régi felelős neve) |
| `toValue` | `string` | O | A változás utáni érték |
| `note` | `string` | O | Az eseményhez tartozó szabad szöveg (pl. az elutasítás vagy visszanyitás indoklása, denormalizálva ide is) |

**Az `ActivityEventType` értékkészlete (pilot):**

| Érték | Mikor keletkezik | Forrás |
|---|---|---|
| `Created` | A bejelentés létrejött | — |
| `StatusChanged` | Státusz-átmenet, amelynek nincs specifikusabb típusa (pl. `Kiosztva → Folyamatban`) | `10` 6.1 |
| `Assigned` | Felelős kiosztása (`Új → Kiosztva` része) | `10` 3.4 |
| `Reassigned` | Felelős-váltás (nem státuszváltás) | `10` 4.4 |
| `Rejected` | Elutasítás | `10` 5. |
| `Reopened` | Korrekciós visszanyitás | `10` 4.5 |
| `Merged` | Duplikációként összevonva | `20` 4. |

> A belső jegyzet **nem** szerepel az `ActivityEventType`-ban — a `TicketNote`
> entitás maga a jegyzet-történet (SD-17), nincs szükség `NoteAdded`
> naplóeseményre. Az `ActivityLog` tisztán a bejelentés **életciklus-eseményeit**
> rögzíti.

> **Miért nem `AuditableEntity` az `ActivityLog`.** Az `AuditableEntity` a
> "ki és mikor módosította utoljára" kérdésre válaszol — egy rekord *aktuális*
> állapotát írja le. Az `ActivityLog` ezzel szemben **append-only
> esemény-folyam**: minden sor egy megtörtént esemény, sosem módosul, sosem
> törlődik. A két minta fogalmilag különbözik. Az `ActivityLog`-nak saját
> `occurredAt` és `actorId` mezője van — ezek nem az `AuditableEntity`
> `createdAt/By`-jával azonosak fogalmilag (bár értékük gyakran egyezne). A
> J-2 döntést ez a szakasz tölti ki.

> **Egy esemény — egy naplósor (TD-13).** Egy elutasítás fogalmilag egyszerre
> státuszváltás és elutasítás. A pilotra **egy** `ActivityLog`-sor keletkezik
> eseményenként, a *legspecifikusabb* `eventType`-pal (`Rejected`, nem
> `StatusChanged`), és a `fromValue`/`toValue` hordozza a státusz-változást. A
> `StatusChanged` csak azokra az átmenetekre marad, amelyeknek nincs
> specifikusabb típusuk (pl. `Kiosztva → Folyamatban`). Így a tevékenység-napló
> nem duplázódik.

### 1.5 A `TicketNote` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Szerep:** a bejelentéshez fűzött belső jegyzetek. Egy `Ticket`-hez **több**
`TicketNote` tartozhat, időrendben — ez a jegyzet-történet (SD-17, TD-10).

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `ticketId` | `FK → Ticket` | K | Melyik bejelentéshez tartozik |
| `authorId` | `FK → TenantUser` | K | A jegyzet szerzője (lásd 2.3). A diszpécser és a vezető írhat jegyzetet; a terepi dolgozó **olvassa, de nem írja** (`05_jogosultsagok` 6.1) — ezt a `TicketNote`-CRUD jogosultsága érvényesíti, nem a domain-modell |
| `body` | `string` (hosszú) | K | A jegyzet szövege. Max 2000 karakter. A polgárnak **soha nem** látható |

> A `TicketNote` az `AuditableEntity`-ből származik — a `createdAt`/`createdBy`
> adja a "mikor és ki írta" adatot. Az `authorId` ezzel látszólag redundáns a
> `createdBy`-jal, de **nem az**: a `createdBy` a Core `User.id` (a JWT
> subject, `01_kozos_mintak` 7.1), míg az `authorId` a tenant-oldali
> `TenantUser`-re mutat — ez kell ahhoz, hogy a jegyzet szerzőjének neve
> cross-DB join nélkül megjeleníthető legyen (SD-2). A megjelenítés az
> `authorId`-t használja; a `createdBy` az audit-konzisztenciát adja.
>
> **Szerkeszthetőség.** A pilotra a `TicketNote` a létrehozás után
> **módosítható és törölhető** a szerző (és a vezető) által — standard CRUD. A
> jegyzet nem append-only napló, hanem szerkeszthető bejegyzés-lista. Ez eltér
> az `ActivityLog`-tól, ami append-only. *(Ha a pilot-tapasztalat azt mutatja,
> hogy a jegyzeteket "véglegesíteni" kell, az append-only viselkedés egy
> későbbi iteráció — nem a piloté.)*

---

## 2. Referencia- és önálló operatív entitások

Ez a blokk öt entitást rögzít — három referencia-jellegűt, amelyekre az
1. blokk `Ticket`-je már hivatkozott "→ 2. blokk" jelöléssel, és két
önálló operatív entitást, amelyek nem a `Ticket`-hez tartoznak, hanem
saját életciklusú aggregátumok:

| Entitás | DB-szint | Szerep |
|---|---|---|
| `Category` | Tenant DB | A kétszintű kategória-fa (K-036) — a `Ticket` besorolása |
| `Group` | Tenant DB | Terepi munkacsoport — a `Ticket` egyik felelős-típusa |
| `TenantUser` | Tenant DB | A Core `User` szűk, csak-olvasható tenant-projekciója (SD-2) |
| `WeeklyReport` | Tenant DB | A heti PDF-riport perzisztált pillanatképe (SD-49) — önálló operatív, nem `Ticket`-kísérő |
| `WeeklyReportRecipient` | Tenant DB | A heti riport-címzett-lista (SD-59) — önálló konfigurációs entitás, a `40_riport`-feature fogyasztja |

Mind az öt a **Tenant DB**-ben él. A `TenantUser` különleges: nem
"szabad" entitás, hanem a Core DB-ből replikált projekció — a CRUD-ja a Core
`User`-é, ide csak propagálódik (SD-3). A `WeeklyReport` szintén különleges:
nem felhasználói CRUD-dal, hanem **háttér-job által** keletkezik és
frissül (`40_riport.md` 3.6); a `BaseController`-ből csak a `list` és `get`
műveletet teszi közzé. A `WeeklyReportRecipient` ezzel szemben **felhasználói
CRUD-dal** keletkezik (a vezető szerkeszti a `/beallitasok/altalanos`-on,
`30_beallitasok.md` 3.3.3) — standard `BaseController`.

### 2.1 A `Category` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó döntések:** K-036 (kétszintű fa), SD-8 (core-default hibrid
modell), SD-18 (a besorolás bármely szinten teljes)

A `Category` egy **kétszintű fa**: a gyökér-kategória a terméktulajdon
(Urbino-katalógus), az alkategória a tenant-tulajdon (K-036). A fát egy
**self-FK** (`parentId`) modellezi — ugyanaz a minta, mint a `Ticket`
duplikáció-kapcsolatánál.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `parentId` | `FK → Category` (self) | F | A szülő-kategória. **Feltételes:** `null` a gyökér-kategóriáknál; kitöltött az alkategóriáknál. A fa mélysége pontosan kettő — egy alkategória `parentId`-ja mindig gyökér (lásd lent) |
| `name` | `string` | K | A kategória megjelenő neve (pl. "Utak és járdák", "kátyú"). Max 100 karakter |
| `iconRef` | `string` | O | A kategória ikonja — egy **közös, előre definiált ikonkészlet kulcsa** (pl. `road`, `lighting`), nem feltöltött kép (NY-3 döntés). A polgári app és az admin felület ugyanabból a szettből rajzol. A Flutter `screen_uj_bejelentes_2` minden gyökérnél ikont mutat |
| `isActive` | `bool` | K | Aktív-e. **Default: `true`.** Inaktív kategória nem választható új bejelentéshez, de a meglévő hivatkozások megmaradnak (lásd lent) |
| `sortOrder` | `int` | O | A megjelenítési sorrend a szülőn belül (a polgári csempék és az admin lista rendezése). A `BaseController` reorder-mintája kezeli |
| `sourceCatalogId` | `FK → DefaultCategoryCatalog` | F | A Core-beli default-katalógus forrás-rekordja (SD-8, lásd 3.4). **Feltételes:** kitöltött a default-ból provisioning-időben másolt kategóriákon; `null` a tenant-egyedi kategóriákon. Ez különbözteti meg a "termékesített" és a "tenant-egyedi" kategóriát |

> **A fa mélysége pontosan kettő.** A K-036 kétszintű fát ír elő — gyökér +
> alkategória, nem több. A `parentId` self-FK elvileg tetszőleges mélységet
> elbírna, de a domain-szabály ezt **kettőre korlátozza**: egy `Category`,
> aminek a `parentId`-ja `null`, gyökér; egy `Category`, aminek a `parentId`-ja
> kitöltött, alkategória — és az alkategória `parentId`-ja **csak gyökérre**
> mutathat (alkategória alá nem vehető fel kategória). Ezt FluentValidation
> érvényesíti a `Category`-spec szintjén; a domain-modell a szabályt rögzíti.

> **Tervezési döntés — a besorolás bármely szinten teljes (SD-18).** A
> `Ticket.categoryId` (és a `citizenSuggestedCategoryId`) **bármely**
> `Category`-rekordra mutathat — gyökérre vagy alkategóriára egyaránt. Egy
> bejelentés besorolása **teljes akkor is, ha csak gyökér-kategóriáig megy**;
> az alkategória pontosít, de nem kötelező. Indok: a polgári `screen_uj_bejelentes_2`
> hat gyökér-csempét mutat alkategória-választó nélkül — ha a modell
> alkategóriát követelne, a polgári bejelentés sosem lehetne érvényes triage
> előtt. A triage-ben (K-031) az alkategória így *lehetőség*, nem kötelező
> ötödik kattintás — a gyors triage-t (`10`, 30 mp cél) ez támogatja. A
> kötelező alkategória ráadásul értelmezhetetlen a természetesen
> alkategória-nélküli gyökereknél (pl. "Egyéb"). *(Döntésnapló: SD-18.)*

> **Az inaktiválás, nem törlés.** Egy `Category`-t, amelyre már mutat
> bejelentés, **nem lehet törölni** — csak `isActive = false`-ra állítani. Az
> inaktív kategória eltűnik a választókból (polgári csempe, triage-dropdown),
> de a meglévő `Ticket.categoryId`-hivatkozások érvényesek maradnak, és a
> riport továbbra is név szerint mutatja. A tényleges törlés (a `BaseController`
> delete-je) csak olyan kategóriára megengedett, amelyre nincs hivatkozás —
> ezt a `Category`-spec `BeforeSaveAsync`/delete-guard rögzíti. **Eltérés a
> mintától:** a standard `BaseController` delete nem ellenőrzi a hivatkozásokat;
> a `Category` delete-je igen — ezt a `Category`-spec "Eltérés a mintától:"
> jelöléssel részletezi.

**A gyökér-szint feloldása a duplikáció-heurisztikához.** A duplikáció-szűrés
(K-032) gyökér-szintű kategória-egyezést hasonlít. Mivel a `Ticket.categoryId`
bármely szintre mutathat (SD-18), a heurisztikának fel kell oldania a
**gyökeret**: ha a `categoryId` alkategória, a `parentId`-n felmegy egy
szintet; ha már gyökér, önmaga. Ez a `Category`-fa természetes navigációja —
a duplikáció-spec (`20`) ezt használja, a domain-modell a `parentId`-val
biztosítja.

### 2.2 A `Group` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó dokumentum:** `50_konfig` 3. (csoport-konfiguráció)

A `Group` egy terepi munkacsoport (pl. "Útkarbantartó csapat"). A triage
felelős-mezője csoportra **vagy** személyre oszt (TD-4); a `Group` a
csoport-ág.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `name` | `string` | K | A csoport neve. Max 100 karakter |
| `isActive` | `bool` | K | Aktív-e. **Default: `true`.** Inaktív csoport nem választható új kiosztáshoz; a meglévő `Ticket.assignedGroupId`-hivatkozások megmaradnak — ugyanaz az elv, mint a `Category`-nál |
| `members` | n-n `↔ TenantUser` | — | A csoport tagjai. **`[ManyToManyConnection]`** — a `CLAUDE.md` join-auto-mapping mintája. Lásd lent |

> **A `Group` ↔ `TenantUser` kapcsolat.** A `50_konfig` 3. szerint a csoport
> a pilotra "egy címke + egy taglista" — nincs csoport-hierarchia, nincs
> csoport-szintű jogosultság. A tagság egy **n-n kapcsolat** a `TenantUser`-rel,
> a `[ManyToManyConnection]` attribútummal (a join-tábla auto-mapping a
> `CLAUDE.md` szerint). A tagság a pilotra **információs**: megmutatja, kik
> tartoznak a csoporthoz, de a kiosztás a `Group` egészére mutat
> (`Ticket.assignedGroupId`), nem a tagokra külön. A "melyik tag csinálja"
> a csoporton belül a pilotra nincs modellezve — ez a terepi mobilapp
> későbbi kérdése.

> **Tervezési döntés — a `Group` taglista csak `TenantUser`-t tartalmaz.** A
> csoport tagjai a tenant **belső** felhasználói (diszpécser, vezető, terepi
> dolgozó) — `TenantUser`-ek. A polgár sosem tagja csoportnak. A taglista
> tehát a `TenantUser`-re mutat, nem egy általánosabb személy-entitásra.
> *(Nem önálló SD — a TD-4 és az SD-2 természetes következménye.)*

### 2.3 A `TenantUser` entitás

**DB-szint:** Tenant DB
**Származik:** — (lásd lent; a CRUD-ja nem itt van)
**Kapcsolódó döntések:** SD-2 (a projekció elve), SD-3 (egyirányú szinkron), SD-37 (a `status` mező)

A `TenantUser` a Core `User` **szűk, csak-olvasható projekciója** a Tenant
DB-ben (SD-2). Nem önálló entitás abban az értelemben, hogy **a tenant-oldalon
nincs CRUD-ja** — a Core `User`/`UserTenantRole` változása propagálódik bele
(SD-3). A `Ticket.assignedUserId`, a `ActivityLog.actorId`, a `TicketNote.authorId`
és a `Group.members` mind erre mutatnak — így a tenant-oldali lekérdezések
cross-DB join nélkül oldják fel a felhasználó-neveket.

> **A `TenantUser` az `Invited` felhasználót is projektálja (SD-37).** A
> projekció nem csak az aktív felhasználókat tükrözi: a meghívott, még nem
> aktivált (`Invited`) felhasználó is megjelenik a `TenantUser`-ben, hogy a
> tenant-oldali felhasználó-lista és a Dashboard csapat-nézete lássa a még
> belépésre nem képes kollégát. Ezért a `status` mező háromértékű enum, nem
> bináris `isActive` — lásd a mezőtáblát és az SD-37-et.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | A `TenantUser` tenant-oldali elsődleges kulcsa. **Nem azonos** a Core `User.id`-val — lásd `coreUserId` |
| `coreUserId` | `long` | K | A forrás Core `User.id` (SD-2). Ez a kapcsolat a Core felé; a szinkron ezen az azonosítón propagál. **Egyedi** a tenant DB-n belül |
| `displayName` | `string` | K | A megjelenítendő név (triage felelős-választó, `ActivityLog`, `TicketNote` szerző, Dashboard csapat-tábla). A Core `User`-ből replikált |
| `email` | `string` | K | A felhasználó e-mail címe — azonosításhoz, kapcsolatfelvételhez. A Core `User`-ből replikált |
| `roles` | `enum[] TenantRole` | K | A tenant-szintű szerepkörök ezen a tenanton: `dispatcher` / `manager` / `content_manager` / `field_worker` (SD-7). A Core `UserTenantRole`-ból replikált. **Lásd NY-4 a tárolási formáról** |
| `status` | `enum UserStatus` | K | A felhasználó Core-oldali fiók-állapotának tükre ezen a tenanton: `Invited` / `Active` / `Disabled` (SD-37). A Core `User.status`-ból replikált. Csak `Active` `TenantUser` választható új kiosztáshoz/jegyzethez; az `Invited` projektálódik (a felhasználó-lista és a Dashboard lássa a meghívottat), de munkavégzésre még nem jelölhető |

> **Miért nem `AuditableEntity` a `TenantUser`.** Az `AuditableEntity` a
> "ki és mikor hozta létre/módosította" kérdést válaszolja — de a `TenantUser`-t
> nem egy tenant-oldali felhasználó hozza létre, hanem a **szinkron** (SD-3).
> A "ki/mikor" adat a Core `User`-höz tartozik, nem ide. A `TenantUser`-nek
> ezért nincs `AuditableEntity`-öröksége; ha a szinkron-folyamat utolsó
> propagálásának időpontját követni kell, az egy dedikált `lastSyncedAt` mező
> lehet — de ezt a szinkron-mechanizmus fejlesztői kidolgozása dönti el
> (`01_kozos_mintak` 1.5 nyitott pont), nem a domain-modell.

> **A `TenantUser` és a `Ticket.reporterId` viszonya.** Fontos, hogy a
> `TenantUser` **csak a tenant belső felhasználóit** projektálja — a
> diszpécsert, vezetőt, tartalomkezelőt, terepi dolgozót. A **polgár nem**
> `TenantUser`. A `Ticket.reporterId` ezért **nem** a `TenantUser`-re mutat,
> hanem a polgári felhasználó (még nyitott, NY-2) entitására. A `TenantUser`
> a belső oldal; a `reporter` a polgári oldal — a kettő külön.

### 2.4 A `WeeklyReport` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó dokumentum:** `20_admin_felulet/40_riport.md` 2. (a Dashboard
és heti PDF-riport feature-spec); `40_riport_v1.md` 4. (a funkcionális
terv)
**Kapcsolódó döntések:** SD-49 (az entitás), SD-55/c (idempotencia),
SD-56 (kézbesítés-státusz)

A `WeeklyReport` egyetlen tenant egyetlen naptári hetére generált heti
riportot reprezentál: a perzisztált PDF-bináris hivatkozását, a számszerű
pillanatképet, és a generálás/kézbesítés állapotát. A heti PDF-et el kell
tárolni, mert három dolog épül rá: a `/riportok` archívum, a Dashboard
"Heti riport letöltése" gombja (a legutóbbit adja), és az e-mail-
melléklet. Az entitás **nem felhasználói CRUD-dal**, hanem **háttér-job
által** keletkezik — a `BaseController`-ből csak a `list` és `get`
műveletet teszi közzé; írás a `resend` akció-végponton kívül nincs.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `year` | `int` | K | A riport-hét ISO-8601 éve. Az `isoWeek`-kel együtt azonosítja a hetet. Az ISO-év a hét-forduló környékén eltérhet a naptári évtől (pl. 2027.01.01 az ISO-2026 52. hetébe eshet) — ezért `year` az ISO-év, nem a naptári |
| `isoWeek` | `int` | K | A riport-hét ISO-8601 hétszáma. Érték-tartomány: `1 ≤ isoWeek ≤ 53` |
| `periodStart` | `DateTime` (UTC) | K | A hét kezdete: a tenant-időzónás hétfő 00:00, UTC-re konvertálva (SD-10). Denormalizált — a lekérdezések ne számolják újra |
| `periodEnd` | `DateTime` (UTC) | K | A hét vége: a tenant-időzónás vasárnap 24:00 (= a következő hétfő 00:00), UTC-re konvertálva. Félig nyílt intervallum: `[periodStart, periodEnd)` |
| `generationStatus` | `enum ReportGenerationStatus` | K | A generálás-pipeline állapota: `Pending` / `Generating` / `Completed` / `Failed`. **Default: `Pending`** |
| `generatedAt` | `DateTime` (UTC) | O | A sikeres generálás befejezésének időpontja. Üres, amíg `generationStatus ≠ Completed` |
| `pdfFileRef` | `string` | O | Az S3-hivatkozás a bináris PDF-re — az `Attachment.fileRef` mintájával konzisztens (SD-15). Max 512 karakter. Üres, amíg `generationStatus ≠ Completed` |
| `snapshotData` | `string` (JSON, `jsonb`) | O | A riport számszerű tartalma strukturáltan: `weekNumbers` (öt aggregátum) + `categoryBreakdown` (gyökér-kategória szerint). A "múlt héthez képest" szakasz és az archívum-adatlap ebből olvas, PDF-visszafejtés nélkül. Pontos séma: a feature-spec 2.4 |
| `deliveryStatus` | `enum ReportDeliveryStatus` | K | Az e-mail-kézbesítés összesített állapota (SD-56): `Pending` / `AllSent` / `PartialFailure` / `AllFailed`. **Default: `Pending`** |
| `deliveredAt` | `DateTime` (UTC) | O | Az utolsó kézbesítési kísérlet időpontja. Üres, amíg `deliveryStatus = Pending` |
| `recipientCount` | `int` | O | Hány címzettnek ment a riport az utolsó kézbesítési kísérletkor — a `PartialFailure` kontextusához. Üres, amíg `deliveryStatus = Pending` |

> **Egyediségi megkötés (SD-55/c — idempotencia).** A `(year, isoWeek)`
> pár **egyedi** a tenant DB-n belül. Mivel minden tenantnak saját Tenant
> DB-je van (SD-1), ez tenant-szinten egy hétre pontosan egy
> `WeeklyReport`-ot garantál. A generáló háttér-job (`40_riport.md` 3.6)
> a meglévő rekordot frissíti, nem újat hoz létre — a unique constraint
> a végső védvonal a duplikátum ellen.

> **A két status-dimenzió függetlensége.** A `generationStatus` és a
> `deliveryStatus` két párhuzamos kis életciklus — együtt sem alkotnak
> állapotgépet, ezért **nem** a `02_globalis_allapotgep.md`-ben élnek
> (az kizárólag a `Ticket` állapotgépe). Az átmenetek formálisan a
> `40_riport.md` 2.3-ban. Egy `WeeklyReport` lehet `Completed` +
> `AllFailed` egyszerre — a PDF kész és archiválva, de az e-mail nem
> ment ki ("best effort" kézbesítés, `02_globalis_allapotgep.md` 6.).

> **Miért nem `ContentStatus`-életciklus.** A `WeeklyReport` nem tartalmi
> entitás (mint a `News`/`Event`/`CityInfo`) — nincs `Draft`/`Published`
> állapota, nem szerkeszthető, automatikusan generálódik. A
> `generationStatus` a háttér-job futás-állapota, nem publikálási
> életciklus. *(SD-49.)*

> **Miért `AuditableEntity`.** Az `ActivityLog`-gal ellentétben (ami
> append-only, ezért nem `AuditableEntity`) a `WeeklyReport` egy rekord,
> amelynek aktuális állapota van, és amely a generálás/kézbesítés során
> módosul. A `createdBy` a rendszer-job; a `resend` akciónál az
> `updatedBy` a vezetőt rögzíti.

### 2.5 A `WeeklyReportRecipient` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó dokumentum:** `20_admin_felulet/30_beallitasok.md` 2.2 (a
Beállítások feature-spec); `50_konfig_v2.md` 5.2 (a funkcionális terv);
`20_admin_felulet/40_riport.md` 4.4, SD-57 (a fogyasztó-feature)
**Kapcsolódó döntések:** SD-59 (az entitás), SD-67 (a `/beallitasok/altalanos`
beágyazott listája)

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 vezető (Béla),
esetleg a polgármester (Péter), és a hivatali titkárság.

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** — két azonos e-mail nem szerepelhet ugyanazon a listán |
| `isActive` | `bool` | K | Aktív-e (kap-e a következő heti riportból). **Default: `true`.** Inaktív címzett megmarad a listában, de a generáló job nem küld neki — szelektív kikapcsolás (pl. szabadság alatt) |

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

> **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-szinten 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. *(`30_beallitasok.md` 2.2.)*

> **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) — a Beállítások felülete
> és a riport-feature ugyanazt a végpontot fogyasztja.

> **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: 5.1.

---

## 3. Core DB entitások

Ez a blokk a **Core DB** öt entitását rögzíti — a tenant-független, központi
adatokat (SD-1). A 2. blokk `TenantUser`-e és `Category`-ja ezekből replikálódik
(SD-2, SD-3, SD-8).

| Entitás | DB-szint | Szerep |
|---|---|---|
| `User` | Core DB | A globális felhasználói fiók — a teljes identitás |
| `Tenant` | Core DB | A tenant-regisztrum (SD-4) |
| `UserTenantRole` | Core DB | A `User` ↔ `Tenant` kötés, tenant-szintű szerepkörrel |
| `UserInvitation` | Core DB | A felhasználó-meghívó — lejáratos, egyszer használatos token (SD-34) |
| `DefaultCategoryCatalog` | Core DB | Az Urbino termékesített default kategória-fája |

> **A Core DB és a `BaseController`.** A Core-entitások ugyanúgy a
> `BaseController`-mintát követik, mint a tenant-entitások — a különbség, hogy
> a Core DB-hez kapcsolódó `DbContext` **nem** megy át a tenant-resolution
> middleware-en (SD-6): a Core egyetlen, fix adatbázis. A Core-entitások
> CRUD-ja az **Urbino-admin** és részben a tenant-vezető hatásköre — a pontos
> jogosultságot a vonatkozó feature-spec adja; a domain-modell az entitásokat
> rögzíti.

### 3.1 A `User` entitás

**DB-szint:** Core DB
**Származik:** `AuditableEntity`
**Kapcsolódó döntések:** SD-1, SD-2 (a `TenantUser` ennek a projekciója), SD-3
(egyirányú szinkron)

A `User` a globális felhasználói fiók. **A teljes identitás itt él** — a
tenant DB-beli `TenantUser` ennek a szűk, csak-olvasható másolata.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | A globális felhasználó-azonosító. A `TenantUser.coreUserId` erre mutat; a `Ticket.createdBy`/`updatedBy` is ezt tárolja (`01_kozos_mintak` 7.1) |
| `displayName` | `string` | K | A megjelenítendő név. Max 200 karakter. Innen replikálódik a `TenantUser.displayName` |
| `email` | `string` | K | A felhasználó e-mail címe — egyúttal a Zitadel-bejelentkezés azonosítója és a meghívó-flow címzettje. **Egyedi** a Core DB-n belül |
| `status` | `enum UserStatus` | K | A globális fiók-állapot: `Invited` / `Active` / `Disabled` — lásd lent. **Default: `Invited`** (a felhasználó meghívóval jön létre, Ü-6) |
| `externalAuthId` | `string` | O | A Zitadel-oldali felhasználó-azonosító (a JWT subject claim). A bejelentkezett `User` ezen kötődik a Zitadel-identitáshoz. **Feltételes:** üres lehet, amíg a meghívott felhasználó még nem aktiválta a fiókját — lásd NY-5 |

**A `UserStatus` értékkészlete:**

| Érték | Jelentés |
|---|---|
| `Invited` | A felhasználó meghívót kapott (Ü-6), de még nem aktiválta a fiókját. Még nem tud belépni |
| `Active` | Aktív, bejelentkezésre képes fiók |
| `Disabled` | Letiltott fiók — nem tud belépni, de az adatai (és a `Ticket`-hivatkozásai) megmaradnak |

> **Tervezési döntés — a `User` globális, a szerepkör nem rajta van (SD-19).**
> A `User` **nem** tartalmaz szerepkör-mezőt. A felhasználó *globális*
> entitás — egy `User` több tenanthoz is tartozhat (K-025 implicit
> unió-modell, `01_kozos_mintak` 1.3), és tenantonként **más** szerepköre
> lehet. A szerepkör ezért nem a `User`-ön, hanem a `UserTenantRole`
> kötés-entitáson él (3.3). A `User.status` a *fiók* globális állapota
> (be tud-e lépni egyáltalán); a tenant-szintű aktivitást a `UserTenantRole`
> és a `TenantUser.isActive` adja. *(Döntésnapló: SD-19.)*

> **A jelszó nincs a `User`-ön.** Az autentikációt a Zitadel kezeli
> (`01_kozos_mintak` 3.1) — a jelszó-hash, a jelszó-házirend, a 2FA mind
> Zitadel-oldali. A `User` csak az `externalAuthId`-n keresztül kötődik a
> Zitadel-identitáshoz. A domain-modell ezért nem modellez jelszó-mezőt.

### 3.2 A `Tenant` entitás

**DB-szint:** Core DB
**Származik:** `AuditableEntity`
**Kapcsolódó döntések:** SD-4 (a regisztrum-séma), SD-5 (a `status`-értékek),
SD-16 (a `displayPrefix`), SD-58 (a logó és kontakt-mezők)

A `Tenant` a tenant-regisztrum — minden tenant metaadata és DB-kapcsolata. Az
SD-4 pilot-sémáját a `01_kozos_mintak` rögzítette; itt a teljes mezőlista, az
SD-16 (`displayPrefix`) és SD-58 (logó + kontakt) bővítéseivel.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `code` | `string` | K | A tenant rövid kódja (pl. `almadi`). Ez érkezik a `Tenant` HTTP headerben (SD-6), és ez a `tenant_<role>_<code>` Zitadel-szerepkör suffixe (SD-7). **Egyedi.** Max 50 karakter, kisbetűs, URL-barát |
| `name` | `string` | K | A tenant teljes megjelenő neve (pl. "Balatonalmádi Városgondnokság"). Max 200 karakter. **Szerkeszthető** a vezető által a `/beallitasok/altalanos`-on (SD-61) |
| `displayPrefix` | `string` | K | A bejelentés-azonosító megjelenítési prefixe (pl. `ALM`) — a `ticketNumber` elé kerül, így áll elő az `ALM-234` (SD-16). Rövid, nagybetűs, 2–5 karakter. **Egyedi** — két tenant ne kapjon azonos prefixet, különben a polgári városokon átnyúló lista nem egyértelmű. **Nem szerkeszthető** a vezető által — minden bejelentés-azonosítóra hat, `urbino_admin`-hatáskör |
| `dbConnectionRef` | `string` | K | A tenant DB elérésének **hivatkozása** — nem nyers connection string. A tényleges kapcsolat (jelszóval) a futtatókörnyezet titok-kezelőjéből jön (SD-4 biztonsági elv). **Nem szerkeszthető** a vezető által — `urbino_admin`-hatáskör |
| `status` | `enum TenantStatus` | K | `Setup` / `Active` / `Suspended` / `Archived` (SD-5). **Default: `Setup`** — az új tenant provisioning alatt áll, amíg az Urbino-admin élesíti. **Nem szerkeszthető** a vezető által — `urbino_admin`-hatáskör |
| `timeZone` | `string` | K | A tenant megjelenítési időzónája (IANA-azonosító, pl. `Europe/Budapest`). **Default: `Europe/Budapest`.** A nap/hét-határ számításának alapja (SD-10). **Nem szerkeszthető** a vezető által a pilotra — `urbino_admin`-hatáskör |
| `logoFileRef` | `string` | O | **SD-58.** 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. Megjelenik a manager felület fejlécében és a heti PDF-riport fejlécében. **Szerkeszthető** dedikált multipart-végpontokon (`POST/DELETE /v1/tenant-settings/logo`, SD-60), nem a `Tenant`-PUT-tal |
| `contactAddress` | `string` | O | **SD-58.** A tenant fizikai címe (pl. `"8220 Balatonalmádi, Széchenyi sétány 1."`). Max 300 karakter. **Szerkeszthető** a vezető által |
| `contactPhone` | `string` | O | **SD-58.** Kapcsolattartási telefonszám. Max 50 karakter. Formátum-validáció nincs (nemzetközi, formázott, mellékes számok elférnek). **Szerkeszthető** a vezető által |
| `contactEmail` | `string` | O | **SD-58.** Nyilvános kapcsolattartási e-mail. Max 200 karakter, e-mail formátum-validációval. **Szerkeszthető** a vezető által |

> **A `displayPrefix` és a `code` viszonya.** A kettő külön mező, mert más a
> szerepük és más a formátumuk: a `code` URL-barát, kisbetűs, technikai
> azonosító (`almadi`); a `displayPrefix` rövid, nagybetűs, **emberi
> olvasásra** szánt jel (`ALM`). A `code`-ból nem mindig képezhető jó prefix
> automatikusan ("almadi" → "ALM" még megy, de egy hosszabb vagy
> többszavas név esetén nem triviális) — ezért a `displayPrefix` önálló,
> a tenant felvételekor megadott mező. *(Az SD-16 nyomán; a 2. blokk-döntés
> kiegészítése.)*

> **A `status`-átmenetek.** A `Tenant.status` egy kis állapotgép
> (`Setup → Active → Suspended ⇄ Active`, `Active/Suspended → Archived`), de
> ez **Urbino-admin hatáskör** (K-024), és a manager felület
> specifikációjának nem tárgya. A `02_globalis_allapotgep.md` a `Ticket`
> állapotgépét rögzíti; a `Tenant`-életciklus az Urbino-admin spec dolga. Itt
> csak a `status`-mező és az értékkészlet rögzül.

> **A "tartalom-paraméter, nem brand-paraméter" határ konkrét mezőkben.** A
> K-037 elv ("a tenant tartalmat konfigurál, nem brandet") a `Tenant`
> mezőtáblájában is láthatóvá válik: a vezető a `name`, `logoFileRef`,
> `contactAddress`, `contactPhone`, `contactEmail` mezőt **szerkesztheti**
> (ezek tartalom-paraméterek). A `code`, `displayPrefix`, `dbConnectionRef`,
> `status`, `timeZone` mezőt **nem** — ezek platformszintű regisztrum-mezők,
> `urbino_admin`-hatáskörűek. A `30_beallitasok.md` 3.3.1 `TenantSettingsController`-e
> ezt a határt API-szinten érvényesíti (a tiltott mezők szerepeltetése
> `400 field_not_editable`-lel zár). *(SD-58, SD-61.)*

> **A `logoFileRef` mint "egyedi-fájl-mező" minta (SD-60).** A logó **nem**
> `Attachment`-rekord (az `Attachment` `ticketId`-hez kötött, 1.4). Az
> `Attachment.fileRef`-mintával konzisztens S3-kulcsséma
> (`tenants/{code}/logo/{guid}.{ext}`) tárolja a binárist; a `logoFileRef` a
> hivatkozást őrzi. A feltöltés/törlés dedikált multipart-végponton megy
> (`POST/DELETE /v1/tenant-settings/logo`), nem a standard `BaseController`
> JSON-CRUD-on keresztül. Ha más entitásnál is felmerül egyedi-kép-mező (pl.
> `News.coverImage`), ez a minta — vagy az `Attachment`-általánosítás —
> választható. *(VF-K1 a `60_tartalom`-spec felé.)*

### 3.3 A `UserTenantRole` entitás

**DB-szint:** Core DB
**Származik:** `AuditableEntity`
**Kapcsolódó döntések:** SD-3 (ebből replikál a `TenantUser.roles`), SD-7 (a
szerepkör-értékek), K-025 (implicit unió-modell)

A `UserTenantRole` a `User` ↔ `Tenant` **kötés-entitás**, tenant-szintű
szerepkörrel. Ez az **igazság forrása** arra, hogy ki, melyik tenanton, milyen
szerepkört birtokol — a JWT és a `TenantUser.roles` egyaránt ennek a leképezése
(`01_kozos_mintak` 3.2).

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `userId` | `FK → User` | K | Melyik felhasználó |
| `tenantId` | `FK → Tenant` | K | Melyik tenanton |
| `role` | `enum TenantRole` | K | A szerepkör: `dispatcher` / `manager` / `content_manager` / `field_worker` (SD-7) |

> **Egy sor = egy (felhasználó, tenant, szerepkör) hármas.** Ha egy
> felhasználó ugyanazon a tenanton **több** szerepkört birtokol (K-025
> unió-modell — pl. diszpécser ÉS tartalomkezelő), az **több
> `UserTenantRole`-sor** — nem egy sor szerepkör-tömbbel. Indok: így a
> szerepkör-hozzárendelés és -visszavonás egy-egy rekord beszúrása/törlése, és
> a kötés-tábla tiszta. A `(userId, tenantId, role)` hármas **egyedi** — egy
> szerepkört nem lehet kétszer hozzárendelni. A `TenantUser.roles` tömb (2.3)
> ezeknek a soroknak az aggregált, replikált képe a tenant DB-ben.

> **Tervezési döntés — a `UserTenantRole` a sok-a-sokhoz kapcsolat explicit
> entitásként (SD-20).** Modellezhető lenne a `User` ↔ `Tenant` n-n kapcsolat
> `[ManyToManyConnection]`-nel is — de itt a kapcsolatnak **saját adata** van
> (a `role`), és a `CLAUDE.md` `[ManyToManyConnection]` mintája tiszta
> join-táblára való. Ezért a `UserTenantRole` **explicit entitás**, nem
> attribútumos join. Ez egyúttal lehetővé teszi, hogy a kötés
> `AuditableEntity` legyen (ki és mikor adta a szerepkört — ez audit-adat).
> *(Döntésnapló: SD-20.)*

### 3.4 A `DefaultCategoryCatalog` entitás

**DB-szint:** Core DB
**Származik:** `AuditableEntity`
**Kapcsolódó döntések:** SD-8 (provisioning-időben másolódik a tenant DB-be),
K-036 (kétszintű fa)

A `DefaultCategoryCatalog` az Urbino által **termékesített** default
kategória-fa. Tenant-provisioning-időben lemásolódik a tenant DB `Category`
táblájába (SD-8); a másolt `Category`-rekordok a `sourceCatalogId`-n hivatkoznak
vissza ide.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | A `Category.sourceCatalogId` (2.1) erre mutat |
| `parentId` | `FK → DefaultCategoryCatalog` (self) | F | A szülő. `null` a gyökérnél, kitöltött az alkategóriánál — ugyanaz a kétszintű self-FK minta, mint a `Category`-nál (2.1) |
| `name` | `string` | K | A default kategória neve. Max 100 karakter |
| `iconRef` | `string` | O | A default ikon — a közös ikonkészlet kulcsa (NY-3); a provisioning ezt is átmásolja a `Category.iconRef`-be |
| `sortOrder` | `int` | O | A default megjelenítési sorrend |

> **Miért külön entitás, és nem a `Category` egy "default" jelzővel.** A
> `DefaultCategoryCatalog` a **Core DB**-ben él, a `Category` a **Tenant
> DB**-ben — két külön adatbázis (SD-1), tehát szükségképpen két külön
> entitás. A `DefaultCategoryCatalog` a *termék-szintű sablon*, a `Category` a
> *tenant-példány*. A provisioning a sablonból példányt készít (SD-8); a
> `sourceCatalogId` tartja a kapcsolatot. A `DefaultCategoryCatalog` CRUD-ja
> tisztán **Urbino-admin** hatáskör — a tenant-vezető a saját `Category`-fáját
> szerkeszti, a default-katalógust nem látja.

> **Amit ez a blokk tudatosan nem fed.** A default-katalógus *futás közbeni
> frissítésének* propagálása élő tenantokba (amikor az Urbino új default
> gyökeret termékesít) — ez az SD-8 és a `01_kozos_mintak` 4.4 szerint
> pilot-scope-on kívül, az Urbino-admin modul dolga. A `DefaultCategoryCatalog`
> entitás *létezik* és szerkeszthető, de a "változás → meglévő tenantok
> frissítése" folyamat nincs megépítve a pilotra.

### 3.5 A `UserInvitation` entitás

**DB-szint:** Core DB
**Származik:** `AuditableEntity`
**Kapcsolódó döntések:** SD-34 (külön entitás), SD-35 (lejárat és újraküldés),
Ü-6 (e-mail meghívó); az NY-5 lezárása.

A `UserInvitation` egyetlen felhasználó-meghívási kísérletet reprezentál: egy
`User`-höz tartozó, lejáratos, egyszer használatos token. A vezető a
felhasználó-felvételkor küldi (`20_admin_felulet/20_felhasznalokezeles.md` 3.3),
a meghívott a token-linkkel aktiválja a fiókját.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `userId` | `FK → User` | K | Melyik felhasználóhoz tartozik a meghívó |
| `tenantId` | `FK → Tenant` | K | Melyik tenant kontextusában keletkezett a meghívó. Egy `User` több tenanton is meghívható |
| `tokenHash` | `string` | K | A meghívó-token **hash-elt** alakja. A nyers token csak a meghívó-e-mailbe kerül, a DB-ben sosem. Hossz a választott hash-algoritmus kimenete (fejlesztői döntés) |
| `expiresAt` | `DateTime` (UTC) | K | A token lejárati időbélyege. Létrehozáskor `createdAt + 7 nap` (SD-35). UTC-ben tárolva (SD-9) |
| `status` | `enum InvitationStatus` | K | A meghívó állapota — lásd lent. **Default: `Pending`** |
| `consumedAt` | `DateTime` (UTC) | O | Mikor váltották be. Csak `Accepted` státuszban kitöltött; egyébként üres |

**Az `InvitationStatus` értékkészlete:**

| Érték | Jelentés |
|---|---|
| `Pending` | Kiküldve, érvényes, még nem váltották be és nem járt le |
| `Accepted` | A felhasználó beváltotta — a `User` ezzel `Active` lett |
| `Expired` | Lejárt beváltás nélkül (`expiresAt` elmúlt) |
| `Revoked` | Érvénytelenített — újraküldéskor a korábbi meghívó, vagy ha a `User`-t a beváltás előtt deaktiválták |

> **Tervezési döntés — a meghívó külön entitás (SD-34).** A meghívásnak saját,
> ismételhető életciklusa van: kiküldik, lejár, újraküldik (új token, régi
> érvénytelen), beváltják. Ha ezt a `User` mezőin tárolnánk, az újraküldés
> felülírná az előzményt, és nem maradna nyoma a meghívási kísérleteknek.
> Külön entitásként a kísérletek sora megmarad (egy `User`-höz időben több
> `UserInvitation`), és a tábla `AuditableEntity` — a `createdBy` rögzíti, ki
> (melyik vezető) küldte a meghívót. *(Döntésnapló: SD-34.)*

> **Az `Expired` állapot beállása.** A `Pending → Expired` átmenet
> idő-vezérelt, nem akció-vezérelt. A pilotra lazy ellenőrzés: a beváltáskor,
> ha `expiresAt < now`, a `status` `Expired`-re frissül. Időzített háttér-job
> iterációs elem. A felhasználó-feature-spec API-szakasza részletezi.

> **Egy `Pending` meghívó tenantonként.** Egy `User`-höz egy adott tenanton
> legfeljebb egy `Pending` `UserInvitation` tartozhat — ezt nem
> adatbázis-constraint adja (a régi `Revoked`/`Expired` sorok megmaradnak),
> hanem a meghívás-indító logika: új meghívó kiküldése a korábbi `Pending`
> meghívót `Revoked`-ra állítja (SD-35).

---

## 4. Tartalmi entitások

Ez a blokk a polgári app **puha tartalmának** három entitását rögzíti — a
tartalomkezelő (`content_manager`) szerepkör adatvilágát, a `60_tartalom_v2.md`
alapján.

| Entitás | DB-szint | Szerep |
|---|---|---|
| `News` | Tenant DB | Hír — a városvezetés hivatalos közleménye |
| `Event` | Tenant DB | Városi program — időponthoz és helyszínhez kötött rendezvény |
| `CityInfo` | Tenant DB | Városi információ — külső linkre mutató hasznos tétel |

Mindhárom a **Tenant DB**-ben él (tenant-operatív tartalom), és mindhárom a
`BaseController` standard CRUD-mintáját követi. A három entitás
**szerkezetileg rokon** — van közös vázuk (állapot, publikálási idő) —, de a
pilotra **külön entitásként** modellezzük őket.

> **Tervezési döntés — három külön entitás, nem egy közös `Content` (SD-21).**
> A `News`, `Event`, `CityInfo` modellezhető lenne egyetlen `Content`
> entitásként is, egy `contentType` diszkriminátor-mezővel. **Elvetve:** a
> három típus mezőkészlete érdemben eltér — az `Event`-nek időpontja és
> helyszín-koordinátája van, a `CityInfo`-nak kötelező külső `url`-je, a
> `News` ezektől mentes. Egy közös entitásban ez sok típus-feltételes nullable
> mező lenne ("ez a mező csak `Event`-nél értelmes"), ami a validációt és a
> `BaseController`-illesztést zavarossá tenné. Három tiszta entitás, külön
> `BaseController`-leszármazottal, külön `TableStateConfig`-gal — átláthatóbb,
> és a `60_tartalom` is három külön nézetcsaládként kezeli őket. A közös vázat
> (4.1) nem entitás-örökléssel, hanem ismételt mezőkként visszük — három
> mezőről van szó. *(Döntésnapló: SD-21.)*

> **A GYIK nem tartozik ide.** A GYIK Urbino-hatáskör (K-039), az Urbino-admin
> modul kezeli — nem tenant-tartalom, nem a `content_manager` szerepkör eleme.
> A `00_terminologia.md` 7. ezt rögzíti; a tartalmi blokk a GYIK-et nem
> modellezi.

### 4.1 Közös tartalmi váz — a publikálási életciklus

Mindhárom tartalmi entitásnak van **életciklusa**: a tartalomkezelő `Draft`
(vázlat) állapotban dolgozik rajta, majd `Published` (publikált) állapotba
mozdítja, és onnantól látszik a polgári appban (`60_tartalom` 3.5). Ezt a
közös vázat minden tartalmi entitás hordozza:

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `contentStatus` | `enum ContentStatus` | K | `Draft` / `Published` / `Archived` — lásd lent. **Default: `Draft`** |
| `publishedAt` | `DateTime` | O | A `Draft → Published` átmenet időpontja. Üres, amíg `Draft`. A polgári app a publikált tartalmat ez szerint rendezi (legújabb elöl) |

**A `ContentStatus` értékkészlete:**

| Érték | Jelentés |
|---|---|
| `Draft` | A tartalomkezelő dolgozik rajta; a polgári appban **nem** látszik |
| `Published` | Publikált — a polgári appban látszik |
| `Archived` | Visszavont / lejárt tartalom — a polgári appban már nem látszik, de nincs törölve. A tartalomkezelő archívumában megmarad |

> **Tervezési döntés — a `ContentStatus` enum, nem egy `isPublished` bool
> (SD-22).** Modellezhető lenne egyszerű logikai mezővel is (`isPublished`
> igen/nem). De az `Archived` állapot — a "volt publikálva, de már nem
> aktuális" — egy logikai mezővel nem megkülönböztethető a "még sosem volt
> publikálva" `Draft`-tól. A háromértékű enum tisztán elválasztja a
> sosem-publikált, az élő és a visszavont tartalmat — ez a tartalomkezelő
> archívum-nézetéhez (`60_tartalom` 3.5) kell. *(Döntésnapló: SD-22.)*

> **A `Draft`/`Published` nem a `Ticket`-állapotgép.** Fontos, hogy a tartalmi
> `ContentStatus` **nem** keverendő a `Ticket` `TicketStatus`-ával — két külön
> életciklus, két külön enum. A `02_globalis_allapotgep.md` a `Ticket`
> állapotgépét formalizálja; a tartalmi `Draft → Published → Archived` egy
> jóval egyszerűbb átmenet-készlet, amit a `60_tartalom`-spec rögzít. A
> domain-modell itt csak az enumot és a mezőket adja.

A közös váz a `createdAt`/`createdBy` (az `AuditableEntity`-ből) — ez adja a
"ki és mikor hozta létre" adatot, ami a tartalmi entitásoknál a **szerző**.

> **Tervezési döntés — a tartalmi szerző az `AuditableEntity.createdBy` (SD-23, megerősítve).**
> A `Ticket`-nél a `TicketNote` külön `authorId`-t kapott (SD-17), mert ott a
> szerző-megjelenítés cross-DB join nélkül kell. A tartalmi entitásoknál a
> szerző a `createdBy` (a Core `User.id`) — **nincs külön `authorId` mező**. A
> különbség oka: a tartalmi entitások listái és nézetei (`60_tartalom`) a
> pilotra nem mutatják a szerző nevét hangsúlyosan a lista-soronkon (3.2, 3.3,
> 3.4 oszlopkészlete sehol nem nevesít szerző-oszlopot), és ahol mégis kell
> (adatlap-szint), a `TenantUser`-feloldás egy +1 LEFT JOIN ugyanazon a Tenant
> DB-n belül (nem cross-DB). **A `60_tartalom`-spec az SD-23-at megerősítette**
> — dedikált `authorId` mezőre nincs igény. *(Döntésnapló: SD-23.)*

### 4.2 A `News` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó dokumentum:** `60_tartalom` 3.2
**Közös váz:** `contentStatus`, `publishedAt` (4.1)

A `News` a városvezetés hivatalos közleménye a polgári appban.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `title` | `string` | K | A hír címe. Max 200 karakter |
| `body` | `string` (hosszú) | K | A hír szövege HTML-tárolással. Szanitizációs whitelist és méret-cap lent (SD-69) |
| `coverImageRef` | `string` | O | A hír borítóképe — S3-objektum-hivatkozás. **Csak dedikált végponton szerkeszthető** (SD-68) |
| `pushOnPublish` | `bool` | K | **SD-68 + `60_tartalom`-spec 2.1.** Hírenkénti push-jelzés a polgári mobilapp felé. Default `false`. A polgári app a `Draft → Published` átmenet idején (vagy azt követően egy feed-szinkronizációval) olvassa; a push tényleges kiküldése a polgári mobilapp értesítési rendszerének dolga (NY-T1) |
| `contentStatus` | `enum ContentStatus` | K | Közös váz (4.1) |
| `publishedAt` | `DateTime` | O | Közös váz (4.1) |

> **A `News.body` HTML-tárolású, szigorú szerver-oldali szanitizációval (SD-69).**
> Whitelist: `<p>`, `<br>`, `<strong>`, `<em>`, `<ul>`, `<ol>`, `<li>`,
> `<a href="https://...">`. Max 10 000 karakter (HTML-tartalommal együtt). A
> szanitizáció FluentValidation-hoz csatolva; szanitizáció után üres tartalom
> `400 invalid_html_or_empty_after_sanitization`. Részletek a `60_tartalom`-spec
> 3.1.3-ban. Az **NY-6 ezzel lezárva**.

> **A `News.coverImageRef` az SD-68 "egyedi-fájl-mező" mintáját követi.**
> Dedikált `POST/DELETE /v1/news/{id}/cover-image` multipart-végpontokon
> módosítható (az SD-60 logó-feltöltő mintájával konzisztensen). A standard
> `PUT /v1/news/{id}`-en `400 fieldErrors.coverImageRef = "field_not_editable"`.
> Részletek a `60_tartalom`-spec 3.2.8-ban. *(SD-68 — a VF-K1 visszacsorgó
> jelzés lezárása.)*

### 4.3 Az `Event` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó dokumentum:** `60_tartalom` 3.3
**Közös váz:** `contentStatus`, `publishedAt` (4.1)

Az `Event` egy városi program — rendezvény-jellegű tartalom, **időponttal és
helyszínnel**. Ez különbözteti meg a `News`-tól.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `title` | `string` | K | A program neve. Max 200 karakter |
| `body` | `string` (hosszú) | K | A program leírása HTML-tárolással (SD-69 — lásd a `News.body`-nál) |
| `coverImageRef` | `string` | O | Borítókép — S3-objektum-hivatkozás. **Csak dedikált végponton szerkeszthető** (SD-68 — lásd a `News.coverImageRef`-nél) |
| `startsAt` | `DateTime` | K | A program kezdő időpontja. UTC-ben tárolva, tenant-időzónában jelenítve (SD-9, SD-10). Múltbeli megengedett (utólagos rögzítéshez) |
| `endsAt` | `DateTime` | O | A program záró időpontja. **Opcionális** — egy program lehet "nyitott végű" (pl. egész napos). Ha kitöltött, az `endsAt` ≥ `startsAt` (FluentValidation, a `60_tartalom`-spec 3.3.3 rögzíti) |
| `locationText` | `string` | O | A program helyszíne szabad szövegként (pl. "Városháza, nagyterem"). Max 300 karakter |
| `latitude` | `decimal` | O | A helyszín koordinátája — ha a polgári app térképen mutatja a programot. **A `latitude`/`longitude` páros együtt jár** (mint a `Ticket`-nél, 1.2.4) |
| `longitude` | `decimal` | O | Lásd `latitude` |
| `contentStatus` | `enum ContentStatus` | K | Közös váz (4.1) |
| `publishedAt` | `DateTime` | O | Közös váz (4.1) |

> **Az `Event.body` és `coverImageRef` ua. mintát követi, mint a `News`.**
> A `body` HTML-tárolású, az SD-69 whitelist-szabályaival; a `coverImageRef`
> dedikált `POST/DELETE /v1/events/{id}/cover-image` végpontokon
> szerkeszthető (SD-68). A `60_tartalom`-spec 3.3 részletezi.

> **Az `Event` koordináta-mezői opcionálisak.** Eltérően a `Ticket`-től (ahol a
> polgári bejelentés mindig hoz koordinátát), egy városi programnak nem mindig
> van térképre tehető pontja — elég a `locationText`. A koordináta akkor
> kitöltött, ha a polgári app a programot térképen is megjeleníti. Ezt a
> `60_tartalom`-spec és a polgári adatigény-spec véglegesíti; a domain-modell
> a mezőket opcionálisként adja.

### 4.4 A `CityInfo` entitás

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity`
**Kapcsolódó dokumentum:** `60_tartalom` 3.4
**Közös váz:** `contentStatus`, `publishedAt` (4.1)

A `CityInfo` egy városi információ — **külső linkre mutató** hasznos tétel
(strand, hivatali oldal, közmű-szolgáltató). Lényegében egy rendezett, kategorizált
linkgyűjtemény eleme.

| Mező | Típus | Köt. | Megjegyzés |
|---|---|---|---|
| `id` | `long` (PK) | K | — |
| `title` | `string` | K | A tétel megnevezése (pl. "Wesselényi-strand"). Max 200 karakter |
| `description` | `string` | O | Rövid leírás — mire jó ez a link. Max 500 karakter. Plain text |
| `url` | `string` | K | A külső link. **Szigorúan `https://` előtagú; max 2000 karakter.** `Uri.TryCreate` URI-formátum-validáció (`60_tartalom`-spec 3.4.2). `http://`, `javascript:`, stb. → `400 must_be_https` |
| `iconRef` | `string` | O | A tétel ikonja — a **közös ikonkészletből** (NY-3 döntés szerint kulcs, nem feltöltött kép). Max 50 karakter; whitelist-validáció. A polgári `screen_varosi_informaciok_lista` ikonos listát mutat |
| `groupLabel` | `string` | O | A tétel csoportosító címkéje (pl. "Strandok", "Hivatalok") — a polgári lista szekcionálásához. **Szabad szöveg, max 100 karakter** (SD-71) |
| `sortOrder` | `int` | O | A megjelenítési sorrend — a `BaseController` reorder-mintája kezeli |
| `contentStatus` | `enum ContentStatus` | K | Közös váz (4.1) |
| `publishedAt` | `DateTime` | O | Közös váz (4.1) |

> **A `CityInfo.groupLabel` szabad szöveges címke, nem lookup-entitás (SD-71).**
> Az NY-7 ezzel **lezárva**. Az admin-űrlap a meglévő distinct értékeket egy
> `GET /v1/city-info-groups`-végpontról kínálja autocomplete-tel (egyszerű
> distinct-query a `CityInfo`-n, nem entitás). A polgári app a `groupLabel`-szerint
> szekcionál (`screen_varosi_informaciok_lista`). Részletek a `60_tartalom`-spec
> 3.4.6-ban.

> **A `CityInfo` és a `Category` nem keverendő.** A `CityInfo.groupLabel` egy
> **szabad szöveges** csoportosító címke a polgári lista szekcionálásához —
> nem a `Category`-fa eleme. A `Category` a *bejelentések* besorolása; a
> `CityInfo` egy teljesen külön tartalomtípus, aminek a saját, lazább
> csoportosítása van. Ezt a `60_tartalom`-spec véglegesíti (lásd NY-7).

---

## 5. Entitás-térkép és összegzés

Ez a blokk zárja a domain-modellt: a teljes entitás-térkép egy ábrán, az
entitás-index, az összes `enum` gyűjtött listája. Új entitást és döntést nem
hoz — összegző jellegű.

### 5.1 Entitás-térkép

A diagram az összes domain-entitást és a kapcsolataikat mutatja, a két
DB-szint szerint csoportosítva. A Core DB és a Tenant DB **fizikailag külön**
(SD-1) — a kettő közti szaggatott él **nem FK**, hanem a szinkron-replikáció
(`User → TenantUser`, `DefaultCategoryCatalog → Category`).

```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, lasd 5.1 magyarazat) =====
    User ||--o{ TenantUser : "szinkron-replikacio-SD-3"
    DefaultCategoryCatalog ||--o{ Category : "provisioning-masolas-SD-8"
```

> **A diagram olvasása.** A `}o--||` jelölés: a bal oldal több sora a jobb
> oldal egy sorához (pl. sok `Attachment` egy `Ticket`-hez). A `}o--o|`
> opcionális kapcsolat (a FK nullable). A `}o--o{` n-n kapcsolat.
>
> **Két él NEM FK-kapcsolat**, hanem a Core↔Tenant **szinkron-replikáció**:
> a `User → TenantUser` (azonnali szinkron, SD-3) és a `DefaultCategoryCatalog
> → Category` (provisioning-időben, SD-8). A mermaid `erDiagram` nem jelöli
> külön a szaggatott "nem-FK" élt, ezért ezt a két élt a felirat (`szinkron-`,
> `provisioning-`) és ez a magyarázat különbözteti meg. **Fontos:** ez a két
> él **két külön adatbázis** közt fut — nem adatbázis-szintű FK. A `TenantUser`
> és a `Category` a saját tenant DB-jén belül önálló, FK-célként használható
> entitás; a Core-oldali forrásukhoz a replikáció köti őket, nem idegen kulcs.
>
> **Egy harmadik él NEM FK-kapcsolat, de tenant-DB-n belüli:** a
> `WeeklyReportRecipient }|..|{ WeeklyReport` "logikai sokat-sokhoz,
> opcionális, FK nélkül" é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), és csak az összesített
> `deliveryStatus`/`recipientCount` kerül a `WeeklyReport`-ra. A jelölés
> (`..` szaggatott) emlékeztető: ez **nem hagyományos FK**, hanem logikai
> fogyasztás — analóg a Core↔Tenant szinkron-replikációhoz, csak tenant-
> DB-en belül.

> **Amit a diagram nem mutat — a `reporter`.** A `Ticket.reporterId` a polgári
> felhasználóra mutat, de annak entitása még nyitott (NY-2) — a polgári
> adatigény-spec véglegesíti. A diagram ezért a `reporter`-kapcsolatot nem
> rajzolja; a `Ticket`-nek van `reporterId` mezeje, de a célentitás a
> domain-modell jelen körében nincs modellezve.

### 5.2 Entitás-index

A teljes entitás-lista, gyors hivatkozásként. A "Blokk" oszlop a
`00_domain_model.md` szakaszára mutat.

| Entitás | DB-szint | `AuditableEntity` | Blokk | Szerep |
|---|---|---|---|---|
| `Ticket` | Tenant DB | ✓ | 1.2 | A bejelentés — a domain gerince |
| `Attachment` | Tenant DB | ✓ | 1.3 | A bejelentéshez csatolt fájlok |
| `ActivityLog` | Tenant DB | ✗ (append-only) | 1.4 | A bejelentés életciklus-napló |
| `TicketNote` | Tenant DB | ✓ | 1.5 | A bejelentés belső jegyzetei |
| `Category` | Tenant DB | ✓ | 2.1 | A kétszintű kategória-fa |
| `Group` | Tenant DB | ✓ | 2.2 | Terepi munkacsoport |
| `TenantUser` | Tenant DB | ✗ (replikált) | 2.3 | A Core `User` tenant-projekciója |
| `WeeklyReport` | Tenant DB | ✓ | 2.4 | A heti PDF-riport perzisztált pillanatképe |
| `WeeklyReportRecipient` | Tenant DB | ✓ | 2.5 | A heti riport-címzett-lista |
| `User` | Core DB | ✓ | 3.1 | A globális felhasználói fiók |
| `Tenant` | Core DB | ✓ | 3.2 | A tenant-regisztrum |
| `UserTenantRole` | Core DB | ✓ | 3.3 | A `User` ↔ `Tenant` szerepkör-kötés |
| `UserInvitation` | Core DB | ✓ | 3.5 | A felhasználó-meghívó token |
| `DefaultCategoryCatalog` | Core DB | ✓ | 3.4 | Az Urbino default kategória-sablonja |
| `News` | Tenant DB | ✓ | 4.2 | Hír |
| `Event` | Tenant DB | ✓ | 4.3 | Városi program |
| `CityInfo` | Tenant DB | ✓ | 4.4 | Városi információ (külső link) |

Tizenhét entitás: tizenkettő a Tenant DB-ben, öt a Core DB-ben. Az `ActivityLog`
és a `TenantUser` nem `AuditableEntity` — az előbbi append-only esemény-folyam
(J-2), az utóbbi replikált projekció (SD-2).

### 5.3 Az összes `enum` gyűjtött listája

A domain-modellben hivatkozott összes felsorolás-típus, egy helyen. A
kódbeli `enum`-ok ezek szerint jönnek létre.

| `enum` | Értékek | Entitás | Blokk |
|---|---|---|---|
| `TicketStatus` | `New`, `Assigned`, `InProgress`, `Resolved`, `Rejected` | `Ticket.status` | 1.2.3 |
| `TicketPriority` | `Low`, `Normal`, `High` | `Ticket.priority` | 1.2.2 |
| `RejectionReason` | `NotInScope`, `NotValid`, `Duplicate`, `PrivateProperty` | `Ticket.rejectionReasonCode` | 1.2.3 |
| `TicketOrigin` | `CitizenApp`, `Manual` | `Ticket.origin` | 1.2.5 |
| `AttachmentKind` | `ReportPhoto`, `ResolutionPhoto` | `Attachment.kind` | 1.3 |
| `ActivityEventType` | `Created`, `StatusChanged`, `Assigned`, `Reassigned`, `Rejected`, `Reopened`, `Merged` | `ActivityLog.eventType` | 1.4 |
| `TenantRole` | `dispatcher`, `manager`, `content_manager`, `field_worker` | `UserTenantRole.role`, `TenantUser.roles` | 2.3, 3.3 |
| `UserStatus` | `Invited`, `Active`, `Disabled` | `User.status`, `TenantUser.status` | 2.3, 3.1 |
| `InvitationStatus` | `Pending`, `Accepted`, `Expired`, `Revoked` | `UserInvitation.status` | 3.5 |
| `TenantStatus` | `Setup`, `Active`, `Suspended`, `Archived` | `Tenant.status` | 3.2 |
| `ContentStatus` | `Draft`, `Published`, `Archived` | `News`/`Event`/`CityInfo.contentStatus` | 4.1 |
| `ReportGenerationStatus` | `Pending`, `Generating`, `Completed`, `Failed` | `WeeklyReport.generationStatus` | 2.4 |
| `ReportDeliveryStatus` | `Pending`, `AllSent`, `PartialFailure`, `AllFailed` | `WeeklyReport.deliveryStatus` | 2.4 |

> **`enum` névütközés-figyelmeztetések.** Az `Active` érték három `enum`-ban
> is szerepel (`UserStatus`, `TenantStatus` — és fogalmilag rokon mindkettő),
> az `Archived` kettőben (`TenantStatus`, `ContentStatus`). A `Pending`
> érték **két** `enum`-ban szerepel: a `ReportGenerationStatus`-ban (a
> generálás még nem indult) és a `ReportDeliveryStatus`-ban (a kézbesítés
> még nem futott). Ezek **nem hibák** — külön `enum`-típusok, a C#
> típusrendszerben nem ütköznek. De a fejlesztőnek és a Claude Code-nak
> tudnia kell: a kontextus (melyik entitás melyik mezeje) dönti el, melyik
> érték értelmezendő. A `Rejected` ugyanígy szerepel a `TicketStatus`-ban
> és az `ActivityEventType`-ban — szándékosan, mert az elutasítás státusz
> is és naplóesemény is.

### 5.4 A kétszintű DB-modell összefoglaló képe

A `00_domain_model.md` teljes entitás-készlete a két DB-szint szerint:

**Core DB** (egyetlen, tenant-független adatbázis):
`User`, `Tenant`, `UserTenantRole`, `DefaultCategoryCatalog`.

**Tenant DB** (tenantonként egy, a pilotra: Balatonalmádi):
`Ticket`, `Attachment`, `ActivityLog`, `TicketNote`, `Category`, `Group`,
`TenantUser`, `WeeklyReport`, `WeeklyReportRecipient`, `News`, `Event`,
`CityInfo`.

A Core→Tenant replikáció két útja: a `User`/`UserTenantRole` → `TenantUser`
(azonnali szinkron, SD-3) és a `DefaultCategoryCatalog` → `Category`
(provisioning-időben, SD-8). Minden más Tenant DB-entitás tisztán
tenant-lokális, és a működési lekérdezések cross-DB join nélkül futnak (SD-2).

### 5.5 A domain-modell és a következő dokumentumok

A `00_domain_model.md` az **adatszerkezetet** rögzíti. Amit tudatosan **nem**
tartalmaz, és a következő dokumentumok töltik ki:

- **Az állapot-átmenetek.** A `Ticket.status` öt értéke itt rögzült, de a
  *megengedett átmenetek* (melyik állapotból melyikbe, milyen akcióval,
  milyen jogosultsággal, milyen feltétellel) a **`02_globalis_allapotgep.md`**
  dolga. A `TenantStatus` és a `ContentStatus` átmenetei a vonatkozó
  feature-specekben.
- **A validációs szabályok.** A mező-táblák a kötelezőséget és a feltételeket
  jelzik, de a FluentValidation-szabályok teljes készlete (a
  `latitude`/`longitude` együtt-járása, a felelős-kizárólagosság, az
  `endsAt ≥ startsAt`, stb.) az egyes **feature-specekbe** tartozik.
- **Az API-szerződések és a jogosultság.** Az entitások itt vannak; a
  végpontok, a `BaseController`-illesztés és az `authorization.json`-bejegyzések
  a feature-speceké.
- **A polgári oldal nyitott elemei.** A `Ticket.reporterId` célentitása
  (NY-2) a polgári adatigény-specre vár.

---

## Tervezési döntések ezekben a blokkokban — véglegesítve

Az 1. blokk TD-1 — TD-13 pontjait az alapító 2026.05.18-án eldöntötte; a 2.
blokk a kategória-modell döntésével (SD-18), a 3. blokk a Core-entitások két
döntésével (SD-19, SD-20), a 4. blokk a tartalmi modell három döntésével
(SD-21 — SD-23) bővült. Az alábbi táblázatok a **véglegesített** állapotot
rögzítik; a teljes szöveg a `99_donesnaplo.md`-ben.

### Naplózott specifikációs döntések (SD-14 — SD-23, és SD-68 — SD-71)

| SD | Döntés | Forrás |
|---|---|---|
| **SD-14** | Az `assignedAt` és `resolvedAt` esemény-időpont denormalizáltan a `Ticket`-en is (az `ActivityLog` mellett) — a riport-aggregáció teljesítményéért | 1.2.8 |
| **SD-15** | A csatolmány-entitás általános `Attachment` (nem `Photo`), `AttachmentKind` + `contentType` mezővel; a bináris adat S3-kompatibilis blob storage-ban, az `Attachment.fileRef` hivatkozás | 1.3 |
| **SD-16** | A `Ticket` `ticketNumber` mezőt kap (tenant-szintű folyamatos sorszám); a belső `id` nem jelenik meg a felhasználónak; a megjelenő azonosító `Tenant.displayPrefix` + `ticketNumber` (pl. `ALM-234`) | 1.2.1, 3.2 |
| **SD-17** | A belső jegyzet külön `TicketNote` entitás (több bejegyzés, szerzővel); a `Ticket.internalNote` mező és a `NoteAdded` naplóesemény elmarad | 1.5 |
| **SD-18** | A `Ticket.categoryId` bármely szintű `Category`-ra mutathat; a besorolás gyökér-kategóriáig is teljes, az alkategória nem kötelező | 2.1 |
| **SD-19** | A `User` globális entitás, nincs rajta szerepkör-mező; a szerepkör a `UserTenantRole` kötésen él, tenantonként eltérhet | 3.1 |
| **SD-20** | A `UserTenantRole` explicit kötés-entitás (nem `[ManyToManyConnection]`), mert saját adata van (`role`) és `AuditableEntity` | 3.3 |
| **SD-21** | A `News`, `Event`, `CityInfo` külön entitás (nem egy közös `Content` entitás típus-mezővel), bár szerkezetileg rokonok | 4. |
| **SD-22** | A tartalmi életciklus `ContentStatus` enum (`Draft`/`Published`/`Archived`), nem egyszerű `isPublished` bool | 4.1 |
| **SD-23** | **Megerősítve a `60_tartalom`-spec által:** a tartalmi entitások szerzője az `AuditableEntity.createdBy`, nincs külön `authorId` | 4.1 |
| **SD-68** | A `News.coverImageRef` és `Event.coverImageRef` az SD-60 "egyedi-fájl-mező" mintáját követi — dedikált `POST/DELETE /v1/{entity}/{id}/cover-image` végpontok, standard `PUT`-on `field_not_editable` | 4.2, 4.3 |
| **SD-69** | `News.body` és `Event.body` HTML-tárolású, szigorú szerver-oldali szanitizációval (whitelist a `60_tartalom`-spec 3.1.3-ban); max 10 000 karakter HTML-tartalommal együtt | 4.2, 4.3 |
| **SD-70** | `ContentStatus` állapotgép három állapottal és négy átmenettel; T3 (Archiválás) és T2 (Visszavonás vázlatba) két külön akció publikált tartalmon; `DELETE` engedélyezett `Draft`/`Archived`-re, `Published`-re guard. Részletek a `60_tartalom`-spec 2.9-ben és a `02_globalis_allapotgep.md` új blokkjában | 4.1 |
| **SD-71** | `CityInfo.groupLabel` szabad szöveges címke (max 100 karakter), nem lookup-entitás; admin-űrlap autocomplete distinct-végpontról (`GET /v1/city-info-groups`) | 4.4 |

### A TD-pontok lezárása — összegző tábla

| TD | Lezárás |
|---|---|
| TD-1 | `ticketNumber` + `Tenant.displayPrefix`-prefix (SD-16). A `Tenant.displayPrefix` a 3. blokkban kap definíciót |
| TD-2 | Egyetlen `description` mező; a `shortDescription` elmarad. *A polgári Flutter-képernyő két-mezős — lásd NY-1* |
| TD-3 | Külön `citizenSuggestedCategoryId` mező — a polgár nyers javaslata megőrződik |
| TD-4 | Két külön nullable FK (`assignedGroupId`, `assignedUserId`); a kizárólagosságot FluentValidation adja |
| TD-5 | `rejectionReasonCode` kötelező `Rejected`-ben, `rejectionReasonText` opcionális; `Duplicate`-nél a `rejectionReasonText` üres, a megjelenő szöveg kliens-oldali sablonból az `originalTicketId` alapján (SD-40) |
| TD-6 | A `reopenReasonText` `Ticket`-mező **törölve**; a visszanyitás-indok az `ActivityLog` `Reopened`-eseményében él |
| TD-7 | `resolutionNote` opcionális mező; a "fotó VAGY szöveg" szabály az állapotgép-átmenet feltétele |
| TD-8 | `TicketOrigin` = `CitizenApp` / `Manual`; nincs `FieldApp` a pilotra (tudatos kihagyás) |
| TD-9 | A `reporterId` típusa nyitva — a polgári adatigény-spec véglegesíti. Lásd NY-2 |
| TD-10 | Külön `TicketNote` entitás (SD-17) |
| TD-13 | Egy esemény — egy naplósor, a legspecifikusabb `eventType`-pal |

---

## Nyitott kérdések — állapot

### Lezárt kérdések

| # | Kérdés | Lezárás |
|---|---|---|
| **NY-1** | A polgári Flutter `screen_uj_bejelentes_2` két leírás-mezője vs. a domain egyetlen `description` mezeje | **Lezárva (2026.05.18):** a polgári app is egyetlen leírás-mezőre módosul; a két-mezős képernyő elavult. *Visszacsorgó teendő: a Flutter `screen_uj_bejelentes_2` frissítendő — a polgári mobilapp tervezése felé* |
| **NY-3** | A `Category.iconRef` formátuma — ikonkészlet-kulcs vagy feltöltött kép | **Lezárva (2026.05.18):** közös, előre definiált ikonkészlet; az `iconRef` a kulcsot tárolja, nem fájlt. A polgári app és az admin ugyanabból a szettből rajzol. A konkrét ikonkészlet a feature-spec / admin design-réteg dolga |
| **NY-5** | A meghívott, de még nem aktivált `User` és a Zitadel-identitás kötése — mikor tölti ki a meghívó-flow az `externalAuthId`-t | **Lezárva (2026.05.19):** az `externalAuthId` a meghívó **beváltásakor** töltődik ki, a Zitadel-oldali fióklétrehozás eredményéből; a `User.status` ekkor vált `Invited → Active`-ra. Forrás: `20_admin_felulet/20_felhasznalokezeles.md` v1.0 |
| **NY-6** | A tartalmi entitások (`News`, `Event`) `body` mezőjének formázása | **Lezárva (2026.05.20):** HTML-tárolás szigorú szanitizációval (whitelist: `<p>`, `<br>`, `<strong>`, `<em>`, `<ul>`, `<ol>`, `<li>`, `<a href="https://...">`); max 10 000 karakter. Forrás: `20_admin_felulet/60_tartalom.md` v1.0, SD-69 |
| **NY-7** | A `CityInfo.groupLabel` — szabad szöveges címke vagy lookup | **Lezárva (2026.05.20):** szabad szöveges címke (max 100 karakter), nem lookup-entitás. Az admin-űrlap autocomplete a meglévő distinct értékekről egy `GET /v1/city-info-groups`-végpontról. Forrás: `20_admin_felulet/60_tartalom.md` v1.0, SD-71 |

### Nyitott kérdések

| # | Kérdés | Kinek a hatásköre |
|---|---|---|
| **NY-2** | A `reporterId` cél-típusa — a polgári felhasználó tenant-oldali entitása. A polgári auth és a polgári felhasználó modellje nyitott (Ü-1, `02_kerdeslista` 4.) | **Polgári adatigény-spec** |
| **NY-4** | A `TenantUser.roles` tárolási formája — a `TenantRole` `enum` tömb a tenant DB-ben (PostgreSQL natív tömb vagy join-tábla). Tisztán technikai | **Fejlesztői döntés** — a `01_kozos_mintak` szinkron-mechanizmusával együtt zárul; nem blokkol |
| **NY-K1** | A `DefaultCategoryCatalog` (3.4) formális mezőtáblája hiányzik. A `30_beallitasok.md` 3.1.4 (`POST /v1/categories/from-default`) és 3.1.5 (`GET /v1/categories/available-default-roots`) 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 Beállítások feature-spec kódolható. A `60_tartalom` feature-spec (v1.0) **nem érintette** a `DefaultCategoryCatalog`-ot (a 4. blokk tartalmi entitások, nem a 3.4 default-katalógus) — a megtartott hiány továbbra is fennáll | **Urbino-admin spec** (vagy a következő, kategória-fát érintő specifikációs lépés) |

> A nyitott kérdések egyike sem blokkol. Az NY-2 a polgári adatigény-spec
> szála; az NY-4 fejlesztői; az NY-K1 az Urbino-admin spec hatásköre, és a
> jelenlegi feature-specek (Beállítások, `60_tartalom`) implementációját nem
> blokkolja. Az NY-1, NY-3, NY-5, NY-6, NY-7 lezárult.

---

## Hivatkozott dokumentumok

- `00_terminologia.md` — a `Ticket` és kísérői fogalmi definíciója
- `01_kozos_mintak.md` — SD-1 (DB-szintek), SD-2 (`TenantUser`), SD-4 / SD-16
  (`Tenant.displayPrefix`), SD-9 (UTC), J-2 (`ActivityLog` külön entitás)
- `kanonikus_donek.md` — K-005, K-028, K-029, K-030, K-031, K-032, K-034
- `10_triage_flow_v1.md` — a triage, a státusz-életciklus, az elutasítás, a
  lezárás-dokumentáció, a tevékenység-napló követelménye
- `20_duplikacio_v1.md` — a duplikáció-összevonás és az eredeti/duplikátum
  kapcsolat
- `40_riport_v1.md` — az átlagos lezárási idő (SD-14 indoka)
- `02_architekturalis_kerdeslista.md` — a fájltárolás (S3), a polgári
  felhasználó tenant-oldali referenciája
- `99_donesnaplo.md` — az SD-14 — SD-17 döntések teljes szövege

---

## Verziónapló

- **v0.1 (2026.05.18)** — Első blokk-vázlat: a `Ticket` entitás mezőlistája,
  az `Attachment` és az `ActivityLog`; két naplózott döntés és tizenegy
  megerősítést kérő tervezési pont (TD-1 — TD-13).
- **v0.2 (2026.05.18)** — Az 1. blokk **véglegesítve**: a TD-1 — TD-13
  döntések átvezetve. A `publicId` helyett `ticketNumber` (SD-16); egyetlen
  `description` mező; új `citizenSuggestedCategoryId`; a `reopenReasonText`
  törölve; új `TicketNote` entitás a belső jegyzeteknek (SD-17); a `NoteAdded`
  naplóesemény elhagyva. Négy naplózott döntés (SD-14 — SD-17), két nyitott
  kérdés (NY-1, NY-2) a polgári adatigény-spec felé.
- **v0.3 (2026.05.18)** — A 2. blokk: `Category` (kétszintű fa, `parentId`
  self-FK, `sourceCatalogId`), `Group` (n-n `TenantUser`-tagság), `TenantUser`
  (a Core `User` projekciója). A kategória-modell döntése (SD-18 — a besorolás
  bármely szinten teljes). Az 1. blokk `categoryId`-sorai SD-18 szerint
  javítva, a "→ 2. blokk" előre-hivatkozások feloldva. Két új nyitott kérdés
  (NY-3, NY-4).
- **v0.4 (2026.05.18)** — A 3. blokk: a Core DB entitásai — `User` (globális
  fiók, `UserStatus`), `Tenant` (a teljes regisztrum-séma, köztük az SD-16
  `displayPrefix` mezeje), `UserTenantRole` (a szerepkör-kötés),
  `DefaultCategoryCatalog`. Két új döntés (SD-19 — a `User` szerepkör-mentes;
  SD-20 — a `UserTenantRole` explicit kötés-entitás). A `Category.sourceCatalogId`
  előre-hivatkozása feloldva. Egy új nyitott kérdés (NY-5).
- **v0.5 (2026.05.18)** — A 4. blokk: a tartalmi entitások — `News`, `Event`,
  `CityInfo`, közös publikálási vázzal (`ContentStatus`). Három új döntés
  (SD-21 — három külön entitás; SD-22 — `ContentStatus` enum; SD-23 — a
  tartalmi szerző a `createdBy`). Az NY-1 (egy leírás-mező a Flutteren is) és
  az NY-3 (közös ikonkészlet) **lezárva** az alapítói döntés nyomán; két új
  nyitott kérdés (NY-6, NY-7).
- **v1.0 (2026.05.18)** — Az 5. blokk: a teljes entitás-térkép (mermaid),
  az entitás-index (tizennégy entitás), az összes `enum` gyűjtött listája
  (tíz `enum`), a kétszintű DB-modell összefoglaló képe. A domain-modell
  **adatszerkezeti szinten teljes** — új entitást és döntést az 5. blokk nem
  hozott. A következő dokumentumok (`02_globalis_allapotgep.md`, feature-specek)
  az állapot-átmeneteket, a validációt és az API-szerződéseket töltik ki.
- **v1.1 (2026.05.18)** — Az első feature-spec
  (`20_admin_felulet/10_bejelentes_lista_es_adatlap.md` v1.0) egyetlen
  domain-érintő pontja átvezetve: az 1.2.8 szakasz egy tervezési-döntés
  megjegyzéssel bővült az `updatedAt` optimistic-concurrency token szerepéről
  (SD-32). **Nem új mező, nem új entitás** — az `updatedAt` már létezett; a
  megjegyzés a mező kettős (audit + concurrency-token) szerepét rögzíti, és
  jelzi az eltérést a `BaseController` standard update-mintájától. A
  domain-modell entitás-, mező- és enum-készlete változatlan.
- **v1.2 (2026.05.18)** — A bejelentés-lista és -adatlap feature-spec
  validációs köre nyomán hat addig hiányzó hosszkorlát átvezetve a
  mezőtáblákba: `Ticket.description` (4000), `rejectionReasonText` (1000),
  `resolutionNote` (2000), `reporterContactText` (200), `TicketNote.body`
  (2000). A `Ticket.title` (200), `addressText` (300), `Category.name` (100)
  már korábban rögzült. **Nem új mező, nem új entitás** — a meglévő mezők
  méret-definíciójának pótlása. A `News`/`Event` `body` mezeje nem érintett —
  azok formázása/mérete a `60_tartalom`-spec dolga (NY-6).
- **v1.3 (2026.05.19)** — A felhasználókezelés feature-spec
  (`20_admin_felulet/20_felhasznalokezeles.md` v1.0) átvezetve. Új entitás: a
  `UserInvitation` (3.5 szakasz, Core DB) — a felhasználó-meghívó token, saját
  életciklussal; új `enum`: az `InvitationStatus` (`Pending`/`Accepted`/
  `Expired`/`Revoked`). A `TenantUser` (2.3) `isActive` bool mezeje `status`
  enumra változott (`UserStatus` tükre, SD-37) — az `Invited` felhasználó is
  projektálódik. Az entitás-térkép, az entitás-index (tizenöt entitás), az
  enum-lista és a mermaid-diagram frissítve. Az **NY-5 lezárva** — az
  `externalAuthId` a meghívó beváltásakor töltődik ki. A `User.email`
  hosszkorlátja a `VF-felh-1` visszacsorgó jelzés tárgya — lásd lent.
- **v1.3 függőben — VF-felh-1.** A felhasználókezelés feature-spec jelezte,
  hogy a `User.email` (3.1) mezőnek nincs explicit hosszkorlátja. Javasolt
  érték: max 254 karakter (RFC 5321). A `00_domain_model.md` gazdája ezt a
  3.1 mezőtáblába vezeti — ez egy adatszerkezeti pótlás, a feature-spec
  `VF-felh-1` jelzése nyomán.
- **v1.4 (2026.05.19)** — A triage- és státusz-kezelés feldolgozó kör egyetlen
  domain-modell-pontosítása. A `Ticket.rejectionReasonText` (1.2.3) `Duplicate`-eset
  pontosítva: a mező `Duplicate` elutasítási indoknál **üres marad** (`null`),
  nem szerver-oldalon generált szöveget hordoz; a duplikáció-hivatkozást
  strukturáltan az `originalTicketId`/`originalTicketDisplayId` adja, a megjelenő
  szöveget a kliens állítja össze i18n sablon-kulcsból (SD-40). A TD-pont-lezáró
  tábla TD-5 sora ezzel összhangba hozva. **Nem új mező, nem új entitás, nem
  típusváltozás** — a `Duplicate`-eset viselkedésének pontos leírása. A K-034
  nem sérül: az "eredeti-ügy hivatkozás" a strukturált mezőpárral teljesül.
  Forrás: `99_donesnaplo.md` SD-40.
- **v1.5 (2026.05.19)** — A duplikáció-szűrés és összevonás feature-spec két
  magyarázó megjegyzés-sora az 1.2.6 (Duplikáció) szakaszban. **SD-42** — az
  eredeti ügy értesítendőinek halmaza lekérdezésből áll elő (`reporterId` ∪ a
  rá mutató duplikátumok `reporterId`-jai); nincs `TicketSubscriber` entitás,
  az `originalTicketId` beírása maga az átkötés. **SD-48** — az
  `originalTicketDisplayId` származtatott DTO-mező, nem tárolt domain-mező; a
  `Ticket` egyetlen tárolt duplikáció-mezeje az `originalTicketId` self-FK (az
  SD-40 "mezőpár" megfogalmazásának pontosítása). **Nem új mező, nem új
  entitás, nem típusváltozás** — két hallucináció-megelőző magyarázó megjegyzés.
  Forrás: `99_donesnaplo.md` SD-42, SD-48; `20_admin_felulet/20_duplikacio_es_osszevonas.md` v1.0.
- **v1.6 (2026.05.20)** — A Dashboard és heti PDF-riport feature-spec
  (`20_admin_felulet/40_riport.md` v1.0) egy új entitást vezet be: a
  `WeeklyReport`-ot a Tenant DB-ben (SD-49). Az entitás `AuditableEntity`,
  a 2.4 alszakaszban él (a 2. blokk önálló operatív entitása, a
  referencia-háromas — `Category`, `Group`, `TenantUser` — után); a blokk
  bevezetője "Referencia- és önálló operatív entitások" címre tágult.
  Két új enum: `ReportGenerationStatus` (négy érték) és
  `ReportDeliveryStatus` (négy érték). Az entitás-index tizenötről
  tizenhatra (a Tenant DB tízről tizenegyre); az enum-lista tizenegyről
  tizenháromra; a névütközés-figyelmeztetés kiegészülve a `Pending`
  névütközéssel. A Dashboard nem hoz domain-elemet — tisztán a `Ticket`
  feletti aggregáció (a forrás-leképezés a feature-spec 2.5-ben). A
  `Ticket`, `Group`, `TenantUser`, `Category` érintetlen.
- **v1.7 (2026.05.20)** — A Beállítások feature-spec
  (`20_admin_felulet/30_beallitasok.md` v1.0) két helyen érinti a
  domain-modellt: a `Tenant` entitás négy új mezővel bővül (SD-58), és egy
  új entitás keletkezik a Tenant DB-ben (SD-59, `WeeklyReportRecipient`).
  **A 3.2 `Tenant`-tábla** négy új sorral bővült: `logoFileRef` (S3-
  objektum-hivatkozás, az `Attachment.fileRef`-mintával konzisztens, max
  500 karakter); `contactAddress` (max 300); `contactPhone` (max 50);
  `contactEmail` (max 200, e-mail formátum). Mind opcionális; mind
  **szerkeszthető** a vezető által (a `displayPrefix`, `dbConnectionRef`,
  `status`, `timeZone` és `code` mezőkkel ellentétben — ezek
  `urbino_admin`-hatáskör). A `logoFileRef` szerkesztése dedikált
  multipart-végponton megy (SD-60 "egyedi-fájl-mező" minta), nem a
  `Tenant`-PUT-tal. A táblázat alá három megjegyzés-blokk került: a
  "tartalom-paraméter, nem brand-paraméter" határ konkrét mezőkben (K-037
  érvényesítése), és a `logoFileRef` mintázat-leírása. **A 2.5 új
  szakaszban** a `WeeklyReportRecipient` entitás teljes leírása: a heti
  riport-címzett-lista rekord-szintű reprezentációja (név, e-mail,
  `isActive`), `AuditableEntity`-vel, e-mail egyedi a tenanton belül. A
  `WeeklyReport`-tal **nincs FK-kapcsolat** — logikai fogyasztás
  (`40_riport.md` SD-56 és SD-57 alapján). A 2. blokk bevezetője
  "öt entitásra" tágult; az 5.1 entitás-térkép mermaid-diagram egy új
  csomóponttal és a `}|..|{` szaggatott logikai-fogyasztás éllel
  bővült; az 5.2 entitás-index tizenhatról tizenhétre (a Tenant DB
  tizenegyről tizenkettőre); az 5.4 DB-modell-összegzés ugyanígy
  frissült. Új enum nincs. Egy új nyitott kérdés (NY-K1) a
  `DefaultCategoryCatalog` formális mezőtáblájának hiánya — a
  Beállítások feature-spec kódolását nem blokkolja, az API-szerződések
  egyértelműek; a `00_domain_model.md` 3.4 későbbi pontosítása a
  `60_tartalom`-spec köréhez tartozik. A `Category` és `Group` entitások
  mezőtáblája **érintetlen** — a Beállítások csak végpont-szintű
  eltéréseket vezet be rájuk (SD-62 — SD-65), nem új mezőket.
- **v1.8 (2026.05.20)** — A `60_tartalom` feature-spec
  (`20_admin_felulet/60_tartalom.md` v1.0) átvezetése a 4. blokkban. **A 4.2
  (`News`) mezőtáblája egy új sorral bővült** (`pushOnPublish`, bool, K,
  default `false`; hírenkénti push-jelzés a polgári mobilapp felé). **A `body`
  mező** mindkét tartalmi entitásnál (`News`, `Event`) lábjegyzettel
  pontosított: HTML-tárolás szigorú szanitizációval, whitelist (`<p>`,
  `<br>`, `<strong>`, `<em>`, `<ul>`, `<ol>`, `<li>`, `<a href="https://...">`),
  max 10 000 karakter; az **NY-6 lezárva** (SD-69). **A `coverImageRef`
  mezőhöz** mindkét entitásnál lábjegyzet: az SD-60 "egyedi-fájl-mező" minta
  alkalmazása — dedikált `POST/DELETE /v1/{entity}/{id}/cover-image`
  végpontokon szerkeszthető, standard `PUT`-on `field_not_editable` (SD-68);
  a **VF-K1 visszacsorgó jelzés lezárva**. **A 4.4 (`CityInfo`) mezőtáblája
  három méret-korláttal bővült:** `url` max 2000 karakter + szigorúan
  `https://` előtag (`Uri.TryCreate`-validáció); `iconRef` max 50 karakter +
  whitelist; `groupLabel` max 100 karakter. **A `groupLabel` lábjegyzete
  átírva** — szabad szöveges címke, nem lookup-entitás; autocomplete a
  meglévő distinct értékekről egy `GET /v1/city-info-groups`-végpontról; az
  **NY-7 lezárva** (SD-71). **A 4.3 (`Event`) `startsAt` mezője** egy
  rövid kiegészítéssel ("Múltbeli megengedett") pontosítva; az `endsAt ≥
  startsAt` reláció a `60_tartalom`-spec 3.3.3-ban formalizálva. **A 4.1
  szakasz SD-23 lábjegyzete lezárva** — a `60_tartalom`-spec a `createdBy`-
  alapú szerző-megjelenítést megerősítette (D-T4 a feature-specben);
  dedikált `authorId` mezőre nincs igény. **A Nyitott kérdések táblázat
  frissült:** NY-6 és NY-7 átkerült a lezárt kérdésekhez, NY-K1
  hatásköre az Urbino-admin spec-re módosult (a `60_tartalom` nem érintette
  a `DefaultCategoryCatalog`-ot). **Az SD-tábla négy új sorral bővült:**
  SD-68, SD-69, SD-70 (a `ContentStatus` állapotgép formalizálása —
  részleteit a `02_globalis_allapotgep.md` új blokkja és a `60_tartalom`-spec
  2.9 tartalmazza), SD-71. **Új entitás nincs, új enum nincs, a 17 entitás
  és a 13 enum változatlan** — a `60_tartalom` feature-spec a meglévő
  entitásokat finomította és formalizálta, nem új domain-elemet vezetett be.
