Neue Funktion Schüler austragen statt aus Kurs löschen.

This commit is contained in:
2026-08-15 23:38:42 +02:00
parent 52bb9c2c2f
commit da8d2bb1da
19 changed files with 473 additions and 114 deletions
@@ -20,6 +20,7 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
private readonly IExamRepository _exams;
private readonly IExamResultRepository _results;
private readonly IGradeRepository _grades;
private readonly Dictionary<Guid, GroupMembership> _membershipsByStudent;
private readonly GradingService _grading;
private readonly Guid _groupId;
private readonly string _schoolYear;
@@ -69,7 +70,8 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
public ParticipationWizardDialogViewModel(
IParticipationSessionRepository sessions, IParticipationRepository entries,
IParticipationAspectRepository aspects, IParticipationSectionRepository sectionRepo,
IStudentRepository students, IExamRepository exams, IExamResultRepository results,
IStudentRepository students, IGroupMembershipRepository memberships,
IExamRepository exams, IExamResultRepository results,
IGradeRepository grades, GradingService grading,
Guid groupId, string schoolYear, GradingSystem gradingSystem, string groupLabel)
{
@@ -77,12 +79,17 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
_exams = exams; _results = results; _grades = grades; _grading = grading;
_groupId = groupId; _schoolYear = schoolYear; _gradingSystem = gradingSystem;
GroupLabel = groupLabel;
_membershipsByStudent = memberships.GetByGroup(groupId).ToDictionary(m => m.StudentId);
_aspectWeights = aspects.GetDefaults()
.Concat(aspects.GetByGroup(groupId))
.GroupBy(a => a.Key)
.ToDictionary(g => g.Key, g => g.Last().Weight);
_students = students.GetByGroup(groupId).OrderBy(s => s.LastName).ThenBy(s => s.FirstName).ToList();
var schoolYearRange = GroupMembershipService.SchoolYearPeriod(schoolYear, SchoolYearPeriodKind.FullYear);
_students = students.GetByGroup(groupId)
.Where(s => !_membershipsByStudent.TryGetValue(s.Id, out var membership)
|| GroupMembershipService.Overlaps(membership, schoolYearRange.From, schoolYearRange.To))
.OrderBy(s => s.LastName).ThenBy(s => s.FirstName).ToList();
_allSessions = sessions.GetByGroup(groupId).OrderBy(s => s.Date).ToList();
_sectionList = sectionRepo.GetByGroup(groupId).OrderBy(s => s.StartDate).ToList();
@@ -123,17 +130,20 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
foreach (var session in _allSessions)
{
if (!IsStudentActiveOn(studentId, session.Date)) continue;
var entry = _entries.GetBySessionAndStudent(session.Id, studentId);
points.Add((session.Date, BuildSessionPoint(session, entry, studentId)));
}
foreach (var exam in _exams.GetByGroup(_groupId).OrderBy(e => e.Date))
{
if (!IsStudentActiveOn(studentId, exam.Date)) continue;
var result = _results.GetByExamAndStudent(exam.Id, studentId);
if (result is null) continue;
points.Add((exam.Date, BuildExamPoint(exam, result)));
}
foreach (var grade in _grades.GetByStudentAndGroup(studentId, _groupId)
.Where(g => g.Category != GradeCategory.Participation))
.Where(g => g.Category != GradeCategory.Participation)
.Where(g => IsStudentActiveOn(studentId, g.Date)))
{
points.Add((grade.Date, BuildOtherGradePoint(grade)));
}
@@ -269,6 +279,7 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
foreach (var section in _sectionList)
{
if (!StudentOverlaps(studentId, section.StartDate, section.EndDate)) continue;
var grade = studentGrades.FirstOrDefault(g => g.Note == AbschnittPrefix + section.Label);
var row = new WizardSectionRow(section.Label, section.StartDate, section.EndDate,
grade?.Value ?? "", isOpen: false);
@@ -285,7 +296,8 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
private string? ComputeSuggestion(Guid studentId, DateOnly start, DateOnly end)
{
var points = new List<double>();
foreach (var session in _allSessions.Where(s => s.Date >= start && s.Date <= end))
foreach (var session in _allSessions.Where(s => s.Date >= start && s.Date <= end)
.Where(s => IsStudentActiveOn(studentId, s.Date)))
{
var entry = _entries.GetBySessionAndStudent(session.Id, studentId);
if (entry is null || entry.Ratings.Count == 0) continue;
@@ -375,6 +387,7 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
var sectionGrades = _grades.GetByStudentAndGroup(student.Id, _groupId)
.Where(g => g.Category == GradeCategory.Participation && g.Note is not null && g.Note.StartsWith(AbschnittPrefix))
.Where(g => InPeriod(g.Date, RollupPeriod.Period))
.Where(g => IsStudentActiveOn(student.Id, g.Date))
.Select(g => (g.Value, g.Weight))
.ToList();
if (sectionGrades.Count == 0) continue;
@@ -401,6 +414,14 @@ public partial class ParticipationWizardDialogViewModel : ObservableObject
ParticipationPeriod.H2 => date.Month >= 2 && date.Month <= 7,
_ => true,
};
private bool IsStudentActiveOn(Guid studentId, DateOnly date) =>
!_membershipsByStudent.TryGetValue(studentId, out var membership)
|| GroupMembershipService.IsActiveOn(membership, date);
private bool StudentOverlaps(Guid studentId, DateOnly from, DateOnly to) =>
!_membershipsByStudent.TryGetValue(studentId, out var membership)
|| GroupMembershipService.Overlaps(membership, from, to);
}
// ── Zeitleisten-Bausteine ────────────────────────────────────────────────────