Files
LehrerApp/LehrerApp.Desktop/Services/UntisLessonAbsenceHelper.cs
T
admin 1291b24478
CI / build-and-test (push) Canceled after 0s
Untis-Fehlzeiten und Dashboard-Hinweise verfeinern
2026-09-25 12:09:32 +02:00

63 lines
3.8 KiB
C#

using LehrerApp.Core.Importing;
using LehrerApp.Core.Models;
namespace LehrerApp.Desktop.Services;
/// <summary>
/// Aus <c>WebUntisLessonAbsenceComparisonViewModel</c> herausgelöst (Nachtrag: die MCP-Tools in
/// <c>UntisComparisonTools</c> brauchen exakt dieselbe Regel für dieselbe Fehlzeile, siehe TODO.md) -
/// framework-frei (kein ObservableObject/Avalonia-Bezug), damit beide Aufrufer ohne Duplikat
/// garantiert denselben Statusvorschlag berechnen. Eine Abweichung zwischen Dialog und MCP-Tool für
/// dieselbe WebUntis-Zeile wäre verwirrender als die eine zusätzliche Indirektion hier.
/// </summary>
public static class UntisLessonAbsenceHelper
{
private const int FullLessonMinutes = 45;
// Bewusst zentral gehalten: Die Einstufung ist nur eine überprüfbare Vorauswahl und kann hier
// leicht an eine geänderte schulische Praxis angepasst werden.
public const int SignificantLatenessThresholdMinutes = 10;
// Der Bericht liefert keinen Entschuldigungstext, nur Minutenwerte, ein Bearbeitet-Datum und die
// (laut Schule) über Klammerung der ENr codierte Entscheidung des Klassenlehrers - ENr in
// Klammern bedeutet unentschuldigt, ohne Klammern abgeschlossen/entschuldigt. Reihenfolge ist
// wichtig: "nach Hause entlassen" zählt immer als vorzeitige Entlassung, unabhängig von der
// Dauer; darunter zählt jede Fehlzeit unter einer vollen Stunde (45 Min.) immer als Verspätung
// oder sonstiger Teilverlust, nie als komplette Abwesenheit. Ab zehn Minuten wird für die
// überprüfbare Vorauswahl die lokale Präzisierung "erheblich verspätet" verwendet. Der Text
// "Verspätung" allein ist laut Schule nicht zuverlässig genug, deshalb primär über die
// Minutenschwellen erkannt.
public static AttendanceStatus MapStatus(UntisLessonAbsenceDto absence)
{
if (IsEarlyRelease(absence)) return AttendanceStatus.LeftDuringClass;
if (absence.AbsentMinutes < SignificantLatenessThresholdMinutes) return AttendanceStatus.Late;
if (absence.AbsentMinutes < FullLessonMinutes) return AttendanceStatus.SignificantlyLate;
if (string.IsNullOrWhiteSpace(absence.HandledOn)) return AttendanceStatus.ExcusePending;
if (absence.ExternKey is null) return AttendanceStatus.ExcusePending;
return absence.ExternKeyInParentheses ? AttendanceStatus.Unexcused : AttendanceStatus.Excused;
}
/// <summary>
/// Prüft, ob der lokale Status die Aussage des WebUntis-Status bereits erfüllt. WebUntis kennt
/// nur die allgemeine Kategorie "verspätet"; die lokale Kategorie "erheblich verspätet" ist
/// eine bewusst genauere Einstufung und darf deshalb nicht auf "verspätet" zurückgestuft
/// werden. Die Regel ist absichtlich gerichtet: Ergibt die Minutenschwelle umgekehrt eine
/// erhebliche Verspätung, bleibt die Hochstufung von lokal "verspätet" erkennbar. Andere
/// Änderungen (z.B. eine nachgereichte Entschuldigung) bleiben echte Abweichungen.
/// </summary>
public static bool MatchesUntisStatus(AttendanceStatus? localStatus, AttendanceStatus untisStatus) =>
localStatus == untisStatus ||
localStatus == AttendanceStatus.SignificantlyLate && untisStatus == AttendanceStatus.Late;
private static bool IsEarlyRelease(UntisLessonAbsenceDto absence) =>
absence.Reason?.Contains("entlassen", StringComparison.OrdinalIgnoreCase) == true;
/// Erste Wahl zum Zuordnen einer WebUntis-Fehlzeile: die externe Schülernummer (ENr), sofern der
/// Schüler eine hat (z.B. nicht bei manuell statt per WebUntis-Import angelegten Schülern).
public static int? StudentExternKey(Student student)
{
student.ExternalIds ??= [];
return student.ExternalIds.TryGetValue(StudentImportFormats.MasterDataCsv.Value, out var value)
&& int.TryParse(value, out var key) ? key : null;
}
}