WIP (unstable): Klassenlehrer-Tab (fremde Klassenbucheinträge + Fehlzeiten der Klasse)

Neuer Tab in der Gruppendetailansicht (LearningGroup.IsClassTeacher-Flag), zeigt
Klassenbucheinträge anderer Lehrkräfte sowie Fehlzeiten der ganzen Klasse über alle
Fächer, pro Schüler*in und Tag zusammengefasst. Beides rein lesend, kein Übernehmen
in lokale Daten. Fehlzeiten-Bericht (AbsencePerStudent) adressiert die Klasse über
"KL"+getKlassen-ID, automatisch aufgelöst - 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 21:19:39 +02:00
co-authored by Claude Sonnet 5
parent f9f5f75aa8
commit 7245196689
17 changed files with 649 additions and 7 deletions
+29
View File
@@ -175,6 +175,35 @@ public sealed class WebUntisClient : IAsyncDisposable
return WebUntisClassRegisterEventReportParser.Parse(reportText);
}, cancellationToken);
// Undokumentierter Bericht "AbsencePerStudent" (Unterricht -> Berichte -> Fehlzeiten der ganzen
// Klasse), Gegenstück zu GetLessonAbsencesAsync für das "Klassenlehrer"-Feature: deckt alle
// Fächer der Klasse ab, nicht nur den eigenen Unterricht. Adressiert die Klasse über
// klasseOrStudentgroupId = "KL" + getKlassen-ID (Auflösung siehe
// WebUntisIntegrationService.ResolveClassIdAsync) statt einer lsid. Query-Parameter 1:1 aus
// einem echten Browser-Request übernommen (identisch mit GetClassRegisterEventsReportAsync bis
// auf name/klasseOrStudentgroupId/studentId; _csrf/selectedDateRange dort schon als nicht nötig
// bekannt).
public Task<IReadOnlyList<UntisClassAbsenceEntry>> GetClassAbsencesAsync(string klasseOrStudentgroupId,
int startDate, int endDate, CancellationToken cancellationToken) => WithSessionAsync(async sessionId =>
{
var query = "name=AbsencePerStudent&format=csv" +
$"&klasseOrStudentgroupId={Uri.EscapeDataString(klasseOrStudentgroupId)}&studentId=-1" +
"&calendarChange=0&withAbsences=true&_withAbsences=on&withLateness=true&_withLateness=on" +
"&excludeNotCountingAbsences=true&_excludeNotCountingAbsences=on&_allStudents=on" +
"&absStudGroup=1&studExcuseStatusId=-1&_withoutPageBreaksAbsPerStudent=on&absClassGroup=1" +
"&classExcuseStatusId=-1&_absClassSorted=on&_separateAbsentDays=on&_filterAbsencesKlasse=on" +
"&absSubjectGroup=7&absSubjectId=-1&absSubjectGroupGroup=7&subjectGroupId=-1&_totalDays=on" +
"&absentPeriodsLimit=16&reportWeeks=4&dayLimit=3&excuseGroup=1&_groupPerWeek=on" +
"&_excuseStatusAll=on&studEventReasonId=-1&classEventReasonId=-1&_evntClassSorted=on" +
"&_withoutPageBreaks=on&withStudentAbsences=true&_withStudentAbsences=on" +
"&examinationTypeId=-1&teachingMethodId=-1&examTypeId=-1&_usingMarkNames=on" +
$"&rpt_sd={startDate}&rpt_ed={endDate}&rpt_syid=-1&rpt_drdtype=CUSTOM";
var reportData = await RequestReportAsync(sessionId, query, cancellationToken)
?? await PollReportAsync(sessionId, cancellationToken);
var reportText = await FetchReportTextAsync(sessionId, reportData, cancellationToken);
return WebUntisClassAbsenceParser.Parse(reportText);
}, cancellationToken);
public Task<IReadOnlyList<UntisSubstitution>> GetSubstitutionsAsync(int startDate, int endDate,
int? departmentId, CancellationToken cancellationToken) => WithSessionAsync(async sessionId =>
{