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:
@@ -1,4 +1,5 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
@@ -6,8 +7,18 @@ namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class LessonDialogViewModelTests
|
||||
{
|
||||
private static LessonDialogViewModel BuildVm(Guid unitId, Guid groupId, Lesson? editing = null) =>
|
||||
private static PeriodScheduleService NewPeriodSchedule()
|
||||
{
|
||||
var tempPath = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(), $"lehrerapp-lessondialogvm-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempPath);
|
||||
return new PeriodScheduleService(tempPath);
|
||||
}
|
||||
|
||||
private static LessonDialogViewModel BuildVm(Guid unitId, Guid groupId, Lesson? editing = null,
|
||||
FakeTimetableSlots? slots = null, PeriodScheduleService? periodSchedule = null) =>
|
||||
new(new FakeLessons(), new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
|
||||
slots ?? new FakeTimetableSlots(), periodSchedule ?? NewPeriodSchedule(),
|
||||
unitId, groupId, [], [], editing);
|
||||
|
||||
[Fact]
|
||||
@@ -71,6 +82,7 @@ public sealed class LessonDialogViewModelTests
|
||||
{
|
||||
var codes = new FakeShorthandCodes([new ShorthandCode { Code = "Tb" }, new ShorthandCode { Code = "SH" }]);
|
||||
var vm = new LessonDialogViewModel(new FakeLessons(), codes, new FakeAlternativeLessonPaths([]),
|
||||
new FakeTimetableSlots(), NewPeriodSchedule(),
|
||||
Guid.NewGuid(), Guid.NewGuid(), [], ["Plenum", "LDE", "Tb"], null); // "Tb" doppelt (Katalog + Historie), soll nur einmal erscheinen
|
||||
|
||||
Assert.Equal(["LDE", "Plenum", "SH", "Tb"], vm.ShorthandSuggestions);
|
||||
@@ -133,7 +145,7 @@ public sealed class LessonDialogViewModelTests
|
||||
var groupId = Guid.NewGuid();
|
||||
var lessons = new FakeLessons();
|
||||
var vm = new LessonDialogViewModel(lessons, new FakeShorthandCodes([]), new FakeAlternativeLessonPaths([]),
|
||||
unitId, groupId, [], [], null)
|
||||
new FakeTimetableSlots(), NewPeriodSchedule(), unitId, groupId, [], [], null)
|
||||
{
|
||||
Topic = "Brechung", DateText = "01.09.2025", StartTimeText = "11:45",
|
||||
};
|
||||
@@ -230,9 +242,140 @@ public sealed class LessonDialogViewModelTests
|
||||
};
|
||||
|
||||
var vm = new LessonDialogViewModel(new FakeLessons(), new FakeShorthandCodes([]), alternativePaths,
|
||||
Guid.NewGuid(), Guid.NewGuid(), [], [], editing);
|
||||
new FakeTimetableSlots(), NewPeriodSchedule(), Guid.NewGuid(), Guid.NewGuid(), [], [], editing);
|
||||
|
||||
Assert.True(vm.Phases[0].HasAlternativePath);
|
||||
Assert.Equal("Kurzversion", vm.Phases[0].AlternativePathName);
|
||||
}
|
||||
|
||||
// ── Zeitbedarf-Rückmeldung (4.2.2 Nachtrag: Stundenraster) ───────────────────
|
||||
|
||||
[Fact]
|
||||
public void RecomputeTimeBudget_EinzelneStunde_ZeigtVerfuegbareZeitAusStundenraster()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var date = new DateOnly(2025, 9, 1);
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(9, 45), End = new TimeOnly(10, 30) }]);
|
||||
var vm = BuildVm(Guid.NewGuid(), groupId, periodSchedule: periodSchedule);
|
||||
vm.DateText = date.ToString("dd.MM.yyyy");
|
||||
vm.LessonNumber = 3;
|
||||
vm.AddPhaseCommand.Execute(null);
|
||||
vm.Phases[0].DurationMinutes = 40;
|
||||
|
||||
Assert.True(vm.HasTimeBudgetInfo);
|
||||
Assert.Contains("40 von 45 Minuten geplant", vm.TimeBudgetLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecomputeTimeBudget_Doppelstunde_AddiertBeidePerioden()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var date = new DateOnly(2025, 9, 1);
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = date.DayOfWeek, PeriodNumber = 3 });
|
||||
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = date.DayOfWeek, PeriodNumber = 4 });
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([
|
||||
new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(9, 45), End = new TimeOnly(10, 30) },
|
||||
new PeriodTimeEntry { PeriodNumber = 4, Start = new TimeOnly(10, 30), End = new TimeOnly(11, 15) },
|
||||
]);
|
||||
var vm = BuildVm(Guid.NewGuid(), groupId, slots: slots, periodSchedule: periodSchedule);
|
||||
vm.DateText = date.ToString("dd.MM.yyyy");
|
||||
vm.LessonNumber = 3;
|
||||
vm.AddPhaseCommand.Execute(null);
|
||||
vm.Phases[0].DurationMinutes = 84;
|
||||
|
||||
Assert.True(vm.HasTimeBudgetInfo);
|
||||
Assert.Contains("84 von 90 Minuten geplant", vm.TimeBudgetLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecomputeTimeBudget_FolgeperiodeGehoertAndererGruppe_WirdNichtMitgezaehlt()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var otherGroupId = Guid.NewGuid();
|
||||
var date = new DateOnly(2025, 9, 1);
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = groupId, Weekday = date.DayOfWeek, PeriodNumber = 3 });
|
||||
slots.Add(new TimetableSlot { GroupId = otherGroupId, Weekday = date.DayOfWeek, PeriodNumber = 4 });
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([
|
||||
new PeriodTimeEntry { PeriodNumber = 3, Start = new TimeOnly(9, 45), End = new TimeOnly(10, 30) },
|
||||
new PeriodTimeEntry { PeriodNumber = 4, Start = new TimeOnly(10, 30), End = new TimeOnly(11, 15) },
|
||||
]);
|
||||
var vm = BuildVm(Guid.NewGuid(), groupId, slots: slots, periodSchedule: periodSchedule);
|
||||
vm.DateText = date.ToString("dd.MM.yyyy");
|
||||
vm.LessonNumber = 3;
|
||||
vm.AddPhaseCommand.Execute(null);
|
||||
vm.Phases[0].DurationMinutes = 40;
|
||||
|
||||
Assert.Contains("40 von 45 Minuten geplant", vm.TimeBudgetLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecomputeTimeBudget_FarbeGruenImZielbereich()
|
||||
{
|
||||
var date = new DateOnly(2025, 9, 1);
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]);
|
||||
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
|
||||
vm.DateText = date.ToString("dd.MM.yyyy");
|
||||
vm.LessonNumber = 1;
|
||||
vm.AddPhaseCommand.Execute(null);
|
||||
vm.Phases[0].DurationMinutes = 42; // 42/45 ≈ 93 % — Zielbereich
|
||||
|
||||
Assert.Equal("#43A047", vm.TimeBudgetColorHex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecomputeTimeBudget_FarbeDunkelrotBeiDeutlicherUeberplanung()
|
||||
{
|
||||
var date = new DateOnly(2025, 9, 1);
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 1, Start = new TimeOnly(8, 0), End = new TimeOnly(8, 45) }]);
|
||||
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
|
||||
vm.DateText = date.ToString("dd.MM.yyyy");
|
||||
vm.LessonNumber = 1;
|
||||
vm.AddPhaseCommand.Execute(null);
|
||||
vm.Phases[0].DurationMinutes = 60; // 60/45 ≈ 133 % — deutlich überplant
|
||||
|
||||
Assert.Equal("#B71C1C", vm.TimeBudgetColorHex);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LessonNumberGeaendert_UebernimmtBeginnAusStundenraster_WennNochKeinerEingetragen()
|
||||
{
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 5, Start = new TimeOnly(11, 45), End = new TimeOnly(12, 30) }]);
|
||||
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
|
||||
|
||||
vm.LessonNumber = 5;
|
||||
|
||||
Assert.Equal("11:45", vm.StartTimeText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LessonNumberGeaendert_UeberschreibtBereitsEingetragenenBeginnNicht()
|
||||
{
|
||||
var periodSchedule = NewPeriodSchedule();
|
||||
periodSchedule.SetPeriods([new PeriodTimeEntry { PeriodNumber = 5, Start = new TimeOnly(11, 45), End = new TimeOnly(12, 30) }]);
|
||||
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid(), periodSchedule: periodSchedule);
|
||||
vm.StartTimeText = "09:00";
|
||||
|
||||
vm.LessonNumber = 5;
|
||||
|
||||
Assert.Equal("09:00", vm.StartTimeText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RecomputeTimeBudget_OhneStundenrasterEintrag_KeineRueckmeldung()
|
||||
{
|
||||
var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid()); // leeres PeriodScheduleService
|
||||
vm.DateText = "01.09.2025";
|
||||
vm.LessonNumber = 1;
|
||||
vm.AddPhaseCommand.Execute(null);
|
||||
|
||||
Assert.False(vm.HasTimeBudgetInfo);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user