# Dashboard és heti PDF-riport

**Dokumentum-típus:** feature-spec (admin felület)
**Modul:** Manager felület → Dashboard és heti PDF-riport
**Státusz:** munkadokumentum — átnézésre kész
**Verzió:** v1.0
**Dátum:** 2026.05.20
**Cél olvasó:** senior fejlesztő + Claude Code
**Érintett URL-ek:** `/fooldal`, `/riportok`, `/riportok/details/<id>`

> **Mire való ez a fájl.** A vezetői riport-modul két komponensének — a
> Dashboardnak és a heti PDF-riportnak — a fejlesztői specifikációja. A
> funkcionális tervet (`40_riport_v1.md`) **fejlesztői mélységgel tölti ki**:
> domain-modell, aggregációs API-szerződések, a PDF-generálás és e-mail-
> kézbesítés implementációs szintje, jogosultság, validáció, AC-k.
>
> **Mi NEM ez.** Nem írja újra a funkcionális tervet — arra hivatkozik. Nem
> tervezi újra az admin-keretet (`00_architektura_v4.md`), a jogosultság-
> mátrixot (`05_jogosultsagok_v2.md`), a nap/hét-definíciót
> (`01_kozos_mintak.md` 5.2 / SD-10) — ezeket alkalmazza.
>
> **Az MH-J1 lezárandó szála.** A `05_jogosultsag_es_authorization.md` 8.3
> MH-1 / `99_donesnaplo.md` MH-J1 megtartott hiány a `40_riport` modul pontos
> API-route-neveit a feature-spec hatáskörébe utalta. **Ez a feature-spec
> lezárja** — lásd 3.1.

---

## 0. Funkcionális alap

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

| Dokumentum | Szakasz | Mit ad |
|---|---|---|
| `40_riport_v1.md` | 2.–7. (teljes) | A két komponens funkcionális terve: Dashboard három blokkja, Wow #2 lefúrás, heti PDF szerkezete, e-mail-kézbesítés, pilot-scope |
| `00_architektura_v4.md` | 4.1, 5.2, 6.1–6.3 | A `/fooldal` és `/riportok` URL-modell, Dashboard-keret, riport-mag megerősítése |
| `90_sitemap_v3.md` | 2.1, 2.5, 3. | A `/fooldal` és `/riportok` nézet-leltár, a nézet-szintű jogosultság-térkép |
| `05_jogosultsag_es_authorization.md` | 3.2 E-blokk, 3.3 Szabály-R1, 8.3 MH-1 | A kötelező előírás: minden `40_riport`-route `roles` = `["tenant_manager"]` |
| `01_kozos_mintak.md` | 5.2 / SD-10, 6., 7. | A nap/hét-definíció (szerver-oldali, tenant-időzóna), API-konvenciók, audit |
| `00_domain_model.md` | 1.2.8, 1.4, 2.2, 2.3, 5.2 | Az aggregáció forrásmezői: `Ticket.createdAt`/`assignedAt`/`resolvedAt`/`dueDate`/`priority`/`status`, a `Group`, a `TenantUser` |
| `02_globalis_allapotgep.md` | 1., 3.4, 3.5 | A `TicketStatus` öt értéke, a `resolvedAt` szemantikája (visszanyitásnál `null`-ra áll) |
| `10_bejelentes_lista_es_adatlap.md` | 3.2, 4.2, 5.4 | A `/bejelentesek` lista `?tab=` és `?assignee=` szűrő-szerződése, SD-30 tab-szemantika, NY-bej-5 |
| `20_felhasznalokezeles.md` | 2.4 | A `TenantUser.status` (SD-37) |
| `50_konfig_v2.md` | 5. | A heti riport címzett-listája (fogyasztott) |

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

- **K-011** — a vásárlói átbillenés a vezetői riport-modulon dől el; a piloton
  alapszinten élesednie kell. Ez a feature **üzleti gerince**.
- **K-035** — a riport-modul két komponens: belső Dashboard + kifelé
  kommunikálható heti PDF; a névre bontott teljesítmény csak a Dashboardon
  jelenik meg, a PDF név nélkül, összesítve. A `90_sitemap_v3.md` 8. szerint
  **megerősített** — a feature-spec adottként veszi.
- **K-016** — nincs konfigurálható riport-építő, nincs custom időablak a
  pilotra; fix sablon.
- **K-027** — a vezető landingje a `/fooldal` (Dashboard).
- **K-029** — a `Ticket` öt állapota — az aggregáció ezeket számolja.
- **K-009** — pilot-scope fegyelem (kerethivatkozás).

### 0.3 Mit döntött már el a funkcionális réteg (hivatkozás, nem ismétlés)

- A Dashboard három blokkja: négy KPI-kártya ("Ez a hét") + terepi
  csapat-teljesítmény tábla; chartok nincsenek (`40` 3.2, 3.4).
- A Wow #2 lefúrás: a KPI-kártya és a csapat-tábla név-cellája **link a
  meglévő `/bejelentesek` listára**, előszűrve — nem új képernyő (`40` 3.3).
- A heti PDF négy szakasza: *a hét számai*, *a múlt héthez képest*,
  *kategória szerinti bontás*, *lábléc*. Fix sablon (K-016); narratíva =
  viszonyítás; generált prózai értékelés nincs (`40` 4.2, 4.3).
- E-mail-kézbesítés: hétfő reggel automatikus generálás az előző naptári
  hétre, e-mail-mellékletként a címzett-listának; archívum a `/riportok`-ban
  (`40` 4.4).
- A nap/hét-definíciót a **szerver** számítja tenant-időzónában (SD-10) — a
  Dashboard és a PDF ugyanazt használja (`40` 3.5).
- A címzett-listát a `50_konfig` kezeli (`/beallitasok/altalanos`, `40` 4.5);
  ez a feature-spec azt **fogyasztja**.
- Jogosultság: a Dashboard és a riport **vezető-only**
  (`05_jogosultsagok_v2.md` 2.7, Szabály-R1).

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

A funkcionális terv megmondta, *mit lát a vezető*. A feature-spec ezt
fejlesztői mélységgel tölti ki:

1. A heti PDF perzisztált entitása (`WeeklyReport`) — archívum + e-mail-
   újraküldés alapja.
2. Az aggregációs API-szerződések — a `GET /v1/dashboard` és a `WeeklyReport`
   végpontok pontos lekérdezési predikátumokkal; minden KPI/oszlop formális
   definíciója `Ticket`-mezőkre vezetve.
3. A PDF-generálás implementációs szintje — háttér-job, idempotencia, S3-
   tárolás, generálás-pipeline.
4. Az e-mail-kézbesítés implementációs szintje — best-effort, manuális
   újraküldés, kézbesítés-státusz.
5. **Az MH-J1 lezárása** — a `40_riport` modul hat pontos API-route-neve,
   mindegyik `roles` = `["tenant_manager"]` (Szabály-R1).
6. Validáció, edge case-ek, MVP vs. iteráció szétbontás, nyitott kérdések.

---

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

A Dashboard a vezető (`tenant_manager`) belső munkaeszköze a `/fooldal`-on:
napi/heti operatív ránézés a Tenant DB `Ticket`-adatára. A heti PDF-riport
a kifelé (polgármester és más kommunikációs címzettek felé) automatikusan
generált, e-mailben küldött összegző dokumentum, fix sablonnal. **Üzleti
érték:** K-011 a vásárlói átbillenés magját ehhez a modulhoz kötötte; a Wow
#1 (proaktív kommunikáció) és a Wow #2 (csapat-rácsodálkozás) hordozója.

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

- A `50_konfig` címzett-lista **kezelése** (a `/beallitasok/altalanos`
  oldalon) — a feature ezt fogyasztja, de a CRUD-ja a `50_konfig` dolga.
- Polgári elégedettség-mérés — a `40` 7.3, `10` NY-bej-5 szerint a polgári
  mobilapp hatásköre; a pilot-riport elégedettségi mutató nélkül készül
  (NY-R2, 8.5).
- Heti trend-grafikon, kategória-kördiagram, custom KPI-k, havi riport,
  Excel-export, munkatárs "teljesítmény-profil" oldal — 6-12. hó (`40` 5.2).
- Terepi mobilapp dashboard-vetülete — nincs.

---

## 2. Domain-modell

### 2.1 Áttekintés — mi új, mi módosul

| Elem | Státusz | DB-szint |
|---|---|---|
| `WeeklyReport` | **új entitás** (SD-49) | Tenant DB |
| `ReportGenerationStatus` enum | **új enum** — `Pending` / `Generating` / `Completed` / `Failed` | — |
| `ReportDeliveryStatus` enum | **új enum** — `Pending` / `AllSent` / `PartialFailure` / `AllFailed` | — |
| `Ticket` | **változatlan** — a Dashboard és a PDF kizárólag olvassa | Tenant DB |
| `Group`, `TenantUser`, `Category` | **változatlan** — az aggregáció forrásaként hivatkozott | Tenant DB |

A Dashboard **nem vezet be entitást**: a négy KPI és a csapat-tábla a
`Ticket` (és kísérői) feletti számolt lekérdezés.

### 2.2 Új entitás — `WeeklyReport`

**DB-szint:** Tenant DB
**Származik:** `AuditableEntity` (`createdAt/By`, `updatedAt/By` automatikus —
`01_kozos_mintak.md` 7.1)
**Kapcsolódó döntések:** SD-49 (az entitás), SD-55/c (idempotencia), SD-56
(kézbesítés-státusz)
**Kapcsolódó dokumentum:** `40_riport_v1.md` 4. (a heti PDF funkcionális
terve)

