Files
svc-essensplanung/Doku/plan.md
T

103 lines
5.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementierungsplan
Status: **final** (2026-09-13, abgestimmt) · Basis: [`anforderungen.md`](anforderungen.md), [`architektur.md`](architektur.md), ADR 001–007
## Prinzipien
- **Vertikal geschnitten:** Jedes Milestone ist ein vollständiger UI→Logic→Data-Slice
und am Ende lauffähig/testbar (kein Layer-Reihenbau).
- **Ein Milestone = ein Review-Pausenpunkt.** Jeder MS wird einzeln abgenommen,
bevor der nächste startet.
- **Kein CI** – PHPUnit läuft manuell (`composer test`).
- **Auth:** Web-UI über **Keycloak-OIDC** (realm `family`, eigener Client
`essens`), Auto-Provision des lokalen Users (wie `svc-webui`). HA bezieht die
Tagesansicht über `GET /api/today` mit **statischem API-Token**
(`X-Api-Token` aus `.env`, kein Keycloak-Refresh für HA). Damit wird
ADR-003 in M0 entsprechend überarbeitet.
- **Deploy** (M5) erst nach expliziter Freigabe.
## Milestones
### M0 – Skelett + Auth-Grundgerüst
- Symfony 7.4 LTS, PHP 8.4, FrankenPHP: `composer.json`, `config/`
(framework, doctrine/SQLite, twig, routing), `public/index.php`
- Struktur laut `architektur.md` leergelegt (`src/UI`, `src/Logic`, `src/Data`)
- Keycloak-OIDC: manueller `OidcClient` (kein `league/oauth2-client`, kein
Security-Bundle; vgl. ADR-003), Login/Callback/Logout im UI-Layer,
`sub`/`email`-Claim → lokale User-Zeile (Auto-Provision)
- Docker: `Dockerfile` (php:8.4-frankenphp), `compose.yaml`
(Volume `data/` für SQLite, `.env`), Network `infra-traefik_traefik-net` (external),
Traefik-Labels → `essen.ljh31.de` + ACME + redirect-to-https
- `phpunit.xml`, `tests/`, htmx + SortableJS in `public/js/`
- ADR-003 überarbeitet (Keycloak statt „kein Login")
- **DoD:** Container läuft, `GET /` → Keycloak-Login, danach Hello-World-Seite
### M1 – CRUD-Slice (Model: `Mahlzeit` – Namenskonszept: Domäne DE, Tech-EN, s. architektur.md §0.1)
- **Schritt 1 (Logik+Daten):** `src/Logic/Essen` – `Mahlzeit`-Model (+`MahlzeitStatus`, `Wochentag`),
`Mahlzeit{Create,Update}Request/Response`-DTOs, `MahlzeitFactory`, `Mahlzeit{Provider,Processor}Interface`,
`MahlzeitManager`, `Mahlzeit{Create,Update,Delete}UseCase`, `MahlzeitListQuery`,
`MahlzeitNotFoundException` (→404), Events; `src/Data/Essen` – `MahlzeitEntity`
(Tabelle `essens_mahlzeiten`, wochentage als CSV), `MahlzeitMapper/Provider/Processor`, Migration.
Tests: UseCase/Model-Unit (Pure PHP, Fakes) + Provider/Processor-Integration (file-basierte SQLite, `var/test.db`).
**DoD 1:** `composer test` grün, CRUD vollständig über Logic-API testbar.
- **Schritt 2 (UI):** `MahlzeitController` + `templates/mahlzeiten/` + Routen `/mahlzeiten[/{id}]`
(POST/Redirect/Flash, Wochentage als Checkboxes) + Controller-Feature-Test (eingeloggte Session).
- **DoD:** Gericht anlegen/bearbeiten/löschen im Browser (als eingeloggte/r Nutzer)
### M2 – Status & Wochentage
- `CheckOffDish`/`UncheckDish` (Status `aktiv`/`gecheckt`), `ArchiveDish`
- Wochentag-Tags (Mo–So) setzen/ändern, Liste nach Tag gruppier-/filterbar
- **DoD:** Check-Off via htmx-Toggle, Tagesansicht, Archiv-Tab
- *Konkrete Umsetzung (M1-Konvention, Domäne DE):* `MahlzeitCheckOffUseCase`
(geplant→gegessen) / `MahlzeitUncheckUseCase` (gegessen→geplant), beide
idempotent, `MahlzeitStatusChangedEvent`. „Archiv“ = Read-View auf
`status = gegessen` (kein eigener Status/Use-Case). Check-Off als htmx-Partial
(`POST /mahlzeiten/{id}/checkoff|uncheck`), Tagesansicht gruppiert nach
Wochentag (Mo…So, „Ohne Tag“), Archiv-Tab `GET /mahlzeiten/archiv`.
### M3 – Reorder + Inspiration
- `ReorderDishes` (SortableJS-`change` → Position-Ints, persistiert)
- `SuggestDishes`-Query: Slow 0.5 / Rarität 0.3 / Hotness 0.2 (ADR-006),
5 Vorschläge + „Weitere 5", 1-Klick-Adopt → `CreateDish` mit Referenz
- **DoD:** Reorder bleibt nach Reload bestehen; Inspiration klickbar adoptierend
- *Konkrete Umsetzung (M1-Konvention, Domäne DE):* Schritt 1 (Logik):
`MahlzeitReorderUseCase` (neue ID-Reihenfolge → `sortOrder` neu nummerieren,
nur geänderte Positionen updaten, `MahlzeitReorderedEvent`),
`MahlzeitInspirationQuery` (Kandidaten = Namen mit ≥ 1 `gegessen`-Eintrag,
Repräsentant = neuester `gegessen`-Eintrag, Score nach ADR-006 mit
Defaults 0.5/0.3/0.2, `ausschluss`-IDs für „Weitere 5", Sortierung
absteigend nach Score, bei Gleichstand alphabetisch), `MahlzeitAdoptUseCase`
(Quelle → neue `geplant`-Kopie mit `gegessenAm = null`, `MahlzeitAdoptedEvent`).
Schritt 2 (UI): Planungsliste mit SortableJS + `POST /mahlzeiten/reorder`,
Inspiration-View `GET /inspiration` + `POST /mahlzeiten/{id}/adopt`.
### M4 – Härtung & API
- `X-Api-Token`-Guard (optional, `.env`) für `GET /api/today` (HA-ready)
- Fehlerseiten, Logging, Backup-Hinweis (SQLite-Datei `data/`)
- **DoD:** `curl -H "X-Api-Token: …" /api/today` liefert Tagesgericht-JSON;
DoD-Punkte aus `anforderungen.md` abgehakt
### M5 – Deploy (erst nach Freigabe)
- Stack auf `docker1:/root/stacks/svc-essensplanung`, `git pull && docker compose up -d`
- Traefik: `essen.ljh31.de`, Let's-Encrypt
- Keycloak: Client `essens` in realm `family` anlegen (Redirect-URI `https://essen.ljh31.de`)
- **DoD:** Gesamt-DoD aus `anforderungen.md` erfüllt, erreichbar ohne LAN-IP
## Test-Strategie (quer)
- PHPUnit je Milestone: UseCase/Model-Unit (Pure PHP, Logic-Dependencies als
Fakes) + Integration-/Feature-Tests (HTTP/DB gegen file-basierte Test-DB `var/test.db`)
- Manuell: `docker compose run app composer test` bzw. `vendor/bin/phpunit`
- Kein CI (bewusst, s. Prinzipien)
## Abnahme-Historie
| MS | Datum | Status |
|----|----------|------------|
| M0 | 2026-09-13 | abgenommen |
| M1 | 2026-09-19 | abgenommen |
| M2 | 2026-09-19 | abgenommen |
| M3 | | offen |
| M4 | | offen |
| M5 | | offen |