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:
@@ -0,0 +1,65 @@
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class ClassTeacherViewModelsTests
|
||||
{
|
||||
[Fact]
|
||||
public void GroupByStudentAndDay_FasstFehlstundenProSchuelerUndTagZusammen()
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260824, 1, 45, "HED", "Che", "Absent",
|
||||
"Krank", 1, "24.08.26", true, null, 3, "nicht entsch.", false),
|
||||
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260824, 1, 45, "PAN", "Kunst", null,
|
||||
"Krank", 2, "24.08.26", true, null, 5, "nicht entsch.", true),
|
||||
new UntisClassAbsenceEntryDto("Andere Schuelerin", 2, "6a", 20260824, 1, 20, "BEN", "Deu",
|
||||
"Verspätung", null, 3, "24.08.26", false, "Zug verpasst", 1, "entsch.", false),
|
||||
};
|
||||
|
||||
var rows = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(entries);
|
||||
|
||||
Assert.Equal(2, rows.Count);
|
||||
|
||||
var max = rows[1];
|
||||
Assert.Equal("Muster Max", max.StudentName);
|
||||
Assert.Equal(2, max.TotalAbsentPeriods);
|
||||
Assert.Equal(90, max.TotalAbsentMinutes);
|
||||
Assert.Equal(["Che", "Kunst"], max.Subjects);
|
||||
Assert.Equal([3, 5], max.PeriodNumbers.Order());
|
||||
Assert.Equal(["nicht entsch."], max.Statuses);
|
||||
Assert.Equal(["Absent"], max.AbsenceReasons);
|
||||
Assert.Equal("Krank", max.Note);
|
||||
Assert.Null(max.ExcuseNote);
|
||||
Assert.True(max.CountsAsFullDay);
|
||||
|
||||
var andere = rows[0];
|
||||
Assert.Equal("Andere Schuelerin", andere.StudentName);
|
||||
Assert.Equal(1, andere.TotalAbsentPeriods);
|
||||
Assert.Equal(20, andere.TotalAbsentMinutes);
|
||||
Assert.Equal(["Deu"], andere.Subjects);
|
||||
Assert.Null(andere.Note);
|
||||
Assert.Equal("Zug verpasst", andere.ExcuseNote);
|
||||
Assert.False(andere.CountsAsFullDay);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GroupByStudentAndDay_TrenntGleichenNamenAnUnterschiedlichenTagen()
|
||||
{
|
||||
var entries = new[]
|
||||
{
|
||||
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260824, 1, 45, "HED", "Che", null,
|
||||
null, 1, "24.08.26", true, null, 3, "nicht entsch.", false),
|
||||
new UntisClassAbsenceEntryDto("Muster Max", 1, "6a", 20260825, 1, 45, "HED", "Che", null,
|
||||
null, 2, "25.08.26", true, null, 3, "nicht entsch.", false),
|
||||
};
|
||||
|
||||
var rows = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(entries);
|
||||
|
||||
Assert.Equal(2, rows.Count);
|
||||
Assert.Equal(new DateOnly(2026, 8, 25), rows[0].Date);
|
||||
Assert.Equal(new DateOnly(2026, 8, 24), rows[1].Date);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
@@ -29,7 +30,9 @@ public sealed class GroupDetailViewModelTests
|
||||
new FakeCompetencyDomains(), students, new CompetencyAnalysisService()),
|
||||
new SeatingPlanTabViewModel(new FakeSeatingPlans(), students, memberships,
|
||||
new FakeSessions([]), new FakeEntries(), new FakeAspects()),
|
||||
new GroupDocumentationTabViewModel(new FakeDocumentation(), students, groups));
|
||||
new GroupDocumentationTabViewModel(new FakeDocumentation(), students, groups),
|
||||
new ClassTeacherTabViewModel(
|
||||
new WebUntisIntegrationService(new HttpClient(), TestSupport.BuildWebUntisSettingsService()), groups));
|
||||
|
||||
vm.LoadGroup(group.Id);
|
||||
vm.SelectedExam = vm.Exams.First(e => e.Id == exam.Id);
|
||||
|
||||
@@ -30,6 +30,41 @@ public sealed class WebUntisIntegrationServiceTests
|
||||
Assert.Single(handler.Requests, request => request.Body.Contains("\"method\":\"authenticate\""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetClassAbsencesAsync_LoestKlasseNamensbasiertAufUndAdressiertSieAlsKLPlusId()
|
||||
{
|
||||
var csv = Encoding.UTF8.GetBytes(
|
||||
"Schüler*innen\tExterne Id\tText\tKlasse\tDatum\tWochentag\tFehlstd.\tFehlmin.\tLehrkraft\tFach\t" +
|
||||
"Abwesenheitsgrund\tText\tENr\tErledigt\tAbwesenheit zählt\tEntschuldigungstext\tStundennr.\tStatus\tFehltage\r\n" +
|
||||
"Muster Max\t12345\t\t10c\t24.08.26\tMo.\t1\t45\tHED\tChe\tAbsent\tKrank gemeldet\t555\t24.08.26\ttrue\t\t3\tnicht entsch.\t1\r\n");
|
||||
var handler = new QueueHandler(
|
||||
Json("{\"result\":{\"sessionId\":\"desktop-session\"}}"), // authenticate (ConnectAsync)
|
||||
Json("{\"result\":[{\"id\":7,\"name\":\"2026/27\",\"startDate\":20260801,\"endDate\":20270731}]}"), // getSchoolyears (ConnectAsync-Validierung)
|
||||
Json("{\"result\":[{\"id\":7,\"name\":\"2026/27\",\"startDate\":20260801,\"endDate\":20270731}]}"), // getSchoolyears (ResolveClassIdAsync)
|
||||
Json("{\"result\":[{\"id\":874,\"name\":\"10c\"}]}"), // getKlassen
|
||||
Json("{\"data\":{\"finished\":true,\"error\":false," +
|
||||
"\"reportParams\":\"get=rpt.tmp&name=AbsencePerStudent&format=csv\"}}"),
|
||||
new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent(csv) },
|
||||
Json("{\"result\":{}}")); // logout (Dispose)
|
||||
var settings = TestSupport.BuildWebUntisSettingsService();
|
||||
await using var service = new WebUntisIntegrationService(new HttpClient(handler), settings);
|
||||
|
||||
await service.ConnectAsync(new WebUntisCredentials(
|
||||
"bk-ostvest", "arche.webuntis.com", "lehrkraft", "geheim"));
|
||||
var entries = await service.GetClassAbsencesAsync("10c",
|
||||
new DateOnly(2026, 8, 24), new DateOnly(2026, 8, 28));
|
||||
|
||||
var entry = Assert.Single(entries);
|
||||
Assert.Equal("Muster Max", entry.StudentName);
|
||||
Assert.Equal("nicht entsch.", entry.Status);
|
||||
// Zweimal getSchoolyears: einmal als ConnectAsync-Validierung, einmal in ResolveClassIdAsync.
|
||||
Assert.Equal(2, handler.Requests.Count(request => request.Body.Contains("\"method\":\"getSchoolyears\"")));
|
||||
Assert.Single(handler.Requests, request => request.Body.Contains("\"method\":\"getKlassen\""));
|
||||
Assert.Single(handler.Requests, request =>
|
||||
request.Uri.Contains("reports.do?name=AbsencePerStudent") &&
|
||||
request.Uri.Contains("klasseOrStudentgroupId=KL874"));
|
||||
}
|
||||
|
||||
private static HttpResponseMessage Json(string json) => new(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new StringContent(json, Encoding.UTF8, "application/json"),
|
||||
|
||||
Reference in New Issue
Block a user