| 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. Megjegyzés: 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 (1–53). A `40` 4.2 mintája "28. hét" — ez az érték. É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. **Default: `Pending`.** Lásd 2.3 |
| `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 — lásd 2.4. A "múlt héthez képest" szakasz és az archívum-adatlap ebből olvas, PDF-visszafejtés nélkül. Üres, amíg `generationStatus ≠ Completed` |
| `deliveryStatus` | `enum ReportDeliveryStatus` | K | Az e-mail-kézbesítés összesített állapota (SD-56). **Default: `Pending`.** Lásd 2.3 |
| `deliveredAt` | `DateTime` (UTC) | O | Az utolsó kézbesítési kísérlet időpontja (a kezdeti küldésé vagy egy `resend`-é). Ü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ó job a meglévő rekordot frissíti, nem újat hoz létre —
> a unique constraint a végső védvonal a duplikátum ellen.

> **Miért nincs `ContentStatus`-életciklus.** A `WeeklyReport` nem tartalmi
> entitás — 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. Ezért a `WeeklyReport` **nem** a
> `News`/`Event`/`CityInfo` mintát követi (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` itt a rendszer-job; a `resend` akciónál az `updatedBy` a
> vezetőt rögzíti, aki az újraküldést indította.

### 2.3 A két új enum és a státusz-átmenetek

A `WeeklyReport`-nak két, egymástól független állapot-dimenziója van: a
**generálás** és a **kézbesítés**. Ezek nem alkotnak közös állapotgépet —
két párhuzamos, kis életciklus. Nem kerülnek a `02_globalis_allapotgep.md`-
be (az kizárólag a `Ticket` állapotgépe); itt élnek.

**`ReportGenerationStatus`:**

| Érték | Jelentés | Átmenet |
|---|---|---|
| `Pending` | A `WeeklyReport`-sor létrejött, a generálás még nem indult | kezdő |
| `Generating` | A háttér-job dolgozik (aggregáció + PDF-rajzolás + S3-feltöltés) | `Pending → Generating` |
| `Completed` | A PDF elkészült, `pdfFileRef` és `snapshotData` kitöltve, `generatedAt` beáll | `Generating → Completed` |
| `Failed` | A generálás hibázott (EC-6 — pl. S3 elérhetetlen) | `Generating → Failed` |

A `Failed`-ből a job **újrapróbálható**: `Failed → Generating` megengedett
(retry). A `Completed → Generating` átmenetet a pilot nem használja (a
múltbeli pillanatkép stabil, SD-49).

**`ReportDeliveryStatus`** (SD-56):

| Érték | Jelentés |
|---|---|
| `Pending` | A generálás még nem `Completed`, vagy kész, de a küldés még nem futott |
| `AllSent` | A riport minden címzettnek kiment |
| `PartialFailure` | Legalább egy címzettnek kiment, legalább egynek nem (EC-7) |
| `AllFailed` | Egyetlen címzettnek sem ment ki |

A kézbesítés csak `generationStatus = Completed` után indulhat. A `resend`
akció (SD-56) a `deliveryStatus`-t bármely nem-`Pending` értékből
újraértékeli.

> **A két dimenzió függetlensége.** Egy `WeeklyReport` lehet `generationStatus
> = Completed` **és** `deliveryStatus = AllFailed` — a PDF kész és
> archiválva (Béla a `/riportok`-ból letöltheti), de az e-mail nem ment ki.
> Ez a `02_globalis_allapotgep.md` 6. "best effort" elvének alkalmazása: a
> kézbesítés hibája nem érvényteleníti a generálást.

### 2.4 A `snapshotData` szerkezete

A `snapshotData` a heti PDF négy szakaszának (`40` 4.2) számszerű tartalmát
tárolja strukturáltan. Ez teszi lehetővé, hogy a következő hét riportja a
"múlt héthez képest" szakaszt egy DB-olvasással vegye, és hogy az archívum-
adatlap PDF-letöltés nélkül mutasson számokat. A JSON-séma (a kódbeli DTO-
nak megfelelően, camelCase):

```json
{
  "weekNumbers": {
    "resolvedCount": 17,
    "averageResolutionDays": 4.2,
    "newCount": 23,
    "openAtWeekEnd": 34,
    "highPriorityOpenAtWeekEnd": 5
  },
  "categoryBreakdown": [
    { "rootCategoryId": 1, "rootCategoryName": "Utak és járdák", "resolvedCount": 8 },
    { "rootCategoryId": 2, "rootCategoryName": "Hulladék", "resolvedCount": 4 }
  ]
}
```

A `categoryBreakdown` a `Category`-fa **gyökér-szintjén** összesít (a
duplikáció-heurisztika gyökér-feloldásával konzisztensen — `00_domain_model.md`
2.1); a `rootCategoryName` a generálás pillanatában érvényes kategórianevet
rögzíti (denormalizált — ha egy kategóriát később átneveznek, a régi riport
a régi nevet őrzi).

> **A "múlt héthez képest" számítása.** A PDF generálásakor a job a
> `(year, isoWeek)` alapján megkeresi az **előző hét** `WeeklyReport`-ját,
> és annak `snapshotData.weekNumbers`-éből veszi a viszonyítást. Ha nincs
> előző heti rekord (a pilot első hete, EC-2), a "múlt héthez képest"
> szakasz a semleges sort kapja (SD-55/b). A viszonyítás tehát nem külön
> tárolt mező — a generálás-időben számolódik a két `snapshotData`-ból.

### 2.5 A Dashboard-aggregáció forrás-leképezése

A Dashboard adata teljes egészében a `Ticket`-ből és kísérőiből számolt. A
pontos predikátumokat (állapot-halmazok, időhatárok) a 3. szakasz
formalizálja.

