165 lines
6.5 KiB
C#
165 lines
6.5 KiB
C#
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";
|
||
}
|
||
}
|