using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using System.Collections.ObjectModel; using System.Globalization; namespace LehrerApp.Desktop.ViewModels.Planning; public static class SubstitutionKindDisplay { public static string[] Options { get; } = ["Aufsicht", "Stunde", "Sondereinsatz", "Ausfall"]; public static string ToName(SubstitutionKind k) => k switch { SubstitutionKind.Lesson => "Stunde", SubstitutionKind.SpecialAssignment => "Sondereinsatz", SubstitutionKind.Cancelled => "Ausfall", _ => "Aufsicht", }; public static SubstitutionKind FromName(string? name) => name switch { "Stunde" => SubstitutionKind.Lesson, "Sondereinsatz" => SubstitutionKind.SpecialAssignment, "Ausfall" => SubstitutionKind.Cancelled, _ => SubstitutionKind.Supervision, }; } /// /// Dialog "Vertretung eintragen" (4.3 Nachtrag, Sonderfälle): einmalige Vertretungsaufsicht, /// Vertretungsstunde in einer eigenen oder fremden Lerngruppe, ein Sondereinsatz (Ausflug, /// Berufsmesse, Exkursion, ...), oder ein schlichter Stundenausfall (z.B. weil die betroffene /// Gruppe selbst auf Klassenfahrt ist — nicht die eigene Abwesenheit, sondern die der Gruppe). /// Bei eigener Gruppe bleibt der einfache Weg (nur Plan-Eintrag mit Thema) der Standard — /// "Direkt als Stunde in der Einheit übernehmen" ist ein bewusstes Opt-in, nur bei /// Vertretungsstunden, damit die meist beiläufigen Vertretungsstunden nicht automatisch die /// Fortschrittsanzeige/Reihenfolge der Einheit durcheinanderbringen (siehe TODO.md, Nachtrag zu /// 4.3). Sondereinsatz und Ausfall sind bewusst nie als Einheiten-Stunde übernehmbar — ein /// Ausflug ist inhaltlich kein Verlaufsplan-Eintrag, und ein Ausfall ist per Definition keine /// gehaltene Stunde. /// public partial class SubstitutionEntryDialogViewModel : ObservableObject { private readonly ISubstitutionEntryRepository _substitutions; private readonly IUnitRepository _units; private readonly ILessonRepository _lessons; [ObservableProperty] private string _dateText = DateOnly.FromDateTime(DateTime.Today).ToString("dd.MM.yyyy"); [ObservableProperty] private string _kindName = SubstitutionKindDisplay.Options[0]; [ObservableProperty] private int _afterPeriod; [ObservableProperty] private int _periodNumber = 1; [ObservableProperty] private bool _isAllDay = true; [ObservableProperty] private int _fromPeriod = 1; [ObservableProperty] private int _toPeriod = 10; [ObservableProperty] private LearningGroup? _selectedOwnGroup; [ObservableProperty] private string _groupLabel = ""; [ObservableProperty] private string _description = ""; [ObservableProperty] private bool _promoteToLesson; [ObservableProperty] private Unit? _selectedUnit; [ObservableProperty] private string _dateError = ""; [ObservableProperty] private string _validationError = ""; public string[] KindOptions => SubstitutionKindDisplay.Options; public bool IsSupervisionKind => KindName == "Aufsicht"; public bool IsLessonKind => KindName == "Stunde"; public bool IsSpecialAssignmentKind => KindName == "Sondereinsatz"; public bool IsCancelledKind => KindName == "Ausfall"; public bool CanPromoteToLesson => IsLessonKind && SelectedOwnGroup is not null && UnitsOfSelectedGroup.Count > 0; public ObservableCollection OwnGroups { get; } = []; public ObservableCollection UnitsOfSelectedGroup { get; } = []; public SubstitutionEntry? Result { get; private set; } public SubstitutionEntryDialogViewModel(ISubstitutionEntryRepository substitutions, IGroupRepository groups, IUnitRepository units, ILessonRepository lessons) { _substitutions = substitutions; _units = units; _lessons = lessons; foreach (var g in groups.GetAll().OrderBy(g => g.Name)) OwnGroups.Add(g); } partial void OnKindNameChanged(string value) { OnPropertyChanged(nameof(IsSupervisionKind)); OnPropertyChanged(nameof(IsLessonKind)); OnPropertyChanged(nameof(IsSpecialAssignmentKind)); OnPropertyChanged(nameof(IsCancelledKind)); OnPropertyChanged(nameof(CanPromoteToLesson)); } partial void OnSelectedOwnGroupChanged(LearningGroup? value) { UnitsOfSelectedGroup.Clear(); if (value is not null) { GroupLabel = value.Name; foreach (var u in _units.GetByGroup(value.Id)) UnitsOfSelectedGroup.Add(u); } SelectedUnit = UnitsOfSelectedGroup.FirstOrDefault(); PromoteToLesson = false; OnPropertyChanged(nameof(CanPromoteToLesson)); } [RelayCommand] private void Save() { DateError = ""; ValidationError = ""; if (!DateOnly.TryParseExact(DateText, "dd.MM.yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) { DateError = "Format TT.MM.JJJJ."; return; } var kind = SubstitutionKindDisplay.FromName(KindName); var entry = new SubstitutionEntry { Date = date, Kind = kind }; switch (kind) { case SubstitutionKind.Supervision: if (AfterPeriod is < 0 or > 10) { ValidationError = "Pause muss zwischen 0 und 10 liegen."; return; } if (string.IsNullOrWhiteSpace(Description)) { ValidationError = "Grund/Ort erforderlich."; return; } entry.AfterPeriod = AfterPeriod; entry.Description = Description.Trim(); break; case SubstitutionKind.Lesson: if (PeriodNumber is < 1 or > 10) { ValidationError = "Stunde muss zwischen 1 und 10 liegen."; return; } if (string.IsNullOrWhiteSpace(GroupLabel)) { ValidationError = "Gruppe/Bezeichnung erforderlich."; return; } if (string.IsNullOrWhiteSpace(Description)) { ValidationError = "Thema erforderlich."; return; } entry.PeriodNumber = PeriodNumber; entry.GroupId = SelectedOwnGroup?.Id; entry.GroupLabel = GroupLabel.Trim(); entry.Description = Description.Trim(); break; case SubstitutionKind.SpecialAssignment: if (string.IsNullOrWhiteSpace(Description)) { ValidationError = "Bezeichnung erforderlich."; return; } if (!IsAllDay) { if (FromPeriod is < 1 or > 10 || ToPeriod is < 1 or > 10) { ValidationError = "Stunden müssen zwischen 1 und 10 liegen."; return; } if (ToPeriod < FromPeriod) { ValidationError = "Die Bis-Stunde darf nicht vor der Von-Stunde liegen."; return; } entry.FromPeriod = FromPeriod; entry.ToPeriod = ToPeriod; } entry.IsAllDay = IsAllDay; entry.GroupId = SelectedOwnGroup?.Id; entry.GroupLabel = GroupLabel.Trim(); entry.Description = Description.Trim(); break; case SubstitutionKind.Cancelled: if (PeriodNumber is < 1 or > 10) { ValidationError = "Stunde muss zwischen 1 und 10 liegen."; return; } entry.PeriodNumber = PeriodNumber; entry.Description = Description.Trim(); break; } _substitutions.Save(entry); if (kind == SubstitutionKind.Lesson && PromoteToLesson && SelectedOwnGroup is not null && SelectedUnit is not null) { _lessons.Save(new Lesson { UnitId = SelectedUnit.Id, GroupId = SelectedOwnGroup.Id, Date = date, LessonNumber = PeriodNumber, Topic = Description.Trim(), }); } Result = entry; } }