fix: WebUntis-Schülerreport verkraftet fehlenden externKey
Klassenlehrer-Übersicht scheiterte komplett mit "Ungültige Zahl in Spalte externKey", sobald ein Schüler der Klasse keinen gepflegten externen Schlüssel hat (in echten Schuldaten vorkommend). externKey ist jetzt optional statt Pflichtfeld (UntisStudent/UntisStudentDto.ExternKey -> int?); betraf denselben Bericht wie der bestehende WebUntis-Klassenimport, der das aber schon immer beim Weiterimport toleriert hatte. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,7 +22,7 @@ public sealed record UntisTimetablePeriodDto(int Id, int Date, int StartTime, in
|
|||||||
IReadOnlyList<UntisEntityDto> Subjects, IReadOnlyList<UntisEntityDto> Rooms);
|
IReadOnlyList<UntisEntityDto> Subjects, IReadOnlyList<UntisEntityDto> Rooms);
|
||||||
public sealed record UntisStudentAddressDto(string? Email, string? Mobile, string? Phone, string? City,
|
public sealed record UntisStudentAddressDto(string? Email, string? Mobile, string? Phone, string? City,
|
||||||
string? PostCode, string? Street);
|
string? PostCode, string? Street);
|
||||||
public sealed record UntisStudentDto(int UntisId, int ExternKey, string ClassName, string? Name, string? LongName,
|
public sealed record UntisStudentDto(int UntisId, int? ExternKey, string ClassName, string? Name, string? LongName,
|
||||||
string? ForeName, string DisplayName, string? Gender, int? BirthDate, string? BirthDateRaw, int? EntryDate,
|
string? ForeName, string DisplayName, string? Gender, int? BirthDate, string? BirthDateRaw, int? EntryDate,
|
||||||
string? EntryDateRaw, int? ExitDate, string? ExitDateRaw, string? Text, string? MedicalReportDuty,
|
string? EntryDateRaw, int? ExitDate, string? ExitDateRaw, string? Text, string? MedicalReportDuty,
|
||||||
string? Schulpflicht, string? Majority, UntisStudentAddressDto Address, string? AttributeIL);
|
string? Schulpflicht, string? Majority, UntisStudentAddressDto Address, string? AttributeIL);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, b
|
|||||||
return students
|
return students
|
||||||
.Select(s =>
|
.Select(s =>
|
||||||
{
|
{
|
||||||
var absence = absenceByKey.GetValueOrDefault(s.ExternKey)
|
var absence = (s.ExternKey is { } key ? absenceByKey.GetValueOrDefault(key) : null)
|
||||||
?? absenceByName.GetValueOrDefault(s.DisplayName);
|
?? absenceByName.GetValueOrDefault(s.DisplayName);
|
||||||
return new ClassTeacherRosterRow(s.DisplayName, s.ExternKey, absence is not null,
|
return new ClassTeacherRosterRow(s.DisplayName, s.ExternKey, absence is not null,
|
||||||
absence is null ? null : $"{absence.TotalAbsentPeriods} Stunde(n) — {absence.StatusLabel}",
|
absence is null ? null : $"{absence.TotalAbsentPeriods} Stunde(n) — {absence.StatusLabel}",
|
||||||
|
|||||||
@@ -51,6 +51,19 @@ public sealed class WebUntisStudentReportParserTests
|
|||||||
Assert.Equal(20270731, student.ExitDate);
|
Assert.Equal(20270731, student.ExitDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Parse_ErlaubtFehlendenExternKey()
|
||||||
|
{
|
||||||
|
// Reale Schuldaten enthalten Schüler*innen ohne gepflegten externen Schlüssel (z.B. frisch
|
||||||
|
// angelegt) - das darf nicht den Abruf der gesamten Klassenliste zum Absturz bringen.
|
||||||
|
const string report = "id\texternKey\tklasse.name\r\n17\t\t10a\r\n";
|
||||||
|
|
||||||
|
var student = Assert.Single(WebUntisStudentReportParser.Parse(report));
|
||||||
|
|
||||||
|
Assert.Equal(17, student.UntisId);
|
||||||
|
Assert.Null(student.ExternKey);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Parse_LehntUngueltigePflichtIdAb()
|
public void Parse_LehntUngueltigePflichtIdAb()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,9 +41,12 @@ public sealed record UntisStudentAddress(
|
|||||||
string? PostCode,
|
string? PostCode,
|
||||||
string? Street);
|
string? Street);
|
||||||
|
|
||||||
|
// ExternKey optional: reale Schuldaten enthalten Schüler*innen (z.B. frisch angelegt/übernommen)
|
||||||
|
// ohne gepflegten externen Schlüssel - ein fehlender Wert bei einer/einem soll nicht den Abruf der
|
||||||
|
// gesamten Klassenliste zum Absturz bringen (siehe WebUntisStudentReportParser).
|
||||||
public sealed record UntisStudent(
|
public sealed record UntisStudent(
|
||||||
int UntisId,
|
int UntisId,
|
||||||
int ExternKey,
|
int? ExternKey,
|
||||||
string ClassName,
|
string ClassName,
|
||||||
string? Name,
|
string? Name,
|
||||||
string? LongName,
|
string? LongName,
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public static class WebUntisStudentReportParser
|
|||||||
values[headers[index]] = index < row.Count ? row[index].Trim() : null;
|
values[headers[index]] = index < row.Count ? row[index].Trim() : null;
|
||||||
|
|
||||||
var untisId = RequiredInt(Get(values, "id"), "id");
|
var untisId = RequiredInt(Get(values, "id"), "id");
|
||||||
var externalKey = RequiredInt(Get(values, "externKey"), "externKey");
|
var externalKey = OptionalInt(Get(values, "externKey"));
|
||||||
var name = Optional(Get(values, "name"));
|
var name = Optional(Get(values, "name"));
|
||||||
var lastName = Optional(Get(values, "longName"));
|
var lastName = Optional(Get(values, "longName"));
|
||||||
var firstName = Optional(Get(values, "foreName"));
|
var firstName = Optional(Get(values, "foreName"));
|
||||||
@@ -73,6 +73,11 @@ public static class WebUntisStudentReportParser
|
|||||||
? parsed
|
? parsed
|
||||||
: throw new InvalidDataException($"Ungültige Zahl in Spalte \"{field}\".");
|
: throw new InvalidDataException($"Ungültige Zahl in Spalte \"{field}\".");
|
||||||
|
|
||||||
|
private static int? OptionalInt(string? value) =>
|
||||||
|
int.TryParse(value?.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsed)
|
||||||
|
? parsed
|
||||||
|
: null;
|
||||||
|
|
||||||
private static int? GermanDate(string? value)
|
private static int? GermanDate(string? value)
|
||||||
{
|
{
|
||||||
if (!DateOnly.TryParseExact(value?.Trim(), ["dd.MM.yyyy", "yyyyMMdd"], CultureInfo.InvariantCulture,
|
if (!DateOnly.TryParseExact(value?.Trim(), ["dd.MM.yyyy", "yyyyMMdd"], CultureInfo.InvariantCulture,
|
||||||
|
|||||||
@@ -1328,6 +1328,15 @@ eigenen Unterricht abfragt und deshalb mit den regulären Lehrkraft-Rechten funk
|
|||||||
Schülerdetail; ein Detail-Drill-down pro Schüler*in (Heatmap gefehlter Einzelstunden,
|
Schülerdetail; ein Detail-Drill-down pro Schüler*in (Heatmap gefehlter Einzelstunden,
|
||||||
Verspätung/vorzeitiges Verlassen je Woche); eine KI-gestützte Zusammenfassung/Bewertung der
|
Verspätung/vorzeitiges Verlassen je Woche); eine KI-gestützte Zusammenfassung/Bewertung der
|
||||||
Einträge (pro Schüler*in oder gesamt) mit Namens-Anonymisierung/Synonymen vor dem Versand.
|
Einträge (pro Schüler*in oder gesamt) mit Namens-Anonymisierung/Synonymen vor dem Versand.
|
||||||
|
**Echter Fehler gefunden und behoben:** Die Übersicht rief für das Roster
|
||||||
|
`WebUntisIntegrationService.GetStudentsAsync` auf (den bestehenden, schon länger genutzten
|
||||||
|
Schülerlisten-Bericht) — `WebUntisStudentReportParser` behandelte die Spalte `externKey` bisher als
|
||||||
|
Pflichtfeld (`RequiredInt`) und ließ den kompletten Abruf mit "Ungültige Zahl in Spalte
|
||||||
|
\"externKey\"" scheitern, sobald auch nur eine/einer der Schüler*innen der Klasse keinen
|
||||||
|
gepflegten externen Schlüssel hat (in der Praxis vorkommend, z. B. frisch angelegt). `externKey`
|
||||||
|
ist jetzt optional (`UntisStudent.ExternKey`/`UntisStudentDto.ExternKey` → `int?`) — betraf auch
|
||||||
|
den bestehenden WebUntis-Klassenimport in der Kursansicht, der das aber schon immer über
|
||||||
|
`FirstNotEmpty` beim Weiterimport toleriert hatte und deshalb nicht auffiel.
|
||||||
|
|
||||||
### 4.4 Wochen-/Tagesansicht
|
### 4.4 Wochen-/Tagesansicht
|
||||||
- [x] **4.4.1** Kalenderansicht über alle Gruppen: Woche und Tag — siehe Nachtrag zu 4.3
|
- [x] **4.4.1** Kalenderansicht über alle Gruppen: Woche und Tag — siehe Nachtrag zu 4.3
|
||||||
|
|||||||
Reference in New Issue
Block a user