feat: Komfort-Features für Unterrichtsplanung, Zeiterfassung und Dashboard

- Neue-Stunde-Dialog: Datum wird beim Anlegen anhand des Stundenplans
  und der letzten Stunde der Einheit vorbelegt statt auf "heute"
  (behebt eine falsch erkannte Doppelstunde, wenn "heute" nicht auf
  den passenden Wochentag fiel).
- Zeiterfassung: Button "Unterrichtszeit heute übernehmen" schlägt
  Start/Ende aus dem heutigen Stundenplan inkl. Puffer davor/danach vor.
- Dashboard: neue Kachel "Ungeplante Stunden" erinnert an Stunden ohne
  Thema für heute/morgen, mit Opt-out je Gruppe ("Benötigt
  Unterrichtsplanung"), Doppelstunden-Erkennung (keine doppelte Meldung
  für die zweite Periode) und Berücksichtigung von Stundenausfall.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 11:30:50 +02:00
co-authored by Claude Sonnet 5
parent a36b1d1d40
commit fd509fc921
17 changed files with 585 additions and 23 deletions
+70
View File
@@ -460,6 +460,20 @@ Redesign:
Viewer unabhängig ab `Lesson.StartTime`, nicht ab einer gemeinsamen Verzweigungsstelle im
Hauptweg — deutlich einfacher und für den schnellen Überblick ausreichend.
**Nachtrag — Datum-Vorbelegung beim manuellen " Stunde"-Dialog:** Nutzer-Bug-Report: eine
manuell angelegte Doppelstunde zeigte immer 45 statt 90 Minuten Zeitbedarf. Ursache war kein
Fehler in der Doppelstunden-Erkennung (Nachtrag zu 4.2.2 oben) selbst, sondern dass `DateText`
im `LessonDialogViewModel` beim Neuanlegen immer auf "heute" vorbelegt war — traf dieses Datum
nicht auf den Wochentag der eingetragenen Stundennummer im Stundenplan, lief die
weekday-basierte Nachschlage-Logik für die Folgeperiode ins Leere und die Erkennung blieb
stumm bei 45 Minuten, ohne dass das im UI ersichtlich war. `LessonDialogViewModel.
SuggestNextLessonDate()` ersetzt jetzt den "heute"-Default beim Neuanlegen: sie ermittelt aus
`TimetableSlot` alle Wochentage, an denen die Gruppe laut Stundenplan Unterricht hat, nimmt das
späteste bereits existierende `Lesson.Date` der Einheit (oder heute, falls die Einheit noch
leer ist) und rollt von dort vorwärts auf den nächsten passenden Wochentag. Fehlt ein
Stundenplan-Eintrag für die Gruppe, bleibt "heute" als Fallback erhalten (keine Verhaltens-
änderung für Gruppen ohne Stundenplan).
### 4.3 Stundenplan
- [x] **4.3.1** Neues Modell `TimetableSlot` (Gruppe, Wochentag, Stunde, Raum) + Repository —
[Planning.cs](LehrerApp.Core/Models/Planning.cs),
@@ -1101,6 +1115,22 @@ Auswertung (6.3.3), der am noch nicht existierenden Kapitel 11 (Export-Infrastru
Zeiteintrag existiert; dafür braucht `WorkTaskListViewModel` jetzt zusätzlich
`ITimeEntryRepository`.
**Nachtrag — Komfort-Funktion "Unterrichtszeit heute übernehmen":** Nutzer-Wunsch: Unterrichtszeit
soll nicht komplett automatisch erfasst werden ("weiß nicht, ob das rechtlich sinnvoll ist"),
aber ein Vorschlag mit Rückfrage ist erwünscht — man beginnt vor der ersten Stunde und geht nicht
sofort nach der letzten. Neuer Button "🕓 Unterrichtszeit heute übernehmen" in
`TimeTrackingView` (nur sichtbar, wenn die Gruppe heute laut Stundenplan überhaupt Unterricht
hat — `TimeTrackingViewModel.HasTeachingTimeSuggestionToday`). `ComputeTodaysTeachingWindow()`
ermittelt aus allen `TimetableSlot`-Einträgen des heutigen Wochentags (`ITimetableSlotRepository`)
und dem Stundenraster (6.2/4.2, `PeriodScheduleService`) die früheste Start- und späteste
Endzeit des Tages, zieht einen festen Puffer davor (`BufferBeforeFirstPeriodMinutes = 15`) und
danach (`BufferAfterLastPeriodMinutes = 10`) ab/dazu. Klick öffnet den bestehenden
`AddTimeEntryDialog` (6.2.2) mit Kategorie "Unterricht" sowie Start/Ende vorbefüllt — bewusst
weiterhin ein normaler, vom Nutzer bestätigter Nacherfassungs-Dialog, keine automatische
Buchung ohne Blick darauf. Neuer `TaskCategory.Teaching`-Wert (`TaskCategoryDisplay.Label`:
"Unterricht") ans Ende des Enums angehängt, um bestehende serialisierte Werte nicht zu
verschieben.
### 6.3 Auswertung
- [x] **6.3.1** Monats-/Jahresauswertung nach Kategorie und Gruppe (Diagramm + Tabelle) —
dritter Tab "Auswertung" (`WorkloadEvaluationViewModel`). Zeitraum wahlweise Monat
@@ -1294,6 +1324,46 @@ Hervorhebung "eigene Klasse" über `LearningGroup.IsOwnClass`, feste Kartenbreit
Unterricht und Klausuren werden jetzt im Detailbereich der Kalenderkachel angezeigt und
verlinkt. Weitere Terminarten können später über das vorhandene `CalendarEventItem` ergänzt
werden, sobald dafür ein eigenes Termine-Modell existiert.
- [x] **9.9** Kachel "Ungeplante Stunden": erinnert an Stunden ohne Thema, für die noch kein
Verlaufsplan existiert. Nutzer-Wunsch: der zeitliche Vorgriff soll sinnvoll begrenzt sein
(morgens sollte wenigstens ein Stundenthema bereits bekannt sein) und einzelne Gruppen ohne
inhaltlichen Verlaufsplan (Klassenrat, Willkommenskreis) sollen sich ausnehmen lassen.
**Umsetzung:** `DashboardViewModel.LoadUnplannedLessons` läuft für jede Gruppe mit
`LearningGroup.RequiresLessonPlanning == true` (neues Feld, Default `true` — bestehende
Gruppen erhalten es beim LiteDB-Deserialisieren automatisch, keine explizite Migration
nötig) über die `TimetableSlot`-Einträge von heute bis morgen
(`UnplannedLessonsLookaheadDays = 1`, Nutzer hat sich im Dialog explizit für "Heute +
morgen" statt eines längeren Vorgriffs entschieden) und meldet jeden Termin, zu dem entweder
keine `Lesson` existiert oder deren `Topic` leer ist. Ferientage/Feiertage werden wie überall
sonst übersprungen (`IsFreeDay`, dieselbe Prüfung wie in `TimetableViewModel`/
`GenerateLessonSeriesDialogViewModel` — bewusst dupliziert statt in einen gemeinsamen
Service extrahiert, konsistent mit dem bestehenden Muster). Klick auf einen Eintrag springt
in die Lerngruppe, Tab "Planung" (`OnNavigateToUnplannedLesson`,
`NavigateToGroupDetail(id, 6)`), damit das Thema direkt ergänzt werden kann — bewusst
anderes Sprungziel als die "Heutige Stunden"-Kachel (9.2, Tab "Mitarbeit"). Deaktivierbar je
Gruppe über eine neue Checkbox "Benötigt Unterrichtsplanung" im Gruppen-Stammdaten-Dialog.
**Nachtrag zu 9.9 (Bugfix Doppelstunden-Erkennung):** Nutzer-Bug-Report: eine als Doppelstunde
geplante 3./4. Stunde (eine `Lesson` mit `LessonNumber = 3` und Thema, die laut Stundenraster
90 Minuten abdeckt, siehe Nachtrag zu 4.2.2) wurde auf dem Dashboard trotzdem als "4. Stunde noch
ungeplant" gemeldet, weil `LoadUnplannedLessons` je `TimetableSlot` stur auf eine `Lesson` mit
exakt derselben `LessonNumber` prüfte — für Periode 4 gibt es bei einer Doppelstunde aber bewusst
keine eigene `Lesson`. Neue Hilfsmethode `IsCoveredByEarlierDoppelstunde` wendet dieselbe
Rückwärts-Erkennung wie `LessonDialogViewModel.RecomputeTimeBudget` an: ausgehend von der
Vorperiode wird so lange rückwärts geprüft, wie der Stundenplan dort ebenfalls einen Slot hat;
trifft man auf eine `Lesson` mit Thema, gilt die Periode als abgedeckt, trifft man auf eine
`Lesson` ohne Thema, bricht die Suche ab (diese Periode bleibt eine eigene, ungeplante Stunde).
Eine eigene, direkt zugeordnete `Lesson` ohne Thema wird also weiterhin gemeldet — nur die
implizit mitabgedeckte Folgeperiode einer bereits geplanten Doppelstunde nicht mehr.
**Nachtrag zu 9.9 (Bugfix Stundenausfall):** Zweiter Nutzer-Bug-Report: eine laut Stundenplan
regulär stattfindende, aber per Vertretungs-Dialog als `SubstitutionKind.Cancelled` eingetragene
Stunde (4.3, "Stundenausfall") wurde trotzdem als ungeplant gemeldet, obwohl sie an diesem Tag
gar nicht stattfindet. `LoadUnplannedLessons` prüft jetzt zusätzlich `ISubstitutionEntryRepository.
GetByDate(date)` auf `Cancelled`-Einträge und überspringt betroffene Stundennummern vollständig —
gleiche Abfrage (Datum + Stundennummer, bewusst ohne Gruppenbezug, siehe `SubstitutionEntry.
PeriodNumber`-Doku) wie in `TimetableViewModel.BuildToday` für die "Heute"-Ansicht des
Stundenplans.
---