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 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"; } }