WIP (unstable): Klassenlehrer-Feature auf Einstellungen/eigenen Bereich umgestellt

Nutzer-Feedback: Klassenlehrer ist eine Rolle für die ganze Klasse, unabhängig vom
eigenen Unterricht - die Anbindung an ein LearningGroup.IsClassTeacher-Flag mit Tab
in der Gruppendetailansicht war deshalb der falsche Ort. Ersetzt durch:

- Klassenauswahl in den WebUntis-Einstellungen (WebUntisSettingsService.HomeroomClassName),
  über den bestehenden WebUntis-Klassenpicker.
- Eigener Sidebar-Bereich "Klassenlehrer" statt Gruppen-Tab.
- Zwei Ebenen: kompakte Roster-Übersicht mit Ampel-Symbolen (Fehlzeiten heute,
  Klassenbuch-Badge) und Details (die bisherigen Rohdaten-Listen, jetzt gefiltert
  auf einzelne Schüler*innen anspringbar).

Noch nicht mit echtem WebUntis-Zugang gegengeprüft (siehe TODO.md).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-25 22:30:34 +02:00
co-authored by Claude Sonnet 5
parent 5e16ebf1bf
commit 33c15ffe3e
21 changed files with 463 additions and 113 deletions
+50 -46
View File
@@ -1277,53 +1277,57 @@ eigenen Unterricht abfragt und deshalb mit den regulären Lehrkraft-Rechten funk
Auch hier keine externe Schülerkennung, nur der Name — Namensabgleich soll deshalb zusätzlich auf
Klasse+Fach aus der Zeile scopen (→ genau eine lokale Lerngruppe), nicht global über alle Schüler.
Abgleichsdialog (Diff gegen `Documentation`, Übernehmen in beide Richtungen) noch nicht gebaut.
- [x] **"Klassenlehrer"-Feature, Schritt 1 (Klassenbuch fremder Einträge, August 2026):** Der
"-alle-"-Bericht enthält für die eigene Klasse auch Einträge, die andere Lehrkräfte in deren
Fächern angelegt haben (`Benutzer` ≠ eigener Login) — für den reinen Dokumentations-Abgleich
(eigene Einträge synchron halten) werden die herausgefiltert. Neues Flag
`LearningGroup.IsClassTeacher` ("Klassenlehrer/-in dieser Klasse", Stammdaten-Checkbox, keine
Migration nötig) blendet einen neuen Tab "Klassenlehrer" in der Gruppendetailansicht ein —
bewusst ein eigenes Flag statt `IsOwnClass` (das ist rein die Kalenderfarb-Markierung und
mehrfach setzbar für Klasse + zugehörige Kurse, würde den Tab sonst versehentlich auch bei
Kursen zeigen). `WebUntisIntegrationService.GetForeignClassRegisterEventsAsync` ist das
Spiegelbild von `GetOwnClassRegisterEventsAsync`: derselbe "-alle-"-Rohbericht, aber Einträge
anderer Lehrkräfte statt der eigenen, zusätzlich auf die übergebene Klasse eingeschränkt (kein
neuer WebUntis-Bericht nötig). `ClassTeacherTabViewModel`/`ClassTeacherTabView` zeigen die
Liste rein lesend an (Datum, Schüler*in, Fach/Lehrkraft, Kategorie, Text) — kein
"Übernehmen"-Command wie beim Dokumentations-Abgleich, da es fremde Einträge sind, keine
eigenen zum lokalen Synchronisieren.
**Nicht Teil dieses Schritts:** Fehlzeiten der Klasse aus fremdem Unterricht — dafür fehlt noch
ein per Netzwerk-Mitschnitt zu ermittelnder klassenbezogener Fehlzeiten-Bericht ohne `lessonId`
(vermutlich `klasseId`/`teacherId` statt `lsid`); folgt als eigener Schritt, sobald die
Query-Parameter bekannt sind. Ebenfalls zurückgestellt: Dokumentation/Behördenschriftverkehr als
eigenständiger, WebUntis-unabhängiger Baustein.
- [x] **"Klassenlehrer"-Feature, Schritt 2 (Fehlzeiten aus fremdem Unterricht, August 2026):** Der
gesuchte Bericht ist `reports.do?name=AbsencePerStudent&klasseOrStudentgroupId=KL<id>&studentId=-1`
(per Netzwerk-Mitschnitt gefunden) — Query-Parameter sind bis auf `name`/`klasseOrStudentgroupId`/
`studentId` identisch mit `ClassregEventPerStudent`. `klasseOrStudentgroupId` ist `"KL"` +
die `getKlassen`-interne Klassen-ID (dasselbe Element-Kürzel-Schema wie `kl`/`te`/`su`/`ro` bei
anderen Berichten) — **kein manuelles Feld nötig**, `WebUntisIntegrationService.ResolveClassIdAsync`
löst sie pro Aufruf über `getSchoolyears`+`getKlassen` und den Klassennamen auf (analog zum
bestehenden Klassen-Auswahldialog). Das CSV-Format ist **nicht** identisch mit `AbsencePerLesson`
(ursprüngliche Vermutung anhand echter Daten widerlegt) — eigenes Spaltenschema, eine Zeile pro
Fehlstunde, neuer Parser `WebUntisClassAbsenceParser`/Modell `UntisClassAbsenceEntry`. Anders als
`AbsencePerLesson` liefert `Status` hier einen Klartext-Wert direkt (im Sample nur `"nicht
entsch."` beobachtet, andere Werte nicht bestätigt), es braucht also keine Status-Heuristik.
Der Header `Text` kommt zweimal vor (eine immer-leere Spalte, dann die eigentliche Tagesnotiz) —
der bestehende Dictionary-per-Header-Parse-Stil überschreibt das zufällig richtig, im Parser
kommentiert statt stillschweigend vorausgesetzt. `Fehltage` (0/1, `CountsAsFullDay`) ist laut
Nutzer-Angabe der auf eine ganze Zahl abgerundete Fehlzeit-Tagesanteil: bleibt 0, bis auch die
- [x] **"Klassenlehrer"-Feature — Datenschicht (August 2026):** Zwei WebUntis-Berichte liefern, was
für einen Klassenlehrer-Überblick fehlte. `WebUntisIntegrationService.GetForeignClassRegisterEventsAsync`
ist das Spiegelbild von `GetOwnClassRegisterEventsAsync`: derselbe "-alle-"-Rohbericht
(`ClassregEventPerStudent`), aber Einträge anderer Lehrkräfte (`Benutzer` ≠ eigener Login) statt
der eigenen, auf eine Klasse eingeschränkt — kein neuer Bericht nötig.
`GetClassAbsencesAsync`/`reports.do?name=AbsencePerStudent&klasseOrStudentgroupId=KL<id>&studentId=-1`
(per Netzwerk-Mitschnitt gefunden) liefert Fehlzeiten der ganzen Klasse über alle Fächer, nicht nur
eigenen Unterricht. `klasseOrStudentgroupId` ist `"KL"` + die `getKlassen`-interne Klassen-ID
(dasselbe Element-Kürzel-Schema wie `kl`/`te`/`su`/`ro` bei anderen Berichten) —
`WebUntisIntegrationService.ResolveClassIdAsync` löst sie pro Aufruf über
`getSchoolyears`+`getKlassen` und den Klassennamen auf. Das CSV-Format ist **nicht** identisch mit
`AbsencePerLesson` (ursprüngliche Vermutung anhand echter Daten widerlegt) — eigenes Spaltenschema,
eine Zeile pro Fehlstunde, eigener Parser `WebUntisClassAbsenceParser`/Modell
`UntisClassAbsenceEntry`. Anders als `AbsencePerLesson` liefert `Status` hier einen Klartext-Wert
direkt (im Sample nur `"nicht entsch."` beobachtet, andere Werte nicht bestätigt), es braucht also
keine Status-Heuristik. Der Header `Text` kommt zweimal vor (eine immer-leere Spalte, dann die
eigentliche Tagesnotiz) — der Dictionary-per-Header-Parse-Stil überschreibt das zufällig richtig,
im Parser kommentiert statt stillschweigend vorausgesetzt. `Fehltage` (0/1, `CountsAsFullDay`) ist
laut Nutzer-Angabe der auf eine ganze Zahl abgerundete Fehlzeit-Tagesanteil: bleibt 0, bis auch die
letzte Unterrichtsstunde des Tages als versäumt gilt, erst deren Zeile trägt dann den Wert 1.
Im Klassenlehrer-Tab (Nutzer-Feedback) **pro Schüler*in und Tag gruppiert** dargestellt statt
einer Zeile pro Fehlstunde — `ClassAbsenceDaySummaryRow.GroupByStudentAndDay` fasst Fehlstunden/
-minuten zusammen und sammelt Fächer/Status/Gründe als distinkte Listen. Rein Ansicht, kein
Abgleich/Übernehmen in lokale Anwesenheitsdaten; eigene und fremde Fächer erscheinen bewusst
beide ungefiltert.
**Zurückgestellte Idee:** ein Detail-Drill-down pro Schüler*in (z. B. Heatmap der gefehlten
Einzelstunden, Wochenansicht mit Verspätung/vorzeitigem Verlassen) — noch offen, ob im selben Tab
oder über einen eigenen Tab/Dialog "in die Tiefe" erreichbar; die Rohdaten
(`UntisClassAbsenceEntryDto`) sind dafür schon vollständig vorhanden, nur noch nicht in der UI
genutzt.
`ClassAbsenceDaySummaryRow.GroupByStudentAndDay` fasst die Fehlstunden pro Schüler*in/Tag zusammen
(Fächer/Status/Gründe als distinkte Listen) statt eine Zeile pro Fehlstunde zu zeigen.
- [x] **"Klassenlehrer"-Feature — Neuausrichtung (August 2026):** Die erste Umsetzung hing das
Feature an ein `LearningGroup.IsClassTeacher`-Flag mit Tab in der Gruppendetailansicht.
Nutzer-Feedback: konzeptionell falsch — Klassenlehrer ist man für eine ganze Klasse, unabhängig
vom eigenen Unterricht (die Checkbox saß z. B. auch an "SOL"/"Klassenrat"), und die Übersicht wird
gerade an Tagen gebraucht, an denen man selbst keinen Unterricht mit der Klasse hat.
`LearningGroup.IsClassTeacher` wieder entfernt. Stattdessen:
- **Klassenauswahl in den Einstellungen** statt an einer Gruppe:
`WebUntisSettingsService.HomeroomClassUntisId`/`HomeroomClassName` (unverschlüsselt, gleiches
Muster wie `TeacherUntisId`), Auswahl im "Untis-Einbettung"-Tab über den bestehenden
`WebUntisClassSelectionViewModel`/`WebUntisClassSelectionDialog` (bisher nur für den
WebUntis-Klassenimport in der Kursansicht genutzt).
- **Eigener Top-Level-Bereich** "Klassenlehrer" in der Seitenleiste (`NavItem.ClassTeacher`,
`ClassTeacherOverviewViewModel`) statt Tab in einer `LearningGroup`-Detailansicht — sichtbar und
ladbar unabhängig davon, ob heute eigener Unterricht mit der Klasse stattfindet. Zeigt bei
fehlender Klassenauswahl einen Hinweis mit Sprung in die Einstellungen.
- **Zwei Ebenen** (Nutzer-Feedback: "auf die Schnelle" statt Rohdaten-Listen als Erstes):
"Übersicht" — kompaktes Roster (`ClassTeacherRosterRow.Build`, Zuordnung primär über `ExternKey`
für die Fehlzeiten, Namens-Fallback für den Klassenbuch-Bericht ohne `ExternKey`) mit
Ampel-Symbol "keine gemeldete Fehlzeit heute laut WebUntis" (unabhängig von eigener
Anwesenheitserfassung) und Badge bei Klassenbucheinträgen anderer Lehrkräfte in den letzten 7
Tagen; "Details" — die bisherigen Rohdaten-Listen (jetzt `ClassTeacherDetailsViewModel`/
`ClassTeacherDetailsView`, aus `ViewModels/Groups`/`Views/Groups` nach
`ViewModels/ClassTeacher`/`Views/ClassTeacher` verschoben, `Initialize(string className)` statt
`Initialize(Guid groupId)`), aus der Übersicht per Klick auf eine Zeile gefiltert auf
eine/n Schüler*in anspringbar (`ClassTeacherDetailsViewModel.StudentFilter`).
**Zurückgestellte Ideen:** Verknüpfung Roster-Zeile ↔ lokaler `Student`/Navigation zum
Schülerdetail; ein Detail-Drill-down pro Schüler*in (Heatmap gefehlter Einzelstunden,
Verspätung/vorzeitiges Verlassen je Woche); eine KI-gestützte Zusammenfassung/Bewertung der
Einträge (pro Schüler*in oder gesamt) mit Namens-Anonymisierung/Synonymen vor dem Versand.
### 4.4 Wochen-/Tagesansicht
- [x] **4.4.1** Kalenderansicht über alle Gruppen: Woche und Tag — siehe Nachtrag zu 4.3