Klassenlehrer-Tab: vier Feature-Ideen umgesetzt (ohne Elterngesprächs-PDF)
Offene Entschuldigungen mit Frist (neue Seitenspalten-Karte, sortiert nach Tagen offen), aggregierte Klassenbuch-Kategorien (Chip-Reihe im Klassenbuch-Tab), Wochentags-Häufung als dritte Mustererkennungsregel, und eine Wiedervorlagen-Verknüpfung: ein "+"-Button legt direkt aus der Zeile eine WorkTask "Eltern kontaktieren" an, und eine dritte Musterregel verknüpft hohe Jahresfehlquote mit fallendem Mitarbeit-Trend. ContextMenu auf den Zeilen-Buttons scheiterte an Avalonias $parent-Vorfahren-Bindings (Popup hängt nicht im normalen visuellen Baum) — stattdessen zwei Geschwister-Buttons pro Zeile. Elterngesprächs-Blatt als PDF bewusst zurückgestellt, Konzept noch nicht geschärft. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -264,6 +264,149 @@ public sealed class ClassTeacherViewModelsTests
|
||||
Assert.Equal(row.StatusText, row.StatusTextWithGlyph);
|
||||
}
|
||||
|
||||
// ── Offene Entschuldigungen (Feature-Idee 2) ─────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void OpenExcuseBuild_SortiertNachTagenOffenAbsteigendUndIgnoriertEntschuldigte()
|
||||
{
|
||||
var absences = new[]
|
||||
{
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 20), "Alt Ada", 1001, 2, 90,
|
||||
["Che"], [1, 2], ["nicht entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 25), "Neu Ben", 1002, 1, 45,
|
||||
["Deu"], [1], ["nicht entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Entschuldigt Cem", 1003, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
};
|
||||
|
||||
var rows = ClassTeacherOpenExcuseRow.Build(absences, new DateOnly(2026, 8, 26));
|
||||
|
||||
// StudentName kommt aus StudentDisplayName (WebUntis liefert "Nachname Vorname",
|
||||
// hier für die Anzeige auf "Vorname Nachname" gedreht - siehe ClassAbsenceDaySummaryRow).
|
||||
Assert.Equal(["Ada Alt", "Ben Neu"], rows.Select(r => r.StudentName));
|
||||
Assert.Equal(6, rows[0].DaysOpen);
|
||||
Assert.True(rows[0].IsOverdue);
|
||||
Assert.Equal(1, rows[1].DaysOpen);
|
||||
Assert.False(rows[1].IsOverdue);
|
||||
}
|
||||
|
||||
// ── Aggregierte Klassenbuch-Kategorien (Feature-Idee 5) ──────────────────
|
||||
|
||||
[Fact]
|
||||
public void CategoryAggregateBuild_ZaehltProKategorieUndNenntTopSchueler()
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
new ClassTeacherClassRegisterRow(new DateOnly(2026, 8, 20), "Deu", "Schmidt Ben", "test",
|
||||
"Hausaufgaben fehlen", "Negativ", "HA fehlt"),
|
||||
new ClassTeacherClassRegisterRow(new DateOnly(2026, 8, 21), "Deu", "Schmidt Ben", "test",
|
||||
"Hausaufgaben fehlen", "Negativ", "HA fehlt"),
|
||||
new ClassTeacherClassRegisterRow(new DateOnly(2026, 8, 22), "Che", "Müller Ada", "test",
|
||||
"Hausaufgaben fehlen", "Negativ", "HA fehlt"),
|
||||
new ClassTeacherClassRegisterRow(new DateOnly(2026, 8, 23), "Che", "Müller Ada", "test",
|
||||
"Lob", "Positiv", "Gute Mitarbeit"),
|
||||
};
|
||||
|
||||
var rows = ClassTeacherCategoryAggregateRow.Build(entries);
|
||||
|
||||
Assert.Equal(["Hausaufgaben fehlen", "Lob"], rows.Select(r => r.CategoryName));
|
||||
var homework = rows[0];
|
||||
Assert.Equal(3, homework.Count);
|
||||
Assert.Contains("2× Ben Schmidt", homework.TopStudentsLabel);
|
||||
Assert.Contains("1× Ada Müller", homework.TopStudentsLabel);
|
||||
}
|
||||
|
||||
// ── Erweiterte Mustererkennung: Wochentags-Häufung (Feature-Idee 4) ──────
|
||||
|
||||
[Fact]
|
||||
public void DetectWeekdayPatterns_ErkenntDeutlicheHaeufungAnEinemWochentag()
|
||||
{
|
||||
// 24.08./31.08.2026 und 07.09.2026 sind allesamt Montage.
|
||||
var absences = new[]
|
||||
{
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 31), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 7), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
};
|
||||
var names = new Dictionary<string, string> { [UntisNameMatching.NameKey("Muster Mona")] = "Mona Muster" };
|
||||
|
||||
var notices = ClassTeacherOverviewViewModel.DetectWeekdayPatterns(
|
||||
absences, names, new HashSet<string>());
|
||||
|
||||
var notice = Assert.Single(notices);
|
||||
Assert.Equal("Mona Muster", notice.StudentName);
|
||||
Assert.Contains("Montag", notice.Message);
|
||||
Assert.Equal(ClassTeacherStatusKind.Info, notice.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetectWeekdayPatterns_IgnoriertZuKleineStichprobe()
|
||||
{
|
||||
var absences = new[]
|
||||
{
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 31), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
};
|
||||
|
||||
var notices = ClassTeacherOverviewViewModel.DetectWeekdayPatterns(
|
||||
absences, new Dictionary<string, string>(), new HashSet<string>());
|
||||
|
||||
Assert.Empty(notices);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DetectWeekdayPatterns_LaesstBereitsGemeldeteSchuelerAus()
|
||||
{
|
||||
var absences = new[]
|
||||
{
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 24), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 8, 31), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 7), "Muster Mona", 1001, 1, 45,
|
||||
["Deu"], [1], ["entsch."], ["Absent"], null, null, false),
|
||||
};
|
||||
var names = new Dictionary<string, string> { [UntisNameMatching.NameKey("Muster Mona")] = "Mona Muster" };
|
||||
|
||||
var notices = ClassTeacherOverviewViewModel.DetectWeekdayPatterns(
|
||||
absences, names, new HashSet<string> { "Mona Muster" });
|
||||
|
||||
Assert.Empty(notices);
|
||||
}
|
||||
|
||||
// ── Noten-/Mitarbeit-Brücke: Namensabgleich (Feature-Idee 7) ─────────────
|
||||
|
||||
[Fact]
|
||||
public void MatchStudent_FindetUeberVorUndNachnameTrotzKommaInFullName()
|
||||
{
|
||||
// Student.FullName liefert "Nachname, Vorname" (mit Komma) - ein Abgleich darüber würde
|
||||
// UntisNameMatching.NameKey verfälschen ("Müller," bliebe eigenes Wort). MatchStudent
|
||||
// muss deshalb FirstName/LastName getrennt verwenden, nicht FullName.
|
||||
var students = new List<Student>
|
||||
{
|
||||
new() { FirstName = "Ada", LastName = "Müller" },
|
||||
new() { FirstName = "Ben", LastName = "Schmidt" },
|
||||
};
|
||||
|
||||
var match = ClassTeacherOverviewViewModel.MatchStudent("Müller Ada", students);
|
||||
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal("Ada", match!.FirstName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MatchStudent_LiefertNullOhneTreffer()
|
||||
{
|
||||
var students = new List<Student> { new() { FirstName = "Ada", LastName = "Müller" } };
|
||||
|
||||
Assert.Null(ClassTeacherOverviewViewModel.MatchStudent("Unbekannt Xyz", students));
|
||||
}
|
||||
|
||||
private static UntisStudentRosterCacheEntry Student(int? externKey, string displayName) =>
|
||||
new() { ClassName = "6a", ExternKey = externKey, DisplayName = displayName };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user