Unterrichtsplanung: Serienerzeugung, Stundenraster, Aufsichten & Vertretung (Kapitel 4.2/4.3 Nachtrag)
Stunden serienweise aus dem Stundenplan erzeugen (4.2.5); Stundenraster (Uhrzeiten je Stunde) in den Einstellungen mit Zeitbedarf-Rückmeldung im Verlaufsplan-Editor; wiederkehrende Pausenaufsicht; neuer "Vertretung eintragen"-Dialog für einmalige Vertretungsaufsicht, Vertretungsstunde, Sondereinsätze (Ausflüge, Berufsmessen) und schlichten Stundenausfall. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<LearningGroup> OwnGroups { get; } = [];
|
||||
public ObservableCollection<Unit> 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user