"Vorgang": Fallmappe für Klassenbuch- und Dokumentationseinträge
CI / build-and-test (push) Canceled after 0s

Neuer 4. Tab in der Klassenlehreransicht: bündelt Titel, Beschreibung,
Schlagwörter, verknüpfte Dokumentation und angeheftete (eingefrorene)
WebUntis-Klassenbucheinträge zu einem laufenden Problem mit einer/einem
oder mehreren Schüler*innen. Anheften direkt aus dem Klassenbuch-Tab per
Rechtsklick. Sync-fähig nach dem bestehenden Documentation-Muster.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-01 22:42:06 +02:00
co-authored by Claude Sonnet 5
parent 987b49e761
commit 15463bc292
23 changed files with 1191 additions and 5 deletions
@@ -135,6 +135,7 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
private readonly UntisReportCacheService _cache;
private readonly IDocumentationRepository _documentation;
private readonly IStudentRepository _students;
private readonly ClassTeacherCasesViewModel _cases;
private string _className = "";
private DateOnly _loadedStart;
private DateOnly _loadedEnd;
@@ -149,6 +150,9 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
public ObservableCollection<ClassTeacherCategoryAggregateRow> CategoryAggregates { get; } = [];
public ObservableCollection<DocumentationItem> OwnDocumentationEntries { get; } = [];
/// Für das Kontextmenü "→ An Vorgang anheften" (DataGrid.SelectedItem, zweigleisig gebunden).
[ObservableProperty] private ClassTeacherClassRegisterRow? _selectedEntry;
[ObservableProperty] private DateTimeOffset? _startDate = DateTimeOffset.Now.AddDays(-6);
[ObservableProperty] private DateTimeOffset? _endDate = DateTimeOffset.Now;
[ObservableProperty] private string _status = "Zeitraum wählen und laden.";
@@ -164,6 +168,9 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
public Func<List<StudentOption>, Documentation?, Task<Documentation?>>? OnEditOwnDocumentation { get; set; }
public Func<DocumentationItem, Task<bool>>? OnConfirmDeleteOwnDocumentation { get; set; }
/// Zeigt die "→ Vorgang"-Auswahl für eine Klassenbuchzeile: bestehenden offenen Vorgang wählen
/// oder einen neuen Titel eingeben. Liefert null bei Abbruch.
public Func<ClassTeacherClassRegisterRow, Task<PinToVorgangChoice?>>? OnPickVorgangForPin { get; set; }
public bool HasEntries => Entries.Count > 0;
public bool HasAbsenceEntries => AbsenceEntries.Count > 0;
@@ -177,11 +184,12 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
? "Alle Schüler*innen" : StudentFilter;
public ClassTeacherDetailsViewModel(UntisReportCacheService cache, IDocumentationRepository documentation,
IStudentRepository students)
IStudentRepository students, ClassTeacherCasesViewModel cases)
{
_cache = cache;
_documentation = documentation;
_students = students;
_cases = cases;
}
public void Initialize(string className)
@@ -359,6 +367,42 @@ public partial class ClassTeacherDetailsViewModel : ObservableObject
LoadOwnDocumentationEntries();
}
/// Für den "→ Vorgang"-Auswahldialog im Code-behind: offene Vorgänge, an die sich diese
/// Klassenbuchzeile anheften lässt (Durchreiche zur Geschwister-ViewModel-Instanz, siehe _cases).
public List<VorgangItem> OpenCasesFor(string studentName) => _cases.OpenCasesForStudent(studentName);
/// Heftet eine WebUntis-Klassenbuchzeile als eingefrorene Kopie an einen (ggf. neuen) Vorgang
/// im "Vorgänge"-Tab (<see cref="ClassTeacherCasesViewModel"/>) an — die Zeile selbst hat keine
/// stabile ID, deshalb Werte-Kopie statt Referenz (siehe VorgangClassRegisterEntry).
[RelayCommand]
private async Task PinToVorgang(ClassTeacherClassRegisterRow? row)
{
if (row is null || OnPickVorgangForPin is null) return;
var choice = await OnPickVorgangForPin(row);
if (choice is null) return;
var entry = BuildClassRegisterSnapshot(row);
if (choice.ExistingVorgangId is { } vorgangId)
await _cases.PinClassRegisterEntryAsync(vorgangId, entry);
else if (!string.IsNullOrWhiteSpace(choice.NewVorgangTitle))
{
var student = _cases.RosterStudentOptions
.FirstOrDefault(s => UntisNameMatching.NamesMatch(s.Name, row.StudentName));
if (student is null) return;
await _cases.CreateAndPinAsync(choice.NewVorgangTitle, student.Id, entry);
}
}
/// Baut die eingefrorene Klassenbuch-Kopie aus einer Zeile — reine, ohne Repository-Zugriff
/// testbare Umwandlung.
public static VorgangClassRegisterEntry BuildClassRegisterSnapshot(ClassTeacherClassRegisterRow row) => new()
{
Date = row.Date, StudentName = row.StudentName, Subject = row.Subject,
TeacherUsername = row.TeacherUsername, CategoryName = row.CategoryName,
CategoryGroup = row.CategoryGroup, Text = row.Text,
};
// WebUntis liefert Namen je nach Bericht in anderer Reihenfolge als der Schülerreport, aus dem
// StudentFilter beim Klick in der Übersicht gesetzt wird (siehe UntisNameMatching) - ein
// exakter String-Vergleich hier ließ die gefilterten Listen fälschlich leer erscheinen.