6.0 KiB
6.0 KiB
Implementierungsplan
Status: final (2026-09-13, abgestimmt) · Basis: anforderungen.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 Clientessens), Auto-Provision des lokalen Users (wiesvc-webui). HA bezieht die Tagesansicht überGET /api/todaymit statischem API-Token (X-Api-Tokenaus.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.mdleergelegt (src/UI,src/Logic,src/Data) - Keycloak-OIDC: manueller
OidcClient(keinleague/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(Volumedata/für SQLite,.env), Networkinfra-traefik_traefik-net(external), Traefik-Labels →essen.ljh31.de+ ACME + redirect-to-https phpunit.xml,tests/, htmx + SortableJS inpublic/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(Tabelleessens_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 testgrü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(Statusaktiv/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 aufstatus = 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-TabGET /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 →CreateDishmit Referenz- DoD: Reorder bleibt nach Reload bestehen; Inspiration klickbar adoptierend
- Konkrete Umsetzung (M1-Konvention, Domäne DE): Schritt 1 (Logik):
MahlzeitReorderUseCase(neue ID-Reihenfolge →sortOrderneu nummerieren, nur geänderte Positionen updaten,MahlzeitReorderedEvent),MahlzeitInspirationQuery(Kandidaten = Namen mit ≥ 1gegessen-Eintrag, Repräsentant = neuestergegessen-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 → neuegeplant-Kopie mitgegessenAm = null,MahlzeitAdoptedEvent). Schritt 2 (UI):GET /mahlzeiten= flache, per SortableJS sortierbare Planungsliste (nurgeplant, Drag-Handle,POST /mahlzeiten/reordermit JSON/Formids→ 204); Tagesansicht ausgelagert nachGET /mahlzeiten/tage; Inspiration unterGET /inspiration(5 Vorschläge, „Weitere 5" via?ausschluss=…, 1-Klick-POST /mahlzeiten/{id}/adoptmit Kontext-Erhaltung).
M4 – Härtung & API
X-Api-Token-Guard (optional,.env) fürGET /api/today(HA-ready)- Fehlerseiten, Logging, Backup-Hinweis (SQLite-Datei
data/) - DoD:
curl -H "X-Api-Token: …" /api/todayliefert Tagesgericht-JSON; DoD-Punkte ausanforderungen.mdabgehakt
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
essensin realmfamilyanlegen (Redirect-URIhttps://essen.ljh31.de) - DoD: Gesamt-DoD aus
anforderungen.mderfü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 testbzw.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 | 2026-09-19 | abgenommen |
| M4 | offen | |
| M5 | offen |