Fix early departure classification and calendar markers
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
This commit is contained in:
1 parent
a3cd57e5d8
commit
97eb41a0ea
9 files changed
+72
-19
No files matched your search
@@ -9,6 +9,38 @@ namespace LehrerApp.Desktop.Tests;
|
|||||||
|
|
||||||
public sealed class ClassTeacherViewModelsTests
|
public sealed class ClassTeacherViewModelsTests
|
||||||
{
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Früher gegangen", 2, 90)]
|
||||||
|
[InlineData("Vorzeitig verlassen", 0, 20)]
|
||||||
|
public void FruehesGehen_IstTeilfehlzeitUndImKalenderGesondertErkennbar(string reason, int periods, int minutes)
|
||||||
|
{
|
||||||
|
var date = new DateOnly(2026, 9, 3);
|
||||||
|
var row = new ClassAbsenceDaySummaryRow(date, "Müller Ada", 1, periods, minutes,
|
||||||
|
[], [], ["nicht entsch."], [reason], null, null, true);
|
||||||
|
|
||||||
|
Assert.True(row.IsEarlyDeparture);
|
||||||
|
Assert.False(row.CountsAsFullDay);
|
||||||
|
Assert.False(row.IsLate);
|
||||||
|
Assert.True(row.IsPartialAbsence);
|
||||||
|
var marker = Assert.Single(ClassTeacherDetailsViewModel.BuildDayEvents(date, [row], [], false));
|
||||||
|
Assert.Equal("G", marker.Code);
|
||||||
|
Assert.Equal("#C62828", marker.ColorHex);
|
||||||
|
Assert.Contains("Früher gegangen", marker.Tooltip);
|
||||||
|
Assert.Contains("Unentschuldigt", marker.Tooltip);
|
||||||
|
Assert.DoesNotContain("Ganzer Fehltag", marker.Tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Kalender_GanzeFehltageBehaltenEigenesKuerzel()
|
||||||
|
{
|
||||||
|
var date = new DateOnly(2026, 9, 3);
|
||||||
|
var row = new ClassAbsenceDaySummaryRow(date, "Müller Ada", 1, 6, 270,
|
||||||
|
[], [], ["nicht entsch."], ["Absent"], null, null, true);
|
||||||
|
var marker = Assert.Single(ClassTeacherDetailsViewModel.BuildDayEvents(date, [row], [], false));
|
||||||
|
Assert.Equal("U", marker.Code);
|
||||||
|
Assert.Contains("Ganzer Fehltag", marker.Tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Monatskalender_KodiertFehlzeitenUndBlendetHausaufgabenStandardmaessigAus()
|
public void Monatskalender_KodiertFehlzeitenUndBlendetHausaufgabenStandardmaessigAus()
|
||||||
{
|
{
|
||||||
@@ -32,7 +64,7 @@ public sealed class ClassTeacherViewModelsTests
|
|||||||
|
|
||||||
Assert.Equal(0, days.Count % 5);
|
Assert.Equal(0, days.Count % 5);
|
||||||
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "V");
|
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "V");
|
||||||
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "U");
|
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "T");
|
||||||
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "!");
|
Assert.Contains(days.SelectMany(d => d.Events), e => e.Code == "!");
|
||||||
Assert.DoesNotContain(days.SelectMany(d => d.Events), e => e.Code == "H");
|
Assert.DoesNotContain(days.SelectMany(d => d.Events), e => e.Code == "H");
|
||||||
|
|
||||||
@@ -65,10 +97,10 @@ public sealed class ClassTeacherViewModelsTests
|
|||||||
Assert.Equal(30, days.Count);
|
Assert.Equal(30, days.Count);
|
||||||
Assert.False(days[0].HasSignal);
|
Assert.False(days[0].HasSignal);
|
||||||
Assert.Equal("1", days[0].DayNumber);
|
Assert.Equal("1", days[0].DayNumber);
|
||||||
Assert.Equal("U", days[2].SignalCode); // stärker als der parallele Klassenbucheintrag
|
Assert.Equal("T", days[2].SignalCode); // stärker als der parallele Klassenbucheintrag
|
||||||
Assert.True(days[2].IsToday);
|
Assert.True(days[2].IsToday);
|
||||||
Assert.Contains("Ada Müller", days[2].Tooltip);
|
Assert.Contains("Ada Müller", days[2].Tooltip);
|
||||||
Assert.Equal("E", days[3].SignalCode);
|
Assert.Equal("T", days[3].SignalCode);
|
||||||
Assert.False(days[4].HasSignal); // Hausaufgaben bleiben im Widget bewusst ausgeblendet
|
Assert.False(days[4].HasSignal); // Hausaufgaben bleiben im Widget bewusst ausgeblendet
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +119,7 @@ public sealed class ClassTeacherViewModelsTests
|
|||||||
var days = ClassTeacherOverviewViewModel.BuildCompactMonthDays(month, absences, [], month,
|
var days = ClassTeacherOverviewViewModel.BuildCompactMonthDays(month, absences, [], month,
|
||||||
"Ada Müller");
|
"Ada Müller");
|
||||||
|
|
||||||
Assert.Equal("U", days[2].SignalCode);
|
Assert.Equal("T", days[2].SignalCode);
|
||||||
Assert.False(days[3].HasSignal);
|
Assert.False(days[3].HasSignal);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +139,7 @@ public sealed class ClassTeacherViewModelsTests
|
|||||||
|
|
||||||
Assert.True(drawing.ContentHeight > 0);
|
Assert.True(drawing.ContentHeight > 0);
|
||||||
Assert.Contains(drawing.Commands.OfType<DrawStringEx>(), c => c.Text == "Ada Müller");
|
Assert.Contains(drawing.Commands.OfType<DrawStringEx>(), c => c.Text == "Ada Müller");
|
||||||
Assert.Contains(drawing.Commands.OfType<DrawStringEx>(), c => c.Text == "U");
|
Assert.Contains(drawing.Commands.OfType<DrawStringEx>(), c => c.Text == "T");
|
||||||
Assert.DoesNotContain(drawing.Commands.OfType<DrawStringEx>(), c => c.Text == "E");
|
Assert.DoesNotContain(drawing.Commands.OfType<DrawStringEx>(), c => c.Text == "E");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ public static class StudentAttendanceCalendarDrawingBuilder
|
|||||||
y = gridStartY + 11 * k + 9 * k + maxWeeks * cellHeight + designTrailingGapY * k;
|
y = gridStartY + 11 * k + 9 * k + maxWeeks * cellHeight + designTrailingGapY * k;
|
||||||
|
|
||||||
commands.Add(new DrawStringEx(0, y, 8 * k, contentWidthDesign,
|
commands.Add(new DrawStringEx(0, y, 8 * k, contentWidthDesign,
|
||||||
"U unentschuldigt · A abwesend · V verspätet · E entschuldigt · ! Klassenbuch",
|
"U/E/A ganztägig · T teilweise · G gegangen · V verspätet · ! Klassenbuch",
|
||||||
DrawingTextAlignment.AlignLeft, 6.5f * finalScale, Color: "#6B7280"));
|
DrawingTextAlignment.AlignLeft, 6.5f * finalScale, Color: "#6B7280"));
|
||||||
return new DrawingValue(commands, y + 9 * k);
|
return new DrawingValue(commands, y + 9 * k);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public sealed class UntisReportCacheService(
|
|||||||
{
|
{
|
||||||
private static readonly TimeSpan HotWindowRefreshInterval = TimeSpan.FromHours(1);
|
private static readonly TimeSpan HotWindowRefreshInterval = TimeSpan.FromHours(1);
|
||||||
private const int HotWindowDays = 14;
|
private const int HotWindowDays = 14;
|
||||||
private const int AbsenceReportVersion = 1;
|
private const int AbsenceReportVersion = 2;
|
||||||
|
|
||||||
/// Roster hat anders als Fehlzeiten/Klassenbuch keine Historie (nur "aktueller Stand einer
|
/// Roster hat anders als Fehlzeiten/Klassenbuch keine Historie (nur "aktueller Stand einer
|
||||||
/// Klasse") - deshalb ohne heißes/kaltes Fenster, nur "gilt der letzte Abruf noch als frisch".
|
/// Klasse") - deshalb ohne heißes/kaltes Fenster, nur "gilt der letzte Abruf noch als frisch".
|
||||||
|
|||||||
@@ -66,6 +66,17 @@ public sealed record ClassAbsenceDaySummaryRow(DateOnly Date, string StudentName
|
|||||||
IReadOnlyList<int> PeriodNumbers, IReadOnlyList<string> Statuses, IReadOnlyList<string> AbsenceReasons,
|
IReadOnlyList<int> PeriodNumbers, IReadOnlyList<string> Statuses, IReadOnlyList<string> AbsenceReasons,
|
||||||
string? Note, string? ExcuseNote, bool CountsAsFullDay)
|
string? Note, string? ExcuseNote, bool CountsAsFullDay)
|
||||||
{
|
{
|
||||||
|
public bool CountsAsFullDay { get; init; } = CountsAsFullDay && !HasEarlyDepartureReason(AbsenceReasons);
|
||||||
|
public bool IsEarlyDeparture => HasEarlyDepartureReason(AbsenceReasons);
|
||||||
|
|
||||||
|
private static bool HasEarlyDepartureReason(IEnumerable<string> reasons) => reasons.Any(reason =>
|
||||||
|
reason.Contains("vorzeitig", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
((reason.Contains("früh", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
reason.Contains("frueh", StringComparison.OrdinalIgnoreCase)) &&
|
||||||
|
(reason.Contains("geh", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
reason.Contains("gegang", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
reason.Contains("verlass", StringComparison.OrdinalIgnoreCase))));
|
||||||
|
|
||||||
/// Nur für die Gewichtung von stundenweisen Fehlzeiten im Auffälligkeitsscore. Zeugnis- und
|
/// Nur für die Gewichtung von stundenweisen Fehlzeiten im Auffälligkeitsscore. Zeugnis- und
|
||||||
/// Anzeigezahlen verwenden weiterhin die echten WebUntis-Felder CountsAsFullDay bzw.
|
/// Anzeigezahlen verwenden weiterhin die echten WebUntis-Felder CountsAsFullDay bzw.
|
||||||
/// TotalAbsentPeriods und rechnen keine Stunden künstlich in Fehltage um.
|
/// TotalAbsentPeriods und rechnen keine Stunden künstlich in Fehltage um.
|
||||||
@@ -84,8 +95,8 @@ public sealed record ClassAbsenceDaySummaryRow(DateOnly Date, string StudentName
|
|||||||
public string PeriodsLabel => string.Join(", ", PeriodNumbers.Order());
|
public string PeriodsLabel => string.Join(", ", PeriodNumbers.Order());
|
||||||
public string StatusLabel => string.Join(", ", Statuses);
|
public string StatusLabel => string.Join(", ", Statuses);
|
||||||
public string ReasonLabel => string.Join("; ", AbsenceReasons);
|
public string ReasonLabel => string.Join("; ", AbsenceReasons);
|
||||||
public bool IsLate => TotalAbsentPeriods == 0 || AbsenceReasons.Any(r =>
|
public bool IsLate => !IsEarlyDeparture && (TotalAbsentPeriods == 0 || AbsenceReasons.Any(r =>
|
||||||
r.Contains("verspät", StringComparison.OrdinalIgnoreCase));
|
r.Contains("verspät", StringComparison.OrdinalIgnoreCase)));
|
||||||
public bool IsUnexcused => Statuses.Any(s =>
|
public bool IsUnexcused => Statuses.Any(s =>
|
||||||
s.Contains("nicht entsch", StringComparison.OrdinalIgnoreCase));
|
s.Contains("nicht entsch", StringComparison.OrdinalIgnoreCase));
|
||||||
public bool IsPartialAbsence => !CountsAsFullDay && (!IsLate || IsUnexcused);
|
public bool IsPartialAbsence => !CountsAsFullDay && (!IsLate || IsUnexcused);
|
||||||
@@ -93,7 +104,9 @@ public sealed record ClassAbsenceDaySummaryRow(DateOnly Date, string StudentName
|
|||||||
Math.Min(1d, (TotalAbsentPeriods > 0
|
Math.Min(1d, (TotalAbsentPeriods > 0
|
||||||
? TotalAbsentPeriods
|
? TotalAbsentPeriods
|
||||||
: TotalAbsentMinutes / 45d) / TypicalPeriodsPerSchoolDayForScoring);
|
: TotalAbsentMinutes / 45d) / TypicalPeriodsPerSchoolDayForScoring);
|
||||||
public string KindLabel => IsLate ? $"{TotalAbsentMinutes} Min. verspätet" :
|
public string KindLabel => IsEarlyDeparture
|
||||||
|
? $"Früher gegangen · {(TotalAbsentPeriods > 0 ? $"{TotalAbsentPeriods} Fehlstunde{(TotalAbsentPeriods == 1 ? "" : "n")}" : $"{TotalAbsentMinutes} Fehlminuten")}" :
|
||||||
|
IsLate ? $"{TotalAbsentMinutes} Min. verspätet" :
|
||||||
CountsAsFullDay ? "Ganzer Fehltag" : $"{TotalAbsentPeriods} Fehlstunde{(TotalAbsentPeriods == 1 ? "" : "n")}";
|
CountsAsFullDay ? "Ganzer Fehltag" : $"{TotalAbsentPeriods} Fehlstunde{(TotalAbsentPeriods == 1 ? "" : "n")}";
|
||||||
public string FriendlyStatusLabel => IsUnexcused ? "Unentschuldigt" :
|
public string FriendlyStatusLabel => IsUnexcused ? "Unentschuldigt" :
|
||||||
Statuses.Any(s => s.Contains("entsch", StringComparison.OrdinalIgnoreCase)) ? "Entschuldigt" : StatusLabel;
|
Statuses.Any(s => s.Contains("entsch", StringComparison.OrdinalIgnoreCase)) ? "Entschuldigt" : StatusLabel;
|
||||||
@@ -410,6 +423,13 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
|
|||||||
: absence.FriendlyStatusLabel.Contains("Entschuldigt", StringComparison.OrdinalIgnoreCase)
|
: absence.FriendlyStatusLabel.Contains("Entschuldigt", StringComparison.OrdinalIgnoreCase)
|
||||||
? ("E", "Entschuldigt gefehlt", "#2E7D32", ClassTeacherCalendarEventKind.Excused)
|
? ("E", "Entschuldigt gefehlt", "#2E7D32", ClassTeacherCalendarEventKind.Excused)
|
||||||
: ("A", "Abwesend", "#1565C0", ClassTeacherCalendarEventKind.Absent);
|
: ("A", "Abwesend", "#1565C0", ClassTeacherCalendarEventKind.Absent);
|
||||||
|
// Umfang und Entschuldigungsstatus sind unabhängig: Rot kann auch zwei
|
||||||
|
// unentschuldigte Fehlstunden bedeuten, nicht zwingend einen ganzen Fehltag.
|
||||||
|
if (absence.IsPartialAbsence && !absence.IsLate)
|
||||||
|
code = absence.IsEarlyDeparture ? "G" : "T";
|
||||||
|
else if (absence.IsLate)
|
||||||
|
code = "V";
|
||||||
|
label = $"{absence.KindLabel} · {absence.FriendlyStatusLabel}";
|
||||||
events.Add(new ClassTeacherCalendarEvent(absence.StudentDisplayName, code, label, color, kind));
|
events.Add(new ClassTeacherCalendarEvent(absence.StudentDisplayName, code, label, color, kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,11 +50,7 @@ public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, b
|
|||||||
{
|
{
|
||||||
if (TodayAbsence is null)
|
if (TodayAbsence is null)
|
||||||
return HasRecentClassRegisterEntry ? "Neuer Klassenbucheintrag" : "Keine gemeldete Fehlzeit";
|
return HasRecentClassRegisterEntry ? "Neuer Klassenbucheintrag" : "Keine gemeldete Fehlzeit";
|
||||||
var core = IsLate
|
var core = TodayAbsence.KindLabel;
|
||||||
? $"{TodayAbsence.TotalAbsentMinutes} Min. verspätet"
|
|
||||||
: TodayAbsence.CountsAsFullDay
|
|
||||||
? "Ganzer Fehltag"
|
|
||||||
: $"{TodayAbsence.TotalAbsentPeriods} Fehlstunde{(TodayAbsence.TotalAbsentPeriods == 1 ? "" : "n")}";
|
|
||||||
return IsUnexcused ? $"{core} · Unentschuldigt" : core;
|
return IsUnexcused ? $"{core} · Unentschuldigt" : core;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,7 +432,7 @@
|
|||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
AutomationProperties.Name="Schüler für Monatskalender auswählen"/>
|
AutomationProperties.Name="Schüler für Monatskalender auswählen"/>
|
||||||
<shared:CompactMonthCalendar Days="{Binding CompactMonthDays}"/>
|
<shared:CompactMonthCalendar Days="{Binding CompactMonthDays}"/>
|
||||||
<TextBlock Text="U unentsch. · ! Klassenbuch · A abwesend · V verspätet · E entsch."
|
<TextBlock Text="U/E/A ganzer Fehltag · T Teilfehlzeit · G früher gegangen · V verspätet · ! Klassenbuch. Rot: unentschuldigt."
|
||||||
FontSize="9" Opacity="0.5" TextWrapping="Wrap"/>
|
FontSize="9" Opacity="0.5" TextWrapping="Wrap"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|||||||
@@ -293,7 +293,6 @@ public sealed class WebUntisClientTests
|
|||||||
Assert.Contains("reports.do?name=ClassregEventPerStudent", handler.Requests[1].Uri);
|
Assert.Contains("reports.do?name=ClassregEventPerStudent", handler.Requests[1].Uri);
|
||||||
Assert.Contains("klasseOrStudentgroupId=-1", handler.Requests[1].Uri);
|
Assert.Contains("klasseOrStudentgroupId=-1", handler.Requests[1].Uri);
|
||||||
Assert.Contains("studentId=-1", handler.Requests[1].Uri);
|
Assert.Contains("studentId=-1", handler.Requests[1].Uri);
|
||||||
Assert.Contains("&separateAbsentDays=true&", handler.Requests[1].Uri);
|
|
||||||
Assert.Contains("rpt_sd=20260824", handler.Requests[1].Uri);
|
Assert.Contains("rpt_sd=20260824", handler.Requests[1].Uri);
|
||||||
Assert.Contains("rpt_ed=20260828", handler.Requests[1].Uri);
|
Assert.Contains("rpt_ed=20260828", handler.Requests[1].Uri);
|
||||||
Assert.EndsWith("reports.do?get=rpt2.tmp&name=ClassregEventPerStudent&format=csv", handler.Requests[2].Uri);
|
Assert.EndsWith("reports.do?get=rpt2.tmp&name=ClassregEventPerStudent&format=csv", handler.Requests[2].Uri);
|
||||||
@@ -341,6 +340,7 @@ public sealed class WebUntisClientTests
|
|||||||
Assert.Contains("reports.do?name=AbsencePerStudent", handler.Requests[1].Uri);
|
Assert.Contains("reports.do?name=AbsencePerStudent", handler.Requests[1].Uri);
|
||||||
Assert.Contains("klasseOrStudentgroupId=KL874", handler.Requests[1].Uri);
|
Assert.Contains("klasseOrStudentgroupId=KL874", handler.Requests[1].Uri);
|
||||||
Assert.Contains("studentId=-1", handler.Requests[1].Uri);
|
Assert.Contains("studentId=-1", handler.Requests[1].Uri);
|
||||||
|
Assert.Contains("&separateAbsentDays=true&", handler.Requests[1].Uri);
|
||||||
Assert.Contains("rpt_sd=20260824", handler.Requests[1].Uri);
|
Assert.Contains("rpt_sd=20260824", handler.Requests[1].Uri);
|
||||||
Assert.Contains("rpt_ed=20260828", handler.Requests[1].Uri);
|
Assert.Contains("rpt_ed=20260828", handler.Requests[1].Uri);
|
||||||
Assert.EndsWith("reports.do?get=rpt3.tmp&name=AbsencePerStudent&format=csv", handler.Requests[2].Uri);
|
Assert.EndsWith("reports.do?get=rpt3.tmp&name=AbsencePerStudent&format=csv", handler.Requests[2].Uri);
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ public sealed partial class WebUntisClient : IAsyncDisposable
|
|||||||
"&calendarChange=0&withAbsences=true&_withAbsences=on&withLateness=true&_withLateness=on" +
|
"&calendarChange=0&withAbsences=true&_withAbsences=on&withLateness=true&_withLateness=on" +
|
||||||
"&excludeNotCountingAbsences=true&_excludeNotCountingAbsences=on&_allStudents=on" +
|
"&excludeNotCountingAbsences=true&_excludeNotCountingAbsences=on&_allStudents=on" +
|
||||||
"&absStudGroup=1&studExcuseStatusId=-1&_withoutPageBreaksAbsPerStudent=on&absClassGroup=1" +
|
"&absStudGroup=1&studExcuseStatusId=-1&_withoutPageBreaksAbsPerStudent=on&absClassGroup=1" +
|
||||||
"&classExcuseStatusId=-1&_absClassSorted=on&separateAbsentDays=true&_separateAbsentDays=on&_filterAbsencesKlasse=on" +
|
"&classExcuseStatusId=-1&_absClassSorted=on&_separateAbsentDays=on&_filterAbsencesKlasse=on" +
|
||||||
"&absSubjectGroup=7&absSubjectId=-1&absSubjectGroupGroup=7&subjectGroupId=-1&_totalDays=on" +
|
"&absSubjectGroup=7&absSubjectId=-1&absSubjectGroupGroup=7&subjectGroupId=-1&_totalDays=on" +
|
||||||
"&absentPeriodsLimit=16&reportWeeks=4&dayLimit=3&excuseGroup=1&_groupPerWeek=on" +
|
"&absentPeriodsLimit=16&reportWeeks=4&dayLimit=3&excuseGroup=1&_groupPerWeek=on" +
|
||||||
"&_excuseStatusAll=on&studEventReasonId=-1&classEventReasonId=-1&_evntClassSorted=on" +
|
"&_excuseStatusAll=on&studEventReasonId=-1&classEventReasonId=-1&_evntClassSorted=on" +
|
||||||
@@ -208,7 +208,7 @@ public sealed partial class WebUntisClient : IAsyncDisposable
|
|||||||
"&calendarChange=0&withAbsences=true&_withAbsences=on&withLateness=true&_withLateness=on" +
|
"&calendarChange=0&withAbsences=true&_withAbsences=on&withLateness=true&_withLateness=on" +
|
||||||
"&excludeNotCountingAbsences=true&_excludeNotCountingAbsences=on&_allStudents=on" +
|
"&excludeNotCountingAbsences=true&_excludeNotCountingAbsences=on&_allStudents=on" +
|
||||||
"&absStudGroup=1&studExcuseStatusId=-1&_withoutPageBreaksAbsPerStudent=on&absClassGroup=1" +
|
"&absStudGroup=1&studExcuseStatusId=-1&_withoutPageBreaksAbsPerStudent=on&absClassGroup=1" +
|
||||||
"&classExcuseStatusId=-1&_absClassSorted=on&_separateAbsentDays=on&_filterAbsencesKlasse=on" +
|
"&classExcuseStatusId=-1&_absClassSorted=on&separateAbsentDays=true&_separateAbsentDays=on&_filterAbsencesKlasse=on" +
|
||||||
"&absSubjectGroup=7&absSubjectId=-1&absSubjectGroupGroup=7&subjectGroupId=-1&_totalDays=on" +
|
"&absSubjectGroup=7&absSubjectId=-1&absSubjectGroupGroup=7&subjectGroupId=-1&_totalDays=on" +
|
||||||
"&absentPeriodsLimit=16&reportWeeks=4&dayLimit=3&excuseGroup=1&_groupPerWeek=on" +
|
"&absentPeriodsLimit=16&reportWeeks=4&dayLimit=3&excuseGroup=1&_groupPerWeek=on" +
|
||||||
"&_excuseStatusAll=on&studEventReasonId=-1&classEventReasonId=-1&_evntClassSorted=on" +
|
"&_excuseStatusAll=on&studEventReasonId=-1&classEventReasonId=-1&_evntClassSorted=on" +
|
||||||
|
|||||||
@@ -1622,6 +1622,11 @@ eigenen Unterricht abfragt und deshalb mit den regulären Lehrkraft-Rechten funk
|
|||||||
Klasse+Fach aus der Zeile scopen (→ genau eine lokale Lerngruppe), nicht global über alle Schüler.
|
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.
|
Abgleichsdialog (Diff gegen `Documentation`, Übernehmen in beide Richtungen) noch nicht gebaut.
|
||||||
- [x] **Klassenlehrer — einzelne Fehlstunden als ganze Fehltage (Oktober 2026):**
|
- [x] **Klassenlehrer — einzelne Fehlstunden als ganze Fehltage (Oktober 2026):**
|
||||||
|
- Nachkorrektur: Der Trennparameter und seine Regression gehören zum Fehlzeitenbericht,
|
||||||
|
nicht zum gleich aufgebauten Klassenbuchbericht. Cacheversion 2 erneuert die alten Abrufe.
|
||||||
|
Explizites früheres Gehen im Abwesenheitsgrund zählt als Teilfehlzeit. Kalender zeigen
|
||||||
|
`G` für früheres Gehen und `T` für sonstige Teilfehlzeiten, mit Umfang/Status im Tooltip;
|
||||||
|
Rot kennzeichnet weiterhin unentschuldigte Fehlzeiten. Ganze Fehltage behalten U/E/A.
|
||||||
Beim Berichtabruf ist `separateAbsentDays=true` jetzt tatsächlich aktiviert; der bisherige
|
Beim Berichtabruf ist `separateAbsentDays=true` jetzt tatsächlich aktiviert; der bisherige
|
||||||
Parameter `_separateAbsentDays=on` allein aktivierte die Checkbox nicht. Alte Fehlzeiten-Caches
|
Parameter `_separateAbsentDays=on` allein aktivierte die Checkbox nicht. Alte Fehlzeiten-Caches
|
||||||
werden über eine Berichtversion neu geladen und auch im historischen Bereich ersetzt.
|
werden über eine Berichtversion neu geladen und auch im historischen Bereich ersetzt.
|
||||||
|
|||||||
Reference in new issue
Block a user