| Dashboard-elem | Forrás-entitás | Forrás-mezők |
|---|---|---|
| "Nyitott ügyek" KPI | `Ticket` | `status ∈ {New, Assigned, InProgress}` (SD-51) |
| "Aznapi új" KPI | `Ticket` | `createdAt` ∈ a mai nap (tenant-időzóna) |
| "Késésben" KPI | `Ticket` | `status ∈ {New, Assigned, InProgress}` ÉS `dueDate < a mai nap kezdete` (SD-30 #1) |
| "Aznap lezárt" KPI | `Ticket` | `resolvedAt` ∈ a mai nap (tenant-időzóna) |
| Csapat-tábla — sorok | `TenantUser` | `roles ∋ field_worker` ÉS `status = Active` (SD-53) |
| Csapat-tábla — "Lezárt (hét)" | `Ticket` | `resolvedAt` ∈ az aktuális hét ÉS `assignedUserId` = a sor munkatársa (SD-54) |
| Csapat-tábla — "Folyamatban" | `Ticket` | `status = InProgress` ÉS `assignedUserId` = a sor munkatársa |
| Csapat-tábla — "Átl. lezárási idő" | `Ticket` | a "Lezárt (hét)" halmaz ügyeire `resolvedAt − createdAt` átlaga |

### 2.6 A domain-modell hatása a `00_domain_model.md`-re

A `00_domain_model.md` a következőkkel bővül (a v1.6 átvezetése):

- **5.2 entitás-index:** egy új sor — `WeeklyReport`, Tenant DB, ✓
  `AuditableEntity`, 2. blokk (a `Group` után), Szerep: "A heti PDF-riport
  perzisztált pillanatképe". A "Tizenöt entitás" számláló **tizenhatra** vált.
- **5.3 enum-lista:** két új sor — `ReportGenerationStatus`
  (`Pending`, `Generating`, `Completed`, `Failed`) és `ReportDeliveryStatus`
  (`Pending`, `AllSent`, `PartialFailure`, `AllFailed`). A `Pending` érték
  több enumban is szerepel — a típusrendszerben nem ütköznek, de a
  névütközés-figyelmeztetés kiegészül.
- **5.4 DB-modell összefoglaló:** a Tenant DB entitás-listája tizeneggyel
  bővül (`WeeklyReport`).

---

## 3. Szerver — API és logika

### 3.1 A `40_riport` route-készlet — az MH-J1 lezárása

| # | Route | Method | Szerep | `roles` |
|---|---|---|---|---|
| R-1 | `/v1/dashboard` | `GET` | A Dashboard-aggregátum (KPI-k + csapat-tábla) | `["tenant_manager"]` |
| R-2 | `/v1/weekly-reports` | `GET` | A riport-archívum lista (`/riportok`) | `["tenant_manager"]` |
| R-3 | `/v1/weekly-reports/{id}` | `GET` | Egy riport metaadata | `["tenant_manager"]` |
| R-4 | `/v1/weekly-reports/{id}/pdf` | `GET` | Egy riport PDF-binárisának letöltése | `["tenant_manager"]` |
| R-5 | `/v1/weekly-reports/latest/pdf` | `GET` | A legutóbbi `Completed` riport PDF-je | `["tenant_manager"]` |
| R-6 | `/v1/weekly-reports/{id}/resend` | `POST` | A riport e-mailes újraküldése (SD-56) | `["tenant_manager"]` |

> **Az MH-J1 ezzel lezárva.** A `05_jogosultsag_es_authorization.md` 8.3
> MH-1 / a `99_donesnaplo.md` MH-J1 a `40_riport` modul pontos route-neveit
> a feature-spec hatáskörébe utalta. A fenti hat route ezt kitölti. Mind a
> hat `roles`-listája pontosan `["tenant_manager"]` — a **Szabály-R1**
> érvényesítve. A `dispatcher`, `content_manager`, `field_worker` egyikhez
> sem fér. Az **AC-D1** (a `05` referencia acceptance criterionja) ezzel
> ténylegesen tesztelhető — a 6. szakasz **AC-F1**-ként hozza. A route-ok
> kanonikus definíciója innentől ez a feature-spec; a `05` referencia
> traceability-mátrixa erre hivatkozik (a `CLAUDE.md` 6. elve: a referencia
> összegez, a feature-spec definiál).

A `Tenant`-header-resolution, a JWT-kiértékelés és a cross-tenant védelem a
platform-minta (`01_kozos_mintak.md` 1.–3.) — minden route alatt
transzparensen érvényes.

### 3.2 R-1 — `GET /v1/dashboard`

**Eltérés a mintától (SD-50):** nem `BaseController`-CRUD — read-only,
számolt aggregátum, nincs mögötte entitás. Nincs paginálás, nincs
`ListRequest`, nincs szűrő-paraméter — a Dashboard nézőpontja fix
(`40` 3.4, K-016).

**Request:** nincs body, nincs query-paraméter. A "ma" és a "hét" a szerver-
oldali tenant-időzónás számításból jön (SD-10).

**Response — `200`, `DashboardDto`:**

```json
{
  "kpi": {
    "openCount": 34,
    "newToday": 6,
    "overdueCount": 3,
    "resolvedToday": 8
  },
  "team": [
    {
      "userId": 12,
      "displayName": "Kovács Zoltán",
      "resolvedThisWeek": 12,
      "inProgressCount": 3,
      "averageResolutionDays": 1.8,
      "isTopPerformer": true
    }
  ],
  "latestReport": {
    "weeklyReportId": 48,
    "year": 2026,
    "isoWeek": 28,
    "available": true
  }
}
```

A `latestReport` blokk a "Heti riport letöltése" gomb állapotát adja: ha van
`Completed` riport, `available: true`; ha még nincs egy sem, `available:
false` és a gomb tiltott (lásd 4.2.1 üres-állapot).

**Hibakódok:** `401`, `403` (a `01_kozos_mintak.md` 6.3 / SD-12 szerint).
Üzleti hibakód nincs — az aggregáció üres halmazon is `200`-at ad (EC-1,
EC-9).

#### 3.2.1 A KPI-k aggregációs definíciója

Minden definíció a tenant-időzónás (`Europe/Budapest`) nap/hét-határra épül,
amit a szerver számít és UTC-re konvertál a lekérdezéshez (SD-10).
Jelölés: `ma_kezdete` = a tenant-időzónás mai nap 00:00 UTC-ben;
`ma_vege` = a következő nap 00:00; `het_kezdete` = az aktuális tenant-
időzónás hétfő 00:00; `het_vege` = a következő hétfő 00:00. Minden
intervallum félig nyílt: `[kezdet, vég)`.

| KPI (`kpi.*`) | Definíció (`Ticket`-predikátum) | Forrás-döntés |
|---|---|---|
| `openCount` | `status ∈ {New, Assigned, InProgress}` | SD-51 |
| `newToday` | `createdAt ∈ [ma_kezdete, ma_vege)` — **a státusztól függetlenül**, így a manuálisan egyenest `Assigned`-be nyitott ügy is beleszámít (EC-12) | `40` 3.2; EC-12 |
| `overdueCount` | `status ∈ {New, Assigned, InProgress}` ÉS `dueDate ≠ null` ÉS `dueDate < ma_kezdete` | SD-30 #1; EC-11 |
| `resolvedToday` | `resolvedAt ∈ [ma_kezdete, ma_vege)` — a `resolvedAt` a denormalizált lezárás-időpont (SD-14) | `40` 3.2 |

> **`overdueCount` és a `/bejelentesek` "Késésben" tab azonossága (EC-11).** Az
> `overdueCount` predikátuma **szó szerint** az SD-30 #1 lista-tab definíciója:
> `dueDate` nélküli ügy soha nem késésben, a határidő napján még nincs
> késésben (`dueDate < ma_kezdete`, nem `≤`), és csak nyitott ügy számít. Ez
> garantálja, hogy a Dashboard "Késésben" kártyája és a `/bejelentesek`
> "Késésben" tabja **ugyanazt a számot** adja — a `40` 3.5 bizalom-
> követelménye. A 6. szakasz AC-A7-ként rögzíti.

#### 3.2.2 A csapat-tábla aggregációs definíciója

**A sorok** (`team[]`): a `TenantUser`-ek, ahol `roles ∋ field_worker` ÉS
`status = Active` (SD-53). Az `Invited` és `Disabled` `TenantUser` nem sor.
Akinek nincs heti ügye, az is sor (a Wow #2 összehasonlítási alapja, `40`
3.3). Default rendezés: `resolvedThisWeek DESC`, másodlagosan `displayName
ASC`.

| Oszlop (`team[].*`) | Definíció | Forrás-döntés |
|---|---|---|
| `resolvedThisWeek` | `Ticket`-ek száma, ahol `resolvedAt ∈ [het_kezdete, het_vege)` ÉS `assignedUserId` = a sor `userId`-ja | SD-54 |
| `inProgressCount` | `Ticket`-ek száma, ahol `status = InProgress` ÉS `assignedUserId` = a sor `userId`-ja (időpillanat-jellegű — *most*) | `40` 3.2 |
| `averageResolutionDays` | a `resolvedThisWeek` halmaz ügyeire `AVG(resolvedAt − createdAt)`, napban, egy tizedesre kerekítve. Ha a halmaz üres: `null` (a kliens "—"-t jelenít, EC-1) | `40` 3.2, 3.5 |
| `isTopPerformer` | `true` annál az **egy** sornál, amelynek `resolvedThisWeek` értéke szigorúan nagyobb minden más soré-nál ÉS `> 0`. Holtverseny esetén vagy ha mindenki `0`: minden sor `false` (SD-55/a) | SD-55/a |

> **A csoportra kiosztott ügy nem számít személyhez (SD-54).** A
> `resolvedThisWeek` és az `inProgressCount` az `assignedUserId`-ra szűr. A
> `assignedGroupId`-ra kiosztott (és `assignedUserId = null`) ügy egyetlen
> sorhoz sem rendelődik. **Következmény, amit a fejlesztő ne "javítson el":**
> a `team[].resolvedThisWeek` értékek összege **eltérhet** a heti PDF
> `resolvedCount`-jától (a PDF minden lezárt ügyet számol, a kiosztás
> módjától függetlenül — 3.6.1). A két szám két különböző kérdésre felel; az
> eltérés helyes (SD-54, K-035).

> **`averageResolutionDays` és a visszanyitás (EC-3).** A `resolvedAt` a
> visszanyitáskor `null`-ra áll (`02_globalis_allapotgep.md` 3.5). Egy a
> héten visszanyitott ügy így automatikusan kiesik a `resolvedThisWeek`
> halmazból — nincs külön logika. Ha az ügyet a héten újra lezárják, az
> **új** `resolvedAt` szerint számít.

### 3.3 R-2, R-3 — a `WeeklyReport` archívum lekérdezése

**R-2 `GET /v1/weekly-reports`** — **standard CRUD lista a `BaseController`
szerint**, a `ListRequest` + offset-paginálás mintán (SD-11). A lista a
`WeeklyReport` metaadatait adja (`WeeklyReportListDto`): `id`, `year`,
`isoWeek`, `periodStart`, `periodEnd`, `generationStatus`, `deliveryStatus`,
`generatedAt`. **A `snapshotData` és a `pdfFileRef` nincs a lista-DTO-ban**
— a lista könnyű marad; a PDF a külön letöltő-végponton (R-4) jön. Default
rendezés: `year DESC, isoWeek DESC`.

**R-3 `GET /v1/weekly-reports/{id}`** — **standard CRUD get a
`BaseController` szerint**. A teljes `WeeklyReportDto`-t adja, beleértve a
`snapshotData`-t (így a riport-adatlap számokat mutathat PDF-letöltés
nélkül). Idegen tenant `id`-jára `404` (SD-12).

> **Eltérés a mintától — nincs `create`, `update`, `delete`, `bulk delete`,
> `reorder`.** A `BaseController` teljes CRUD-ot ad; a `WeeklyReport` ebből
> **csak a `list` és a `get` műveletet** teszi közzé. A riportot a háttér-
> job hozza létre és frissíti (3.6), nem HTTP-kliens — nincs `POST`/`PUT`
> végpont rá. A `DELETE` szintén nincs (a riport-archívum nem törölhető a
> pilotra — konzisztens az MH-2 "fail closed" elvével, `05` 8.3: a route
> hiánya `403`). Az egyetlen állapotváltó kliens-művelet a `resend` (R-6).

### 3.4 R-4, R-5 — a PDF-bináris letöltése

**Eltérés a mintától:** nem JSON-választ adnak, hanem a bináris PDF-et.

- **Response — `200`:** `Content-Type: application/pdf`,
  `Content-Disposition: attachment; filename="..."`. A fájlnév szerver-
  generált, beszélő: `heti-riport-2026-W28.pdf` (a tenant-időzónás év +
  ISO-hétszám). A bináris a `pdfFileRef` S3-objektumból streamel, vagy a
  szerver presigned URL-re irányít — fejlesztői döntés (döntésnaplóba), az
  `Attachment`-letöltés mintájával konzisztens (SD-15).
- **R-5** a legutóbbi `generationStatus = Completed` `WeeklyReport` PDF-jét
  adja (`year DESC, isoWeek DESC`, az első `Completed`).
- **Hibakódok:** `404`, ha a riport nem létezik / idegen tenant (R-4);
  `409 { "reason": "not_generated" }`, ha a riport létezik, de
  `generationStatus ≠ Completed`; `404 { "reason": "no_report_yet" }` az
  R-5-nél, ha egyetlen `Completed` riport sincs.

### 3.5 R-6 — `POST /v1/weekly-reports/{id}/resend`

A `02_globalis_allapotgep.md` 3.4 akció-végpont-stílusát követi: `POST`,
állapotváltó mellékhatással.

- **Request body:** nincs (a címzettek a `50_konfig` címzett-listából
  jönnek).
- **Mellékhatás:** a `WeeklyReport` PDF-je (`pdfFileRef`) újra kimegy az
  **aktuális** címzett-listának; a `deliveryStatus` újraértékelődik
  (`AllSent`/`PartialFailure`/`AllFailed`); a `deliveredAt` és a
  `recipientCount` frissül; az `updatedBy` a kérő vezetőt rögzíti. A PDF-
  tartalom **nem** generálódik újra (a múltbeli pillanatkép stabil, SD-49).
- **Response — `200`** + a frissített `WeeklyReportDto`.
- **Hibakódok:** `409 { "reason": "not_generated" }`, ha `generationStatus ≠
  Completed`; `422 { "reason": "no_recipients" }`, ha a címzett-lista üres;
  `404` idegen/nem létező `id`.

### 3.6 A PDF-generálás és e-mail-kézbesítés — szerver-oldali workflow

**A generáló job.** Egy ütemezett háttér-job minden hétfőn reggel (tenant-
időzónás 7:00 körül — a pontos időpont és az ütemező-mechanizmus fejlesztői
döntés) tenantonként lefut:

1. Létrehoz (vagy a `(year, isoWeek)` egyediség miatt megtalál) egy
   `WeeklyReport`-sort az **előző** naptári hétre; `generationStatus:
   Pending → Generating`.
2. Lefuttatja az aggregációt a `[periodStart, periodEnd)` intervallumra
   (3.6.1) → `snapshotData`.
3. Megrajzolja a PDF-et (tenant-logó a `50_konfig` `/beallitasok/altalanos`-
   ból; ha nincs logó, csak a tenant-név — EC-8); feltölti S3-ra →
   `pdfFileRef`.
4. `generationStatus: Generating → Completed`, `generatedAt` beáll.
5. Elindítja a kézbesítést (lásd lent).
6. Hiba esetén bármely lépésben → `generationStatus: Generating → Failed`;
   a job a következő futáskor (vagy manuális trigger) újrapróbálja.

**Idempotencia (SD-55/c).** A `(tenantId, year, isoWeek)` egyediség miatt a
job kétszeri futása nem hoz duplikátumot — a meglévő sort frissíti. Ha egy
`Completed` riportra fut rá újra, a job kihagyja.

**A kézbesítés.** A `Completed` után a job a `WeeklyReport` PDF-jét e-mail-
mellékletként elküldi a `50_konfig` címzett-listának (`40` 4.5).
Címzettenként követi a sikert, és összesít: `AllSent` / `PartialFailure` /
`AllFailed` (SD-56). A kézbesítés **best effort**
(`02_globalis_allapotgep.md` 6.): ha az e-mail nem megy ki, a `WeeklyReport`
`Completed` marad és archiválódik — Béla a `/riportok`-ból eléri, és a
`resend`-del (R-6) újrapróbálhatja.

#### 3.6.1 A `snapshotData.weekNumbers` aggregációs definíciója

A heti PDF "A hét számai" blokkja (`40` 4.2), a `[periodStart, periodEnd)`
intervallumra:

| Mező | Definíció (`Ticket`-predikátum) |
|---|---|
| `resolvedCount` | `Ticket`-ek száma, ahol `resolvedAt ∈ [periodStart, periodEnd)` — **minden** lezárt ügy, a kiosztás módjától függetlenül (SD-54: a PDF összesít) |
| `averageResolutionDays` | a `resolvedCount` halmazra `AVG(resolvedAt − createdAt)`, napban, egy tizedesre. Üres halmazon `null` → a PDF "—"-t ír (EC-1) |
| `newCount` | `Ticket`-ek száma, ahol `createdAt ∈ [periodStart, periodEnd)` |
| `openAtWeekEnd` | `Ticket`-ek száma, ahol `status ∈ {New, Assigned, InProgress}` **a hét végének pillanatában** — lásd MH-R2 |
| `highPriorityOpenAtWeekEnd` | mint `openAtWeekEnd`, plusz `priority = High` |

> **Az `openAtWeekEnd` "pillanatban" számítása (MH-R2).** A PDF hétfő reggel
> generálódik, de a "hét végén nyitott ügyek" a *vasárnap 24:00-i*
> állapotot kell tükrözze, nem a generálás pillanatát (`40` 4.2). A
> `Ticket.status` csak az *aktuális* állapotot tárolja — a múltbeli állapot
> az `ActivityLog`-ból rekonstruálható. **Pilot-közelítés (technikai
> tervezési döntés, döntésnaplóba):** mivel a job a hét vége (vasárnap
> 24:00) után **néhány órával** fut (hétfő reggel), és pilot-volumenen
> (`~100 bejelentés/hó`, SD-11) az ablakban történő állapotváltás
> elhanyagolható, a pilotra az `openAtWeekEnd` a **generálás pillanatának**
> `status`-át használja. Ez kimondott közelítés (MH-R2, 8.5); ha a pilot-
> tapasztalat pontos hét-végi pillanatképet kíván, az `ActivityLog`-alapú
> rekonstrukció iterációs elem.

#### 3.6.2 A `snapshotData.categoryBreakdown` aggregációs definíciója

A `resolvedCount` halmaz ügyeit a `Category`-fa **gyökér-szintjén**
csoportosítja: minden lezárt ügy `categoryId`-ját feloldja a gyökerére (ha
alkategória, a `parentId`-n felmegy; ha már gyökér, önmaga — `00_domain_model.md`
2.1), és gyökerenként számolja a lezárt ügyeket. A `rootCategoryName` a
generálás pillanatában érvényes név (denormalizált — 2.4). A `categoryId`
nélküli lezárt ügy (ha van ilyen edge) egy "Egyéb" gyűjtőbe kerül.

### 3.7 Validáció (FluentValidation)

A `WeeklyReport`-ot a háttér-job hozza létre, nem HTTP-kliens — nincs
`create`/`update` végpont. A validáció **invariáns-ellenőrzés** a job
mentési útján és a generálás-pipeline lépésein.

| Mező / invariáns | Szabály |
|---|---|
| `year` | Kötelező; pozitív egész. ISO-8601 év |
| `isoWeek` | Kötelező; `1 ≤ isoWeek ≤ 53` |
| `(year, isoWeek)` | **Egyedi** a tenant DB-n belül — DB unique constraint (SD-55/c idempotencia) |
| `periodStart`, `periodEnd` | Kötelező; `periodStart < periodEnd`; a tenant-időzónás hétfő→hétfő intervallum (a `Europe/Budapest` nyári/téli váltás hetében az IANA-zóna kezeli a 23/25 órás napot, fix offset tilos) |
| `generationStatus` | Kötelező; érvényes enum. Az átmenetek a 2.3 szerint; tiltott átmenet a job-mentés útján hiba |
| `pdfFileRef` | Max 512 karakter. **Feltételes:** kötelezően kitöltött, ha `generationStatus = Completed`; egyébként üres |
| `snapshotData` | **Feltételes:** kötelezően kitöltött (érvényes JSON, 2.4 sémája), ha `generationStatus = Completed`; egyébként üres |
| `generatedAt` | **Feltételes:** kötelezően kitöltött, ha `generationStatus = Completed` |
| `deliveryStatus` | Kötelező; érvényes enum. **Feltételes invariáns:** `deliveryStatus ≠ Pending` csak akkor lehet, ha `generationStatus = Completed` |
| `deliveredAt`, `recipientCount` | **Feltételes:** kitöltött, ha `deliveryStatus ≠ Pending`; `recipientCount ≥ 0` |

A `resend` végpont input-validációja: nincs request body; az ellenőrzés a
`generationStatus = Completed` (`409 not_generated`) és a nem-üres
címzett-lista (`422 no_recipients`) — 3.5.

### 3.8 Workflow- és business-szabályok

- A generálás idempotens: `(tenantId, year, isoWeek)` egyediség, a job a
  meglévő rekordot frissíti, nem újat hoz létre (SD-55/c).
- A kézbesítés best-effort: hibája nem érvényteleníti a generálást
  (`02_globalis_allapotgep.md` 6.).
- A `resend` az **aktuális** címzett-listának küld; a PDF-tartalom NEM
  generálódik újra (a múltbeli pillanatkép stabil, SD-49).
- A `WeeklyReport` **nem törölhető** kliensből (3.3 — nincs `DELETE` route;
  fail closed default).

### 3.9 Jogosultság — `authorization.json`

Mind a hat route (3.1) `roles`-listája pontosan `["tenant_manager"]` —
Szabály-R1. A kódbeli szerepkör-név (SD-7): `tenant_manager`; a teljes
Zitadel-szerepkör a `tenant_manager_<tenant_code>` mintát követi
(`01_kozos_mintak.md` 3.3).

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

A `WeeklyReport` a Tenant DB-ben él — a `Tenant`-header-resolution
(`01_kozos_mintak.md` 1.3, SD-6) a `BaseController`-mintában transzparens; a
Dashboard-aggregáció a tenant-aware `DbContext`-en fut. A generáló háttér-
job nem HTTP-kérésből indul, ezért nincs `Tenant` header — a job
**tenantonként ciklusban** fut, és minden iterációban explicit a tenant
DB-re kapcsol (a `01_kozos_mintak.md` 1.5 Core→Tenant mintával analóg; a
konkrét mechanizmus fejlesztői döntés). Cross-tenant adat nem szivároghat:
a Dashboard csak a `Tenant` header tenantjának `Ticket`-jeit aggregálja,
idegen tenant `WeeklyReport`-`id`-jára `404` (SD-12).

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

A `WeeklyReport` `AuditableEntity` — a `createdBy`/`updatedBy` automatikus
(`01_kozos_mintak.md` 7.1). A generáláskor a `createdBy` a rendszer-job; a
`resend`-nél az `updatedBy` a kérő vezetőt rögzíti — így visszakereshető, ki
indított manuális újraküldést. A Dashboard read-only, nincs külön audit-
igénye.

---

## 4. Admin felület

### 4.1 Érintett képernyők

| Nézet | URL | Komponens-jelleg | Forrás |
|---|---|---|---|
| Dashboard | `/fooldal` | Egyedi aggregációs nézet (nem `TableStateConfig`, nem űrlap) | `90_sitemap_v3.md` 2.1; `00_architektura_v4.md` 6.1 |
| Riport-archívum | `/riportok` | `TableStateConfig` lista-oldal | `90_sitemap_v3.md` 2.5 |
| Riport-adatlap | `/riportok/details/<id>` | Adatlap-nézet (a meglévő adatlap-minta) | `90_sitemap_v3.md` 2.5 |

A `/fooldal` a vezető **landingje** (K-027) — belépés után a redirect ide
visz. A `NavStore` (`project_backend_client_angular` CLAUDE.md) a page
title-t és a breadcrumb-ot adja: a Dashboardon "Főoldal", a `/riportok`-on
"Riportok", az adatlapon "Riportok / <hét megnevezése>".

### 4.2 Dashboard — `/fooldal`

A Dashboard egyetlen `GET /v1/dashboard` (R-1) hívásból renderel. A
képernyő három blokkból áll, felülről lefelé.

**Fejléc-sáv.** A tenant neve ("Főoldal — <tenant neve>", a tenant-név a
`50_konfig` `/beallitasok/altalanos`-ból) és jobbra a **"Heti riport
letöltése"** gomb. A gomb az R-5-öt hívja; állapotát a
`DashboardDto.latestReport.available` adja — ha `false`, a gomb tiltott, és
tooltip jelzi: "Az első heti riport hétfőn készül el." (i18n).

**"Ez a hét" blokk — négy KPI-kártya.** A `kpi` objektum négy mezeje,
kártyánként egy szám + felirat:

| Kártya | Mező | Megjelenítés | Lefúrás (kattintásra) |
|---|---|---|---|
| Nyitott ügyek | `kpi.openCount` | szám | `/bejelentesek?tab=mind&status=New,Assigned,InProgress` (SD-51) |
| Aznapi új | `kpi.newToday` | szám | `/bejelentesek?createdFrom=<ma>&createdTo=<ma>` (SD-52) |
| Késésben | `kpi.overdueCount` | szám; **figyelemjelölés**, ha `> 0` | `/bejelentesek?tab=kesesben` |
| Aznap lezárt | `kpi.resolvedToday` | szám | `/bejelentesek?tab=lezart&resolvedFrom=<ma>&resolvedTo=<ma>` — lásd a megjegyzést |

> **A lefúrás query-param-szerződése (SD-51, SD-52).** A négy kártya a
> meglévő `/bejelentesek` listára navigál, a `TableStateConfig` URL-query-
> param mintáján belül. A "Nyitott ügyek" a "Mind" tabot nyitja a státusz-
> fejléc-szűrőt a három nyitott állapotra állítva. Az "Aznapi új" a
> Beérkezett oszlop dátum-tartomány-szűrőjét állítja a mai napra — ez a `10`
> listán **már létező** szűrő (`10` 4.2). Az "Aznap lezárt" az egyetlen,
> amelyhez a `10` lista bővítése kell: egy `resolvedAt` ("lezárás dátuma")
> szűrő — ez a **VF-R1 visszacsorgó jelzés** a `10` feature-spec felé. Amíg
> a VF-R1 nincs átvezetve, az "Aznap lezárt" átmenetileg a
> `/bejelentesek?tab=lezart`-ra fúr le dátum-szűrő nélkül.

**Terepi csapat-tábla.** A `team[]` tömb soronként; oszlopok: Munkatárs
(`displayName`), Lezárt (`resolvedThisWeek`), Folyamatban
(`inProgressCount`), Átl. lezárási idő (`averageResolutionDays`, "X,X nap"
formátum, vagy "—" ha `null`). Az `isTopPerformer: true` sor **vizuálisan
kiemelt**. A Munkatárs név-cella **link**: `/bejelentesek?assignee=<userId>`
— a Wow #2 lefúrás (`40` 3.3). A szerver adja a rendezést
(`resolvedThisWeek DESC`); a tábla a kapott sorrendet jeleníti, kliens-
oldali átrendezés a pilotra nincs.

> **A csapat-lefúrás query-paramja (MH-R3).** A `?assignee=<userId>` a
> `/bejelentesek` Felelős oszlop lookup-szűrőjét állítja a munkatársra. A
> pilotra a lefúrás a munkatárs **összes** ügyét mutatja — nincs kombinált
> "heti+lezárt+felelős" előszűrő a `10` listán. A Wow #2 a felelős-szűrt
> teljes listával is teljesül (`40` 3.3: "olcsó fejlesztés, nem új
> képernyő"); a heti+lezárt szűkítés iterációs finomítás (MH-R3, 8.5).

#### 4.2.1 Üres / betöltési / hibaállapot — Dashboard

| Állapot | Viselkedés |
|---|---|
| Betöltés | A `GET /v1/dashboard` alatt skeleton/spinner a három blokkon |
| Üres — nincs `field_worker` | A csapat-tábla üres-állapota: "Még nincs terepi munkatárs felvéve." (i18n); a KPI-blokk normálisan renderel (EC-9) |
| Üres — minden KPI `0` | A kártyák `0`-t mutatnak — érvényes állapot (EC-1) |
| Üres — nincs még riport | A "Heti riport letöltése" gomb tiltott + segédszöveg |
| Hiba — `GET /v1/dashboard` `403`/`5xx` | A meglévő admin-minta hibaállapota; `403` esetén a route-guard eleve nem engedte volna ide a nem-vezetőt |

### 4.3 Riport-archívum — `/riportok`

**Standard `TableStateConfig` lista-oldal** — a pagináció, az URL-query-
param-kódolás, az oszlop-láthatóság készen jön (`01_kozos_mintak.md` 6.2 / a
`project_backend_client_angular` minta).

**Oszlopok** (a `WeeklyReportListDto`-ból, R-2):

| Oszlop | Mező | Megjelenítés | Szűrhető | Rendezhető | Alap-látható |
|---|---|---|---|---|---|
| Hét | `year` + `isoWeek` | "2026. 28. hét" | — | ✓ | ✓ |
| Időszak | `periodStart` + `periodEnd` | "júl. 6. – júl. 12." (tenant-időzóna, magyar dátum) | ✓ (dátum-tartomány) | ✓ | ✓ |
| Generálás | `generationStatus` | badge | ✓ (enum) | — | ✓ |
| Kézbesítés | `deliveryStatus` | badge | ✓ (enum) | — | ✓ |
| Generálva | `generatedAt` | dátum+idő, tenant-időzóna | ✓ (dátum-tartomány) | ✓ | rejthető |

**Default rendezés:** `year DESC, isoWeek DESC`.

**Sor-akciók:** sorra kattintás → riport-adatlap; kebab-menü: "PDF
letöltése" (R-4 — csak `Completed` soron aktív), "Megnyitás".

**Eltérés a mintától.** A `WeeklyReport` lista **csak olvasható** — nincs
"Új riport" gomb (a riportot a háttér-job hozza, 3.6), nincs sor-szintű
szerkesztés, nincs bulk-művelet, nincs `DELETE` (3.3). Az egyetlen írás-
jellegű művelet a riport-adatlapról elérhető `resend`.

#### 4.3.1 Üres / betöltési / hibaállapot — `/riportok`

| Állapot | Viselkedés |
|---|---|
| Betöltés | `TableStateConfig` standard skeleton |
| Üres — még nincs riport | "Még nem készült heti riport. Az első riport hétfőn generálódik." (i18n) |
| Üres — szűrés nem ad találatot | "Nincs a szűrésnek megfelelő riport." (i18n) |
| Hiba | A meglévő admin-minta hibaállapota |

### 4.4 Riport-adatlap — `/riportok/details/<id>`

A teljes `WeeklyReportDto`-t mutatja (R-3), olvasásra rendezve, szekciókkal:

- **Alapadatok** — Hét ("2026. 28. hét"), Időszak, Generálás állapota,
  Kézbesítés állapota, Generálva időpont.
- **A hét számai** — a `snapshotData.weekNumbers` öt értéke, olvasható
  formában (Lezárt ügyek, Átlagos lezárási idő, Új bejelentések, Hét végén
  nyitott, ebből magas prioritású). Így a vezető PDF-letöltés nélkül látja
  a számokat.
- **Kategória szerinti bontás** — a `snapshotData.categoryBreakdown` sorai
  (gyökér-kategória + lezárt szám).
- **Címzettek** (SD-57) — a `50_konfig` aktuális címzett-listája olvasásra.
  Bevezető: "A riport a Beállítások / Általános oldalon megadott
  címzetteknek megy. Jelenlegi címzettek:" — utána a lista (név vagy e-mail-
  cím, ahogy a `50_konfig` tárolja). A szerkesztés a Beállításokra mutató
  link.
- **Audit** — Létrehozva / Módosítva (a standard `AuditableEntity`-minta,
  tenant-időzóna).

**Akció-gombok az adatlapon:**

- **"PDF letöltése"** — az R-4-et hívja; csak `Completed` esetén aktív,
  egyébként tiltott + segédszöveg ("A riport még nem készült el." vagy "A
  generálás sikertelen volt.").
- **"Riport újraküldése"** — az R-6-ot (`resend`) hívja; csak `Completed`
  esetén aktív. Megerősítő párbeszéd a **konkrét címzettek** felsorolásával
  (SD-57): "Újraküldöd a riportot a következő címzetteknek: <címzett-lista>?"
  Sikeres hívás után a `deliveryStatus` badge frissül.

#### 4.4.1 Üres / betöltési / hibaállapot — adatlap

| Állapot | Viselkedés |
|---|---|
| Betöltés | A meglévő adatlap-minta betöltés-jelzése |
| `generationStatus = Failed` | A "A hét számai" és "Kategória-bontás" helyén: "A riport generálása sikertelen volt. A rendszer a következő futáskor újrapróbálja." (i18n) — a `snapshotData` ekkor üres |
| `generationStatus = Generating`/`Pending` | "A riport generálása folyamatban." (i18n) |
| Hiba — `404` (idegen tenant / nem létező) | A meglévő admin-minta 404-állapota |

### 4.5 Jogosultság

A három nézet **vezető-only** — `05_jogosultsagok_v2.md` 2.7, Szabály-R1. A
kliens-oldali Angular route-guard a `/fooldal`, `/riportok`,
`/riportok/details/*` útvonalakat a `tenant_manager` szerepkörhöz köti; a
guard megkerülésekor a szerver a hat route-on `403`-mal zár (kétrétegű
érvényesítés, `01_kozos_mintak.md` 3.5, `05` 4.3). A `dispatcher`,
`content_manager`, `field_worker` egyik nézethez sem fér — a navigációban a
Főoldal és a Riportok menüpont nem-vezetőnek nem jelenik meg
(`90_sitemap_v3.md` 3.).

### 4.6 i18n kulcsok

Minden UI-szöveg kulcs-alapú, a `hu.json`-ba kerül (`01_kozos_mintak.md`
5.3). A felület tegez (5.4). A `TableStateConfig.translationPrefix` az
archívum-lista oszlopfejléceit auto-generálja.

**Kulcs-csoportok:**

- `dashboard.kpi.*` — a négy KPI-kártya felirata (`open`, `newToday`,
  `overdue`, `resolvedToday`).
- `dashboard.team.*` — a csapat-tábla oszlopfejlécei (`member`, `resolved`,
  `inProgress`, `avgResolution`), az üres-állapot (`empty`), a top-
  performer aria-szövege.
- `dashboard.downloadReport.*` — a "Heti riport letöltése" gomb felirata és
  a tiltott-állapot segédszövege (`disabledHint`).
- `report.field.*` — a riport-adatlap mezőnevei (`week`, `period`,
  `generationStatus`, `deliveryStatus`, `resolvedCount`,
  `averageResolutionDays`, `newCount`, `openAtWeekEnd`,
  `highPriorityOpenAtWeekEnd`).
- `report.generationStatus.*` — a négy `ReportGenerationStatus` érték
  felületi neve (Függő / Folyamatban / Kész / Sikertelen).
- `report.deliveryStatus.*` — a négy `ReportDeliveryStatus` érték felületi
  neve (Függő / Kiküldve / Részben sikertelen / Sikertelen).
- `report.action.*` — a gombfeliratok (`downloadPdf`, `resend`) és a
  `resend` megerősítő szövege (`resendConfirm`, paraméter: `{recipients}`).
- `report.recipients.*` (SD-57) — a Címzettek szekció címe (`section`) és
  bevezetője (`intro`). A lista maga adat, nem kulcs.
- `report.empty.*` — az archívum-lista és az adatlap üres/folyamatban/
  sikertelen állapotai.

**Hiba- és konfliktus-kulcsok** (`01_kozos_mintak.md` 5.5 — a szerver
kódot/`reason`-t ad, a magyar szöveget a kliens fogalmazza):

- `report.conflict.notGenerated` — a `409 { "reason": "not_generated" }`-hoz
  (R-4, R-6): "A riport még nem készült el, nem tölthető le / küldhető újra."
- `report.error.noReportYet` — a `404 { "reason": "no_report_yet" }`-hez
  (R-5): "Még nincs letölthető heti riport."
- `report.conflict.noRecipients` — a `422 { "reason": "no_recipients" }`-hez
  (R-6): "Nincs beállított címzett — add meg a riport címzettjeit a
  Beállításokban."
- `report.error.notFound` — a `404`-hez (idegen tenant / nem létező riport).

A Dashboard naplót nem jelenít meg (nincs esemény-sablon-kulcs).

---

## 5. Polgári mobilapp adatigénye

**Nem releváns, mert** a Dashboard és a heti PDF-riport teljes egészében
manager-oldali feature. A `40_riport_v1.md` 2. a két komponenst
egyértelműen a vezetőhöz (Béla) és a polgármesterhez (Péter) köti — a polgár
egyiknek sem felhasználója:

- A **Dashboard** a vezető belső munkaeszköze a `/fooldal`-on; a polgári
  mobilappnak nincs Dashboardja.
- A **heti PDF** a polgármesternek megy e-mailben; a polgármester nem lép
  be sem az admin felületre, sem a polgári appba (`00_architektura_v4.md`
  5.3). A polgár a riportot nem látja.

A feature **nem hív polgári API-t, nem küld adatot a polgári appnak, és nem
olvas a polgári app képernyőiről**. Az aggregáció forrása kizárólag a
Tenant DB `Ticket`-adata (3.2, 3.6) — ez a polgári appból érkező és a
manuálisan nyitott bejelentésekből egyaránt feltöltődik, de a riport-
feature ezt **kész adatként** aggregálja.

**Egyetlen polgári vonatkozás — elhatárolás (NY-R2).** A `40` 7.3 jelezte:
Péter pályája "polgári elégedettséget" várna a riportban, de a pilot-MVP-
ben nincs mérési mód (`10` NY-bej-5). A riport a pilotra **elégedettségi
mutató nélkül** készül. Ha valaha kell, a polgári appnak mérnie kell — ez a
polgári adatigény-spec dolga. Béla felé jelezni: Péternek ne ígérjen olyat,
ami a pilotra nincs (`40` 7.3).

---

## 6. Acceptance criteria

Given/When/Then, gépiesen ellenőrizhetően — egy AC = egy tesztelhető
állítás. Hat blokk.

### AC-A — Dashboard KPI-k

- **AC-A1** — *Given* három `New`, két `Assigned`, egy `InProgress`, egy
  `Resolved` és egy `Rejected` `Ticket`; *When* `GET /v1/dashboard`;
  *Then* `kpi.openCount == 6` (a három nyitott állapot, a két végállapot
  nem — SD-51).
- **AC-A2** — *Given* egy ma (tenant-időzóna) létrehozott `Ticket` és egy
  tegnap létrehozott; *When* `GET /v1/dashboard`; *Then* `kpi.newToday == 1`.
- **AC-A3** — *Given* egy ma, manuális nyitással egyenest `Assigned`-be
  létrehozott `Ticket`; *When* `GET /v1/dashboard`; *Then* a `kpi.newToday`
  tartalmazza — a `newToday` a `createdAt`-ra szűr, nem a `New` státuszra
  (EC-12).
- **AC-A4** — *Given* egy `Assigned` `Ticket` lejárt `dueDate`-tel, egy
  másik `dueDate` nélkül, egy harmadik `dueDate`-je pontosan a mai nap;
  *When* `GET /v1/dashboard`; *Then* `kpi.overdueCount == 1` (csak a
  lejárt; a `dueDate` nélküli és a mai nem — SD-30 #1).
- **AC-A5** — *Given* egy `Resolved` `Ticket` lejárt `dueDate`-tel; *When*
  `GET /v1/dashboard`; *Then* a `kpi.overdueCount` **nem** tartalmazza —
  csak nyitott állapot lehet késésben.
- **AC-A6** — *Given* egy ma `resolvedAt`-ot kapott `Ticket` és egy tegnap
  lezárt; *When* `GET /v1/dashboard`; *Then* `kpi.resolvedToday == 1`.
- **AC-A7** — *Given* a Dashboard `kpi.overdueCount` egy adott pillanatban
  kiszámolt értéke; *When* ugyanabban a pillanatban a `/bejelentesek` lista
  "Késésben" tabja betölt; *Then* a tab találati száma **megegyezik** a
  `kpi.overdueCount`-tal — a két predikátum azonos (EC-11, `40` 3.5
  bizalom-követelmény).
- **AC-A8** — *Given* nulla `Ticket` a tenant DB-ben; *When* `GET
  /v1/dashboard`; *Then* `200`, mind a négy `kpi`-mező `0`, a `team` üres
  tömb — nincs hiba (EC-1, EC-9).
- **AC-A9** — *Given* a hét-/nap-határ a tenant-időzónában számítva, egy
  `Ticket` `resolvedAt`-ja UTC-ben a budapesti vasárnap 23:30-nak megfelelő
  érték; *When* a heti aggregáció a lezáruló hétre fut; *Then* az ügy a
  lezáruló héthez számít (EC-4, SD-10).

### AC-B — Dashboard csapat-tábla

- **AC-B1** — *Given* két `field_worker` + `Active`, egy `field_worker` +
  `Invited`, egy `field_worker` + `Disabled`, és egy `dispatcher` +
  `Active` `TenantUser`; *When* `GET /v1/dashboard`; *Then* a `team`
  pontosan a két `field_worker` + `Active` sort tartalmazza (SD-53).
- **AC-B2** — *Given* egy `dispatcher` **és** `field_worker` szerepkörű,
  `Active` `TenantUser` (K-025 unió); *When* `GET /v1/dashboard`; *Then* a
  `team` tartalmazza ezt a sort — `roles ∋ field_worker` (TD-R5).
- **AC-B3** — *Given* egy `field_worker`, akihez a héten három személyre
  kiosztott `Ticket` `resolvedAt`-ja esik; *When* `GET /v1/dashboard`;
  *Then* a sorának `resolvedThisWeek == 3`.
- **AC-B4** — *Given* egy a héten lezárt `Ticket`, amely `assignedGroupId`-
  ra van kiosztva (`assignedUserId == null`); *When* `GET /v1/dashboard`;
  *Then* ez az ügy egyetlen `team`-sor `resolvedThisWeek`-jébe sem számít
  bele (SD-54).
- **AC-B5** — *Given* egy `field_worker`, akinek nincs heti lezárt és nincs
  `InProgress` ügye; *When* `GET /v1/dashboard`; *Then* a sora szerepel a
  `team`-ben, `resolvedThisWeek == 0`, `inProgressCount == 0`,
  `averageResolutionDays == null`.
- **AC-B6** — *Given* egy `field_worker`, akinek két héten lezárt ügye van,
  beérkezés→lezárás 2 és 4 nap; *When* `GET /v1/dashboard`; *Then* a
  sorának `averageResolutionDays == 3.0`.
- **AC-B7** — *Given* egy `field_worker`, akinek nincs héten lezárt ügye;
  *When* `GET /v1/dashboard`; *Then* a sorának `averageResolutionDays ==
  null` (nem `0` — a kliens "—"-t jelenít, EC-1).
- **AC-B8** — *Given* egy munkatárs `resolvedThisWeek == 12`, a többi mind
  `< 12`; *When* `GET /v1/dashboard`; *Then* pontosan ennek a sornak
  `isTopPerformer == true`, a többié `false` (SD-55/a).
- **AC-B9** — *Given* két munkatárs `resolvedThisWeek` értéke egyaránt a
  legmagasabb (holtverseny); *When* `GET /v1/dashboard`; *Then* minden sor
  `isTopPerformer == false`.
- **AC-B10** — *Given* minden `team`-sor `resolvedThisWeek == 0`; *When*
  `GET /v1/dashboard`; *Then* minden sor `isTopPerformer == false` — a
  `> 0` feltétel sérül.
- **AC-B11** — *Given* egy `field_worker` egy a héten lezárt ügye, amelyet
  utána a héten visszanyitottak (`resolvedAt := null`); *When* `GET
  /v1/dashboard`; *Then* az ügy nem számít a `resolvedThisWeek`-be (EC-3).

### AC-C — Riport-archívum és PDF-letöltés

- **AC-C1** — *Given* három `Completed` `WeeklyReport` különböző hetekre;
  *When* `GET /v1/weekly-reports`; *Then* `200`, a három riport `year DESC,
  isoWeek DESC` sorrendben, a `WeeklyReportListDto` nem tartalmaz
  `snapshotData`-t és `pdfFileRef`-et (3.3).
- **AC-C2** — *Given* egy `Completed` `WeeklyReport` `id`-ja; *When* `GET
  /v1/weekly-reports/{id}`; *Then* `200`, a teljes `WeeklyReportDto` a
  `snapshotData`-val együtt.
- **AC-C3** — *Given* egy idegen tenant `WeeklyReport`-`id`-ja; *When*
  `GET /v1/weekly-reports/{id}`; *Then* `404` (SD-12).
- **AC-C4** — *Given* egy `Completed` `WeeklyReport` `id`-ja; *When* `GET
  /v1/weekly-reports/{id}/pdf`; *Then* `200`, `Content-Type:
  application/pdf`, `Content-Disposition` a `heti-riport-2026-W28.pdf`
  mintájú fájlnévvel.
- **AC-C5** — *Given* egy `WeeklyReport`, amelynek `generationStatus ==
  Failed`; *When* `GET /v1/weekly-reports/{id}/pdf`; *Then* `409 { "reason":
  "not_generated" }`.
- **AC-C6** — *Given* legalább egy `Completed` `WeeklyReport`; *When* `GET
  /v1/weekly-reports/latest/pdf`; *Then* `200`, a `year/isoWeek` szerinti
  legfrissebb `Completed` riport PDF-je.
- **AC-C7** — *Given* egyetlen `Completed` `WeeklyReport` sincs a tenant
  DB-ben; *When* `GET /v1/weekly-reports/latest/pdf`; *Then* `404 {
  "reason": "no_report_yet" }`.
- **AC-C8** — *Given* egyetlen `Completed` `WeeklyReport` sincs; *When*
  `GET /v1/dashboard`; *Then* a `latestReport.available == false` (a
  kliens a gombot tiltottan jeleníti — 4.2).
- **AC-C9** — *Given* a `WeeklyReport` lista; *When* a kliens `POST`/`PUT`/
  `DELETE` műveletet kísérel meg bármely `weekly-reports` route-on; *Then*
  a route nincs az `authorization.json`-ban → `403` (3.3, fail closed).

### AC-D — A heti PDF generálása

- **AC-D1** — *Given* egy lezárult naptári hét, amelyben öt `Ticket`
  `resolvedAt`-ja esik a `[periodStart, periodEnd)` intervallumba; *When* a
  generáló job lefut; *Then* a `WeeklyReport.snapshotData.weekNumbers.resolvedCount
  == 5`.
- **AC-D2** — *Given* egy hét, amelyben két ügyet csoportra, hármat
  személyre osztva zártak le; *When* a generáló job lefut; *Then* a
  `resolvedCount == 5` — a PDF minden lezárt ügyet számol, a kiosztás
  módjától függetlenül (SD-54); ez **eltérhet** a Dashboard csapat-tábla
  személyenkénti összegétől, és az helyes.
- **AC-D3** — *Given* egy hét nulla lezárt üggyel; *When* a generáló job
  lefut; *Then* `generationStatus == Completed`, `resolvedCount == 0`,
  `averageResolutionDays == null` — a job nem hibázik (EC-1, EC-5).
- **AC-D4** — *Given* egy hét, amelyben lezárt ügyek három különböző
  gyökér-kategóriába és egy alkategóriába esnek; *When* a generáló job
  lefut; *Then* a `categoryBreakdown` a gyökér-szinten összesít — az
  alkategóriás ügy a gyökeréhez számít (3.6.2).
- **AC-D5** — *Given* egy tenant, amelynek nincs feltöltött logója; *When*
  a generáló job lefut; *Then* `generationStatus == Completed`, a PDF a
  tenant nevével, logó nélkül generálódik (EC-8).
- **AC-D6** — *Given* egy `WeeklyReport` egy adott `(year, isoWeek)`-re már
  létezik; *When* a generáló job ugyanarra a hétre újra lefut; *Then* nem
  keletkezik második `WeeklyReport`-sor — a `(year, isoWeek)` egyediség
  miatt a meglévő rekord frissül (SD-55/c, EC-5).
- **AC-D7** — *Given* a generálás közben az S3-feltöltés hibázik; *When* a
  job lefut; *Then* `generationStatus == Failed`, a `pdfFileRef` üres,
  e-mail nem megy ki (EC-6).
- **AC-D8** — *Given* egy `Failed` állapotú `WeeklyReport`; *When* a
  generáló job újra lefut rá; *Then* a `generationStatus: Failed →
  Generating` átmenet megengedett (retry — 2.3).
- **AC-D9** — *Given* a pilot első generált hete (nincs megelőző
  `WeeklyReport`); *When* a job lefut; *Then* a PDF "A múlt héthez képest"
  szakasza a semleges sort tartalmazza, nem hibázik és nem hagyja ki a
  szakaszt (EC-2, SD-55/b).
- **AC-D10** — *Given* egy megelőző heti `WeeklyReport` `resolvedCount ==
  14`, az aktuális heti `resolvedCount == 17`; *When* a job lefut; *Then* a
  PDF "A múlt héthez képest" szakasza a `+3` viszonyítást a két
  `snapshotData`-ból számolja (2.4).

### AC-E — E-mail-kézbesítés és `resend`

- **AC-E1** — *Given* egy `Completed` `WeeklyReport` és egy két-címzettű
  címzett-lista, mindkét kézbesítés sikeres; *When* a kézbesítés lefut;
  *Then* `deliveryStatus == AllSent`, `recipientCount == 2`, `deliveredAt`
  beáll.
- **AC-E2** — *Given* egy két-címzettű lista, az egyik kézbesítés
  sikertelen; *When* a kézbesítés lefut; *Then* `deliveryStatus ==
  PartialFailure` (EC-7, SD-56).
- **AC-E3** — *Given* egy `Completed` `WeeklyReport`, amelynek e-mail-
  kézbesítése `AllFailed`; *When* az állapotot ellenőrizzük; *Then*
  `generationStatus == Completed` változatlan — a kézbesítés hibája nem
  érvényteleníti a generálást (2.3, `02_globalis_allapotgep.md` 6.).
- **AC-E4** — *Given* egy `Completed` `WeeklyReport`; *When* `POST
  /v1/weekly-reports/{id}/resend`; *Then* `200`, a PDF újra kimegy az
  **aktuális** címzett-listának, a `deliveryStatus`/`deliveredAt`/
  `recipientCount` frissül, az `updatedBy` a kérő vezetőt rögzíti.
- **AC-E5** — *Given* egy `WeeklyReport`, `generationStatus == Generating`;
  *When* `POST .../resend`; *Then* `409 { "reason": "not_generated" }`.
- **AC-E6** — *Given* egy `Completed` `WeeklyReport` és üres címzett-lista;
  *When* `POST .../resend`; *Then* `422 { "reason": "no_recipients" }`.
- **AC-E7** — *Given* egy `resend` hívás; *When* lefut; *Then* a
  `WeeklyReport.snapshotData` és `pdfFileRef` **változatlan** — a PDF-
  tartalom nem generálódik újra (SD-49, SD-56).

### AC-F — Jogosultság (az MH-J1 / Szabály-R1 igazolása)

- **AC-F1** — *Given* a hat `40_riport`-route (`/v1/dashboard`,
  `/v1/weekly-reports`, `/v1/weekly-reports/{id}`, `.../{id}/pdf`,
  `.../latest/pdf`, `.../{id}/resend`); *When* megnézzük az
  `authorization.json` `roles`-listájukat; *Then* mindegyik pontosan
  `["tenant_manager"]` — ez az **AC-D1** a `05_jogosultsag_es_authorization.md`-
  ből, Szabály-R1 (statikus lint-szabály, azonnal futtatható).
- **AC-F2** — *Given* egy `tenant_dispatcher` JWT; *When* `GET
  /v1/dashboard`-t hív; *Then* `403`.
- **AC-F3** — *Given* egy `tenant_content_manager` JWT; *When* bármely
  `weekly-reports` route-ot hív; *Then* `403`.
- **AC-F4** — *Given* egy `tenant_field_worker` JWT; *When* `GET
  /v1/dashboard`-t hív; *Then* `403`.
- **AC-F5** — *Given* egy `tenant_manager` JWT, akinek a `Tenant` headere
  más tenant `code`-ját hordozza, mint a JWT; *When* bármely `40_riport`-
  route-ot hív; *Then* `403`, és a kísérlet naplózott biztonsági esemény
  (SD-13).
- **AC-F6** — *Given* egy `content_manager`; *When* a `/fooldal` vagy
  `/riportok` URL-t nyitja; *Then* a kliens-oldali route-guard megtagadja a
  betöltést — a guard megkerülésekor a szerver `403`-mal zár (4.5).

---

## 7. Keresztmetszeti

- **i18n** — minden UI-szöveg kulcs-alapú (4.6); a szerver kódot/`reason`-t
  ad, a magyar szöveget a kliens fogalmazza (`01_kozos_mintak.md` 5.5).
- **Időzóna** — minden időpont UTC-ben tárolva (SD-9), a nap/hét-határt a
  szerver számítja tenant-időzónában (SD-10); a kliens tenant-időzónában
  jeleníti.
- **Teljesítmény** — pilot-volumenen (`~100 bejelentés/hó`, SD-11) az
  aggregáció kis kérdéseket állít: nyitott + lejárt + ma + ezen a héten + a
  `field_worker` `TenantUser`-enként a hét lezárásai. A `(year, isoWeek)`
  unique index a `WeeklyReport`-on a generálás idempotenciáját és az
  archívum-lista rendezését gyorsítja. Cache-elés a Dashboard-aggregátumon
  nem indokolt a pilotra (a vezető ritkán tölti újra, és a friss adat
  fontosabb).
- **Biztonság** — `tenant_manager`-only minden route (3.9); a kliens-oldali
  guard + a szerver `403` kétrétegű (4.5). Az S3-prefix tenant-szegmentált
  (`Attachment`-minta, SD-15) — a PDF-bináris cross-tenant nem szivároghat.

---

## 8. Lezárás

### 8.1 Első kiadás (MVP) vs. következő iterációk

**Első kiadás — a pilotra (T0):**

- `WeeklyReport` entitás (Tenant DB), a két új enummal.
- `GET /v1/dashboard` — négy KPI-kártya + terepi csapat-tábla.
- A KPI-kártyák kattinthatósága (lefúrás a `/bejelentesek` listára); a
  csapat-tábla név-lefúrása (Wow #2).
- `WeeklyReport` archívum: `GET /v1/weekly-reports`, `GET
  /v1/weekly-reports/{id}`, PDF-letöltés (`/pdf`, `/latest/pdf`).
- Heti PDF-generáló háttér-job: hétfő reggel, fix sablon, a hét számai +
  múlt héthez viszonyítás + kategória-bontás + lábléc.
- Automatikus e-mail-kézbesítés a `50_konfig` címzett-listának; összesített
  `deliveryStatus`; manuális `resend` akció.
- `/fooldal` Dashboard, `/riportok` archívum-lista, `/riportok/details/<id>`
  adatlap a Címzettek szekcióval — mind vezető-only.
- Egységes szerver-oldali, tenant-időzónás nap/hét-definíció (SD-10) a
  Dashboard és a PDF között.

**Következő iterációk (listázva, nem most specifikálva):**

- Heti trend-grafikon, kategória-kördiagram a Dashboardon (`40` 5.2).
- Munkatárs "teljesítmény-profil" oldal (`40` 5.2).
- Konfigurálható riport-építő, custom KPI-k, konfigurálható időablak (K-016).
- Havi riport a heti mellé (`40` 7.2 — pilot-megfigyelési pont).
- Generált prózai értékelő szöveg (`40` 4.3).
- Riport-export Excelbe / nyers adat (`40` 5.2).
- Az `openAtWeekEnd` `ActivityLog`-alapú, pontos hét-végi pillanatkép-
  rekonstrukciója (MH-R2).
- A csapat-lefúrás heti+lezárt szűkítése (MH-R3).
- Csoport-szintű teljesítmény-aggregáció a Dashboardon (MH-R1).
- Címzettenkénti kézbesítés-státusz (egy `WeeklyReportDelivery` aldetail) —
  ha a pilot kívánja (SD-56 mérlegelt alternatívája).

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

1. A nap/hét-határt a szerver számítja, tenant-időzónában
   (`Europe/Budapest`), UTC-re konvertálva — platform-szinten rögzített
   (SD-10, `01_kozos_mintak.md` 5.2).
2. A `Ticket.resolvedAt` és `assignedAt` denormalizált esemény-időpontok
   (SD-14) megbízhatóan kitöltöttek — a riport-aggregáció ezekre épül.
3. A heti riport címzett-listáját a `50_konfig` feature-spec kezeli
   (`/beallitasok/altalanos`, `40` 4.5); ez a feature azt **fogyasztja**. A
   `50_konfig` ad egy olvasó-végpontot a tenant aktuális címzett-listájához
   (`tenant_manager` jogosultsággal), amit a riport-adatlap a Címzettek
   szekció (SD-57) betöltéséhez hív. Az olvasó-végpont igénye a `50_konfig`
   feature-spec rendes része.
4. A `WeeklyReport` PDF-binárisa S3-kompatibilis blob storage-ban él, az
   `Attachment.fileRef` mintával konzisztensen (SD-15); a tenant-
   szegmentáció (bucket/prefix) fejlesztői döntés.
5. A multi-tenancy modell a `01_kozos_mintak.md` SD-1 szerinti (Core DB +
   DB-per-tenant, `Tenant` header-resolution) — ez a feature-spec arra
   épül.
6. A generáló háttér-job ütemezője és az e-mail-küldés infrastruktúrája
   fejlesztői/üzemeltetési döntés (döntésnaplóba); a feature-spec a
   *viselkedést* köti ki (hétfő reggel, best-effort kézbesítés), nem az
   eszközt.
7. A pilot-volumen `~100 bejelentés/hó` (SD-11) — ez teszi elfogadhatóvá az
   `openAtWeekEnd` közelítését (MH-R2) és az offset-paginálást a `/riportok`
   listán.

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

A v2.1 instrukció (d) szerint: a megtartott hiány nem blokkolja a feature
gerincét, ezért a feature halad; a hiány nyomon követhető marad.

| # | Tárgy | Típus | Hatáskör |
|---|---|---|---|
| **MH-R1** | A csoport-szintű teljesítmény-aggregáció a Dashboard csapat-tábláján a pilotra kimarad (SD-54): a tábla csak a személyre osztott ügyet számolja. **Nem blokkol:** a személy-szintű tábla és a PDF összesítése enélkül is teljes; a `Group` a pilotra "egy címke + taglista", nincs alapja a csoportos ügy taghoz rendelésének. | Megtartott hiány | Terepi mobilapp későbbi tervezése |
| **MH-R2** | Az `openAtWeekEnd` a generálás pillanatának `Ticket.status`-át használja, nem a pontos vasárnap 24:00-i állapot `ActivityLog`-rekonstrukcióját (3.6.1). **Nem blokkol:** a job hétfő reggel, néhány órával a hét vége után fut; pilot-volumenen az ablak elhanyagolható. | Megtartott hiány | Riport-feature iteráció |
| **MH-R3** | A Dashboard csapat-lefúrása a pilotra a felelős-szűrt teljes `/bejelentesek` listát adja, nem a heti+lezárt szűkítést (4.2). **Nem blokkol:** a `40` 3.3 maga is kimondja — a Wow #2 a felelős-szűrt listával teljesül. | Megtartott hiány | Riport-feature iteráció |
| **VF-R1** | A `10_bejelentes_lista_es_adatlap.md` listája egy "lezárás dátuma" (`resolvedAt`) dátum-tartomány-szűrővel bővítendő, hogy az "Aznap lezárt" Dashboard-kártya lefúrása teljes legyen (SD-52). A `createdAt`-szűrő a `10` listán már létezik; csak a `resolvedAt`-szűrő hézag. **Nem blokkol:** amíg a `10` nem bővül, az "Aznap lezárt" a `/bejelentesek?tab=lezart`-ra fúr le dátum-szűrő nélkül — működik, csak nem szűkített. | Visszacsorgó jelzés | `10_bejelentes_lista_es_adatlap.md` feature-spec |
| **NY-R2** | A polgári elégedettség-mérés: a `40` 7.3 szerint Péter pályája "polgári elégedettséget" várna a riportban, de a pilot-MVP-ben nincs mérési mód (NY-bej-5). A riport a pilotra elégedettségi mutató nélkül készül. Béla felé jelezni kell: Péternek ne ígérjen olyat, ami a pilotra nincs. | Elhatárolás — nem ennek a körnek a tárgya | Polgári mobilapp funkcionális tervezése + adatigény-spec |

> A három megtartott hiány és az elhatárolás egyike sem érinti a feature
> **gerincét** — a `Ticket`-aggregációból számolt KPI-k, a csapat-tábla, a
> heti PDF generálása és kézbesítése enélkül is teljesen specifikált.

### 8.4 Hivatkozott dokumentumok

- **Funkcionális:** `40_riport_v1.md` (a fő bemenet), `00_architektura_v4.md`,
  `90_sitemap_v3.md`
- **Jogosultság:** `05_jogosultsagok_v2.md` 2.7,
  `05_jogosultsag_es_authorization.md` 3.2 E-blokk / 3.3 Szabály-R1 / 8.3 MH-1
- **Alapdokumentumok:** `01_kozos_mintak.md` (5.2 / SD-10, 6., 7.),
  `00_domain_model.md`, `02_globalis_allapotgep.md`
- **Meglévő feature-specek:** `10_bejelentes_lista_es_adatlap.md` (3.2, 4.2,
  5.4), `20_felhasznalokezeles.md` (2.4), `50_konfig_v2.md` (5.)
- **Kanonikus:** `kanonikus_donek.md` — K-009, K-011, K-016, K-027, K-029,
  K-035
- **Kísérő:** `99_donesnaplo.md` (SD-49…SD-57, MH-R1–3, VF-R1, NY-R2),
  `CLAUDE.md`, `00_terminologia.md` (`WeeklyReport` új sor)
- **Meglévő minták:** `project_backend` CLAUDE.md (`BaseController`,
  `authorization.json`, `AuditableEntity`, Zitadel JWT, S3-tárolás),
  `project_backend_client_angular` CLAUDE.md (`TableStateConfig`,
  `NavStore`, adatlap-komponens, OpenAPI-kliens)

---

## Verziónapló

- **v1.0 (2026.05.20)** — Első kiadás. A Dashboard és a heti PDF-riport
  fejlesztői specifikációja a `40_riport_v1.md` funkcionális tervéből.
  Egy új entitás (`WeeklyReport`, Tenant DB, `AuditableEntity`), két új
  enum (`ReportGenerationStatus`, `ReportDeliveryStatus`); hat
  `40_riport`-route mind `tenant_manager`-only — **az MH-J1 lezárva**, a
  Szabály-R1 érvényesítve (AC-F1). Kilenc specifikációs döntés
  (SD-49 — SD-57): a `WeeklyReport` entitás, a `GET /v1/dashboard`
  aggregációs végpont, az "Aznapi új"/"Aznap lezárt" lefúrás query-param-
  szerződése, a "Nyitott ügyek" három-állapotú definíciója, a csapat-tábla
  sorai és személy-szintű aggregációja, a relatív kiemelés, az első hét
  semleges sora, az idempotencia, a kézbesítés-státusz granularitása és a
  `resend` akció, a riport-adatlap Címzettek szekciója. Három megtartott
  hiány (MH-R1 csoport-aggregáció, MH-R2 `openAtWeekEnd` közelítés, MH-R3
  csapat-lefúrás szűkítése), egy visszacsorgó jelzés (VF-R1 — a `10` lista
  `resolvedAt`-szűrője), egy elhatárolás (NY-R2 polgári elégedettség-mérés).
  Az NY-R1 (címzett-lista megjelenítés) lezárva SD-57-ben. 43 acceptance
  criterion hat blokkban.
