Files
LehrerApp/LehrerApp.Desktop/ViewModels/Groups/GroupRolloverViewModel.cs
2026-08-16 00:29:01 +02:00

165 lines
6.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using System.Collections.ObjectModel;
namespace LehrerApp.Desktop.ViewModels.Groups;
public partial class GroupRolloverDialogViewModel : ObservableObject
{
private readonly LearningGroup _source;
private readonly GroupRolloverService _rollover;
private readonly SchoolYearService _schoolYears;
[ObservableProperty] private string _name;
[ObservableProperty] private string _targetSchoolYear;
[ObservableProperty] private int _gradeLevel;
[ObservableProperty] private bool _copyGradingScheme;
[ObservableProperty] private bool _archiveSource = true;
[ObservableProperty] private string _nameError = "";
[ObservableProperty] private string _schoolYearError = "";
[ObservableProperty] private string _selectionError = "";
[ObservableProperty] private string _generalError = "";
public string SourceSummary => $"{_source.Name} · {_source.SchoolYear} · Stufe {_source.GradeLevel}";
public bool HasGradingScheme { get; }
public int SelectedCount => Students.Count(s => s.IsSelected);
public string SelectedCountText => $"{SelectedCount} von {Students.Count} Schülern ausgewählt";
public ObservableCollection<GroupRolloverStudentItem> Students { get; } = [];
public LearningGroup? Result { get; private set; }
public GroupRolloverDialogViewModel(LearningGroup source,
IStudentRepository students, IGroupMembershipRepository memberships,
IGradingSchemeRepository gradingSchemes, SchoolYearService schoolYears,
GroupRolloverService rollover)
{
_source = source;
_rollover = rollover;
_schoolYears = schoolYears;
_name = source.Name;
_targetSchoolYear = NextSchoolYear(source.SchoolYear, schoolYears);
_gradeLevel = Math.Min(13, source.GradeLevel + 1);
HasGradingScheme = gradingSchemes.GetByGroup(source.Id) is not null;
_copyGradingScheme = HasGradingScheme;
var sourceEnd = schoolYears.SchoolYearEnd(source.SchoolYear);
foreach (var membership in memberships.GetByGroup(source.Id)
.Select(m => (Membership: m, Student: students.GetById(m.StudentId)))
.Where(x => x.Student is not null)
.OrderBy(x => x.Student!.LastName).ThenBy(x => x.Student!.FirstName))
{
var canSelect = membership.Student!.IsActive;
var isPreselected = canSelect
&& GroupMembershipService.IsActiveOn(membership.Membership, sourceEnd);
var item = new GroupRolloverStudentItem(
membership.Student, membership.Membership, isPreselected, canSelect);
item.PropertyChanged += (_, e) =>
{
if (e.PropertyName != nameof(GroupRolloverStudentItem.IsSelected)) return;
SelectionError = "";
OnPropertyChanged(nameof(SelectedCount));
OnPropertyChanged(nameof(SelectedCountText));
};
Students.Add(item);
}
}
[RelayCommand]
private void Save()
{
NameError = "";
SchoolYearError = "";
SelectionError = "";
GeneralError = "";
var valid = true;
if (string.IsNullOrWhiteSpace(Name))
{
NameError = "Gruppenname erforderlich.";
valid = false;
}
try
{
var targetText = TargetSchoolYear.Trim();
var targetStart = _schoolYears.SchoolYearStart(targetText);
if (targetText != _schoolYears.FormatSchoolYear(targetStart.Year))
{
SchoolYearError = "Format JJJJ/JJ, z.B. 2027/28.";
valid = false;
}
else if (targetStart <= _schoolYears.SchoolYearStart(_source.SchoolYear))
{
SchoolYearError = "Das Zielschuljahr muss nach dem bisherigen liegen.";
valid = false;
}
}
catch (Exception ex) when (ex is FormatException or ArgumentOutOfRangeException or IndexOutOfRangeException)
{
SchoolYearError = "Format JJJJ/JJ, z.B. 2027/28.";
valid = false;
}
if (SelectedCount == 0)
{
SelectionError = "Bitte mindestens einen Schüler auswählen.";
valid = false;
}
if (!valid) return;
try
{
Result = _rollover.RollOver(_source, new GroupRolloverRequest(
Name, TargetSchoolYear.Trim(), GradeLevel,
Students.Where(s => s.IsSelected).Select(s => s.MembershipId).ToList(),
CopyGradingScheme, ArchiveSource));
}
catch (Exception ex) when (ex is InvalidOperationException or FormatException or ArgumentOutOfRangeException)
{
GeneralError = ex.Message;
}
}
private static string NextSchoolYear(string source, SchoolYearService schoolYears)
{
var start = schoolYears.SchoolYearStart(source);
return schoolYears.FormatSchoolYear(start.Year + 1);
}
}
public partial class GroupRolloverStudentItem : ObservableObject
{
[ObservableProperty] private bool _isSelected;
public Guid MembershipId { get; }
public string FullName { get; }
public string NiveauText { get; }
public string MembershipStatus { get; }
public bool CanSelect { get; }
public GroupRolloverStudentItem(Student student, GroupMembership membership,
bool isSelected, bool canSelect)
{
MembershipId = membership.Id;
FullName = student.FullName;
NiveauText = membership.Niveau?.ToString() ?? "";
CanSelect = canSelect;
_isSelected = isSelected;
MembershipStatus = BuildStatus(student, membership, isSelected);
}
partial void OnIsSelectedChanged(bool value)
{
if (!CanSelect && value) IsSelected = false;
}
private static string BuildStatus(Student student, GroupMembership membership, bool isPreselected)
{
if (!student.IsActive) return "Schüler inaktiv · nicht übernehmbar";
if (isPreselected) return "Bis Schuljahresende aktiv · vorausgewählt";
if (membership.LeftAt is { } leftAt) return $"Ausgetreten am {leftAt:dd.MM.yyyy} · nicht vorausgewählt";
if (membership.Period == MembershipPeriod.H1Only) return "Nur 1. Halbjahr · nicht vorausgewählt";
return "Am Schuljahresende nicht aktiv · nicht vorausgewählt";
}
}