Schülerdokumentation: Einträge, Fehlzeitenbilanz, Datenschutz (Kapitel 5)
Dokumentationseinträge mit Typwahl (Gespräch, Vorkommnis, Förderplan, Fehlzeit, Elternanruf, Elternbrief), vertrauliche Einträge nur nach Bestätigung sichtbar, weiche Löschung mit Nachvollziehbarkeit. Fehlzeiten als Auswertung des bestehenden Anwesenheits-Trackings statt zweiter Erfassung, mit Schwellenwert-Warnung im Schülerdetail und Dashboard. Förderplan-Wiedervorlage als Dashboard-Karte. Datenschutz: Löschfristen mit manueller Bereinigung und DSGVO-Art.-15-Datenauskunft als Export. Auf Nutzer-Feedback hin ergänzt: Elternanruf mit begleitendem Gesprächsprotokoll-Dialog (Punkte abhaken, Eindrücke festhalten), Elternbrief mit Versand-/Rückmeldungs-Tracking, Datei-Anhänge über LiteDBs Dateispeicher, frei vergebbare Labels zur Nachverfolgung mit Dringlichkeits-Farbcodierung, sowie eine sichtbare Farblegende für das bestehende Notenentwicklungs-Diagramm. Dabei einen Absturz behoben: leere Textfelder lieferten über das Binding null statt "", was beim Speichern eine NullReferenceException auslöste. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
@@ -79,6 +80,11 @@ public partial class StudentDetailViewModel : ObservableObject
|
||||
private readonly IExamRepository _exams;
|
||||
private readonly IExamResultRepository _examResults;
|
||||
private readonly IGradeRepository _grades;
|
||||
private readonly IParticipationRepository _participation;
|
||||
private readonly IParticipationSessionRepository _participationSessions;
|
||||
private readonly AttendanceBalanceService _attendanceBalance;
|
||||
private readonly PersonalDataExportService _export;
|
||||
private readonly SchoolYearService _schoolYear;
|
||||
|
||||
[ObservableProperty] private Student? _student;
|
||||
[ObservableProperty] private string _studentTitle = "";
|
||||
@@ -86,23 +92,33 @@ public partial class StudentDetailViewModel : ObservableObject
|
||||
[ObservableProperty] private string _editFirstName = "";
|
||||
[ObservableProperty] private string _editLastName = "";
|
||||
[ObservableProperty] private ContactItem? _selectedContact;
|
||||
[ObservableProperty] private AttendanceBalance? _attendance;
|
||||
[ObservableProperty] private string _exportStatus = "";
|
||||
|
||||
public ObservableCollection<GroupMembershipEntry> GroupMemberships { get; } = [];
|
||||
public ObservableCollection<DocEntry> Documentation { get; } = [];
|
||||
public ObservableCollection<DocumentationItem> Documentation { get; } = [];
|
||||
public ObservableCollection<ContactItem> Contacts { get; } = [];
|
||||
public ObservableCollection<StudentGradeHistoryGroup> GradeHistory { get; } = [];
|
||||
public bool HasNoContacts => Contacts.Count == 0;
|
||||
public Func<Contact?, Task<Contact?>>? OnEditContact { get; set; }
|
||||
public Action<ContactItem>? OnViewAddress { get; set; }
|
||||
public Func<Guid, Documentation?, Task<Documentation?>>? OnEditDocumentation { get; set; }
|
||||
public Func<DocumentationItem, Task<bool>>? OnConfirmDeleteDocumentation { get; set; }
|
||||
public Func<string, Task>? OnSaveExportFile { get; set; }
|
||||
public Func<Documentation, string, Task<Documentation?>>? OnConductParentCall { get; set; }
|
||||
|
||||
public StudentDetailViewModel(IStudentRepository students,
|
||||
IGroupMembershipRepository memberships, IGroupRepository groups, ISubjectRepository subjects,
|
||||
IDocumentationRepository docs, IExamRepository exams, IExamResultRepository examResults,
|
||||
IGradeRepository grades)
|
||||
IGradeRepository grades, IParticipationRepository participation,
|
||||
IParticipationSessionRepository participationSessions, AttendanceBalanceService attendanceBalance,
|
||||
PersonalDataExportService export, SchoolYearService schoolYear)
|
||||
{
|
||||
_students = students; _memberships = memberships;
|
||||
_groups = groups; _subjects = subjects; _docs = docs;
|
||||
_exams = exams; _examResults = examResults; _grades = grades;
|
||||
_participation = participation; _participationSessions = participationSessions;
|
||||
_attendanceBalance = attendanceBalance; _export = export; _schoolYear = schoolYear;
|
||||
}
|
||||
|
||||
public void LoadStudent(Guid id)
|
||||
@@ -127,19 +143,86 @@ public partial class StudentDetailViewModel : ObservableObject
|
||||
}
|
||||
|
||||
LoadContacts();
|
||||
LoadDocumentation();
|
||||
LoadAttendanceBalance();
|
||||
}
|
||||
|
||||
// ── Dokumentation (5.1) ───────────────────────────────────────────────────
|
||||
|
||||
private void LoadDocumentation()
|
||||
{
|
||||
if (Student is null) return;
|
||||
Documentation.Clear();
|
||||
foreach (var d in _docs.GetByStudent(Student.Id))
|
||||
Documentation.Add(new() { Date = d.Date.ToString("dd.MM.yyyy"), Title = d.Title,
|
||||
TypeLabel = d.Type switch
|
||||
{
|
||||
DocumentationType.Conversation => "Gespräch",
|
||||
DocumentationType.Incident => "Vorkommnis",
|
||||
DocumentationType.SupportPlan => "Förderplan",
|
||||
DocumentationType.Absence => "Fehlzeit",
|
||||
_ => "",
|
||||
},
|
||||
IsConfidential = d.IsConfidential });
|
||||
Documentation.Add(new DocumentationItem(d));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddDocumentation()
|
||||
{
|
||||
if (Student is null || OnEditDocumentation is null) return;
|
||||
var result = await OnEditDocumentation(Student.Id, null);
|
||||
if (result is null) return;
|
||||
_docs.Save(result);
|
||||
LoadDocumentation();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task EditDocumentation(DocumentationItem? item)
|
||||
{
|
||||
if (Student is null || item is null || OnEditDocumentation is null) return;
|
||||
var result = await OnEditDocumentation(Student.Id, item.Model);
|
||||
if (result is null) return;
|
||||
_docs.Save(result);
|
||||
LoadDocumentation();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task DeleteDocumentation(DocumentationItem? item)
|
||||
{
|
||||
if (item is null) return;
|
||||
if (OnConfirmDeleteDocumentation is not null && !await OnConfirmDeleteDocumentation(item)) return;
|
||||
_docs.Delete(item.Model.Id);
|
||||
LoadDocumentation();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ConductParentCall(DocumentationItem? item)
|
||||
{
|
||||
if (Student is null || item is null || OnConductParentCall is null) return;
|
||||
var result = await OnConductParentCall(item.Model, Student.FullName);
|
||||
if (result is null) return;
|
||||
_docs.Save(result);
|
||||
LoadDocumentation();
|
||||
}
|
||||
|
||||
// ── Fehlzeitenbilanz (5.2.2/5.2.3) ────────────────────────────────────────
|
||||
|
||||
private void LoadAttendanceBalance()
|
||||
{
|
||||
if (Student is null) return;
|
||||
var schoolYear = _schoolYear.CurrentSchoolYear();
|
||||
var from = _schoolYear.SchoolYearStart(schoolYear);
|
||||
var to = _schoolYear.SchoolYearEnd(schoolYear);
|
||||
|
||||
var entries = _participation.GetByStudent(Student.Id)
|
||||
.Select(e => _participationSessions.GetById(e.SessionId) is { } session
|
||||
? ((DateOnly?)session.Date, e.Attendance) : (null, e.Attendance))
|
||||
.Where(t => t.Item1.HasValue)
|
||||
.Select(t => (t.Item1!.Value, t.Attendance));
|
||||
|
||||
Attendance = _attendanceBalance.Calculate(entries, from, to);
|
||||
}
|
||||
|
||||
// ── Datenauskunft (5.4.3) ─────────────────────────────────────────────────
|
||||
|
||||
[RelayCommand]
|
||||
private async Task ExportPersonalData()
|
||||
{
|
||||
if (Student is null || OnSaveExportFile is null) return;
|
||||
var json = _export.ExportAsJson(Student.Id);
|
||||
await OnSaveExportFile(json);
|
||||
ExportStatus = "Datenauskunft exportiert.";
|
||||
}
|
||||
|
||||
// ── Notenentwicklung (2.5) ────────────────────────────────────────────────
|
||||
@@ -255,7 +338,6 @@ public partial class StudentDetailViewModel : ObservableObject
|
||||
}
|
||||
|
||||
public class GroupMembershipEntry { public string SchoolYear { get; set; } = ""; public string GroupName { get; set; } = ""; public string Subject { get; set; } = ""; }
|
||||
public class DocEntry { public string Date { get; set; } = ""; public string Title { get; set; } = ""; public string TypeLabel { get; set; } = ""; public bool IsConfidential { get; set; } }
|
||||
|
||||
// ── Notenentwicklung (2.5) ──────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user