Files

6.5 KiB
Raw Permalink Blame History

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 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): GET /mahlzeiten = flache, per SortableJS sortierbare Planungsliste (nur geplant, Drag-Handle, POST /mahlzeiten/reorder mit JSON/Form ids → 204); Tagesansicht ausgelagert nach GET /mahlzeiten/tage; Inspiration unter GET /inspiration (5 Vorschläge, „Weitere 5" via ?ausschluss=…, 1-Klick-POST /mahlzeiten/{id}/adopt mit Kontext-Erhaltung).

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 2026-09-19 abgenommen
M4 offen
M5 offen
�������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������