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:
@@ -10,7 +10,8 @@ public sealed class TimetableViewModelTests
|
||||
private static TimetableViewModel BuildViewModel(
|
||||
FakeTimetableSlots slots, FakeGroups groups, FakeSchoolHolidays? holidays = null,
|
||||
FakeSubjects? subjects = null, FakeLessons? lessons = null, FakeExams? exams = null,
|
||||
SchoolCalendarSettingsService? calendarSettings = null)
|
||||
SchoolCalendarSettingsService? calendarSettings = null,
|
||||
FakeSupervisionDuties? supervisionDuties = null, FakeSubstitutionEntries? substitutions = null)
|
||||
{
|
||||
// Bewusst kein "using": SchoolCalendarSettingsService liest den Pfad nur bei Bedarf
|
||||
// (SetState), das Verzeichnis muss über die Lebensdauer des ViewModels bestehen bleiben.
|
||||
@@ -22,7 +23,8 @@ public sealed class TimetableViewModelTests
|
||||
lessons ?? new FakeLessons(), exams ?? new FakeExams([]),
|
||||
holidays ?? new FakeSchoolHolidays(),
|
||||
calendarSettings ?? new SchoolCalendarSettingsService(tempPath),
|
||||
new PublicHolidayService(), new SchoolYearService());
|
||||
new PublicHolidayService(), new SchoolYearService(),
|
||||
supervisionDuties ?? new FakeSupervisionDuties(), substitutions ?? new FakeSubstitutionEntries());
|
||||
}
|
||||
|
||||
/// Nächstes Datum ab (inkl.) <paramref name="from"/>, das auf einen Wochentag Mo-Fr fällt —
|
||||
@@ -410,4 +412,334 @@ public sealed class TimetableViewModelTests
|
||||
var cell = vm.Cells.Single(c => c.IsSlotCell && c.Weekday == weekday.DayOfWeek && c.PeriodNumber == 1);
|
||||
Assert.False(cell.HasBadge);
|
||||
}
|
||||
|
||||
// ── Aufsicht: wiederkehrend (Bearbeiten-Raster) ──────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Load_AufsichtEingetragen_ZeigtSupervisionRowMitOrt()
|
||||
{
|
||||
var duties = new FakeSupervisionDuties();
|
||||
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
|
||||
|
||||
var label = vm.Cells.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 2."));
|
||||
Assert.NotNull(label);
|
||||
var mondayCell = vm.Cells
|
||||
.SkipWhile(c => c != label).Skip(1) // erste Zelle nach dem Label = Montag
|
||||
.First();
|
||||
Assert.True(mondayCell.IsSupervisionCell);
|
||||
Assert.Equal("Pausenhof", mondayCell.SupervisionLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_KeineAufsichtEingetragen_KeineSupervisionRow()
|
||||
{
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
|
||||
|
||||
Assert.DoesNotContain(vm.Cells, c => c.IsSupervisionRow);
|
||||
}
|
||||
|
||||
// ── Aufsicht + Vertretung im Wochenraster ────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_ZeigtWiederkehrendeAufsicht()
|
||||
{
|
||||
var duties = new FakeSupervisionDuties();
|
||||
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
|
||||
|
||||
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 2."));
|
||||
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
|
||||
Assert.Equal("Pausenhof", mondayCell.SupervisionLocation);
|
||||
Assert.False(mondayCell.IsSubstitutionSupervision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_VertretungsaufsichtUeberschreibtRegulaereAnzeige()
|
||||
{
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var duties = new FakeSupervisionDuties();
|
||||
duties.Add(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.Supervision, AfterPeriod = 2,
|
||||
Description = "Vertretung für Hr. Müller",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]),
|
||||
supervisionDuties: duties, substitutions: substitutions);
|
||||
|
||||
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 2."));
|
||||
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
|
||||
Assert.Equal("Vertretung für Hr. Müller", mondayCell.SupervisionLocation);
|
||||
Assert.True(mondayCell.IsSubstitutionSupervision);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_VertretungsaufsichtOhneRegulaereAufsicht_ZeigtEigeneRow()
|
||||
{
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.Supervision, AfterPeriod = 3,
|
||||
Description = "Vertretung für Fr. Schmidt",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 3."));
|
||||
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
|
||||
Assert.Equal("Vertretung für Fr. Schmidt", mondayCell.SupervisionLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_VertretungsstundeUeberschreibtNormaleAnzeige()
|
||||
{
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.Lesson, PeriodNumber = 1,
|
||||
GroupLabel = "8a", Description = "Vertretung Erdkunde",
|
||||
});
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
|
||||
|
||||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
|
||||
Assert.True(cell.IsSubstitutionLesson);
|
||||
Assert.Equal("8a", cell.GroupName);
|
||||
Assert.Equal("Vertretung Erdkunde", cell.Topic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_VertretungsstundeOhnePassendenSlot_ErscheintTrotzdem()
|
||||
{
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.Lesson, PeriodNumber = 5,
|
||||
GroupLabel = "8a", Description = "Vertretung Erdkunde",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 5);
|
||||
Assert.True(cell.IsSubstitutionLesson);
|
||||
Assert.True(cell.IsAssigned);
|
||||
}
|
||||
|
||||
// ── Aufsicht + Vertretung in der Tagesliste ("Heute") ────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Load_Heute_ZeigtHeutigeAufsicht()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var duties = new FakeSupervisionDuties();
|
||||
duties.Add(new SupervisionDuty { Weekday = today.DayOfWeek, AfterPeriod = 2, Location = "Pausenhof" });
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), supervisionDuties: duties);
|
||||
|
||||
var item = Assert.Single(vm.TodaySupervisions);
|
||||
Assert.Equal("Pausenhof", item.Description);
|
||||
Assert.False(item.IsSubstitution);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Heute_VertretungsstundeErsetztNormaleStundenanzeige()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = today, Kind = SubstitutionKind.Lesson, PeriodNumber = 1,
|
||||
GroupLabel = "8a", Description = "Vertretung Erdkunde",
|
||||
});
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
|
||||
|
||||
var item = Assert.Single(vm.TodayItems);
|
||||
Assert.True(item.IsSubstitution);
|
||||
Assert.Equal("8a", item.GroupName);
|
||||
Assert.Equal("Vertretung Erdkunde", item.LessonTopic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpenGroup_MitLeererGroupId_RuftDelegateNichtAuf()
|
||||
{
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]));
|
||||
var called = false;
|
||||
vm.OnNavigateToGroup = _ => called = true;
|
||||
|
||||
vm.OpenGroupCommand.Execute(Guid.Empty);
|
||||
|
||||
Assert.False(called);
|
||||
}
|
||||
|
||||
// ── Sondereinsätze (Ausflüge, Berufsmessen, ...) ─────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_GanztaegigerSondereinsatz_UeberdecktAllePeriodenDesTages()
|
||||
{
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = true,
|
||||
GroupLabel = "8a", Description = "Ausflug ins Museum",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
var period1 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
|
||||
var period7 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 7);
|
||||
Assert.True(period1.IsSpecialAssignment);
|
||||
Assert.True(period7.IsSpecialAssignment);
|
||||
Assert.Equal("Ausflug ins Museum", period1.Topic);
|
||||
Assert.Equal("8a", period1.GroupName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_SondereinsatzMitStundenbereich_NurDortSichtbar()
|
||||
{
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = false,
|
||||
FromPeriod = 3, ToPeriod = 4, Description = "Berufsmesse",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
var period2 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 2);
|
||||
var period3 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 3);
|
||||
var period4 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 4);
|
||||
var period5 = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 5);
|
||||
Assert.False(period2.IsSpecialAssignment);
|
||||
Assert.True(period3.IsSpecialAssignment);
|
||||
Assert.True(period4.IsSpecialAssignment);
|
||||
Assert.False(period5.IsSpecialAssignment);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_SondereinsatzOhneGruppe_ZeigtLeeresGroupName()
|
||||
{
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = true, Description = "Berufsmesse",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 1);
|
||||
Assert.Equal("", cell.GroupName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Heute_ZeigtGanztaegigenSondereinsatz()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = today, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = true,
|
||||
GroupLabel = "8a", Description = "Ausflug ins Museum",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
var item = Assert.Single(vm.TodaySpecialAssignments);
|
||||
Assert.Equal("Ganztägig", item.PeriodLabel);
|
||||
Assert.Equal("Ausflug ins Museum", item.Description);
|
||||
Assert.Equal("8a", item.GroupLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Heute_ZeigtSondereinsatzMitStundenbereich()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = today, Kind = SubstitutionKind.SpecialAssignment, IsAllDay = false,
|
||||
FromPeriod = 3, ToPeriod = 4, Description = "Berufsmesse",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
var item = Assert.Single(vm.TodaySpecialAssignments);
|
||||
Assert.Equal("3.–4. Stunde", item.PeriodLabel);
|
||||
}
|
||||
|
||||
// ── Stundenausfall ────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_Ausfall_ZeigtGruppeUndFachAusDemStundenplan()
|
||||
{
|
||||
var subject = new Subject { Name = "Naturwissenschaften", ShortName = "NAT" };
|
||||
var group = new LearningGroup { Name = "6a", SubjectId = subject.Id };
|
||||
var date = DateInCurrentWeek(DayOfWeek.Monday);
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 2 });
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = date, Kind = SubstitutionKind.Cancelled, PeriodNumber = 2, Description = "6a auf Klassenfahrt",
|
||||
});
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), subjects: new FakeSubjects([subject]), substitutions: substitutions);
|
||||
|
||||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 2);
|
||||
Assert.True(cell.IsCancelled);
|
||||
Assert.Equal("NAT", cell.SubjectLabel);
|
||||
Assert.Equal("6a", cell.GroupName);
|
||||
Assert.Equal("6a auf Klassenfahrt", cell.Topic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_KeinAusfall_ZeigtNormaleStunde()
|
||||
{
|
||||
var group = new LearningGroup { Name = "6a" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 2 });
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]));
|
||||
|
||||
var cell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Monday && c.PeriodNumber == 2);
|
||||
Assert.False(cell.IsCancelled);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Heute_Ausfall_ErsetztNormaleStundenanzeige()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "6a" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 2 });
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 2, Description = "6a auf Klassenfahrt",
|
||||
});
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
|
||||
|
||||
var item = Assert.Single(vm.TodayItems);
|
||||
Assert.True(item.IsCancelled);
|
||||
Assert.Equal("6a", item.GroupName);
|
||||
Assert.Equal("6a auf Klassenfahrt", item.LessonTopic);
|
||||
Assert.Equal(group.Id, item.GroupId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Heute_AusfallOhneGrund_LessonTopicBleibtLeer()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "6a" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 2 });
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry { Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 2 });
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), substitutions: substitutions);
|
||||
|
||||
var item = Assert.Single(vm.TodayItems);
|
||||
Assert.False(item.HasLessonTopic);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user