feat: Komfort-Features für Unterrichtsplanung, Zeiterfassung und Dashboard
- Neue-Stunde-Dialog: Datum wird beim Anlegen anhand des Stundenplans
und der letzten Stunde der Einheit vorbelegt statt auf "heute"
(behebt eine falsch erkannte Doppelstunde, wenn "heute" nicht auf
den passenden Wochentag fiel).
- Zeiterfassung: Button "Unterrichtszeit heute übernehmen" schlägt
Start/Ende aus dem heutigen Stundenplan inkl. Puffer davor/danach vor.
- Dashboard: neue Kachel "Ungeplante Stunden" erinnert an Stunden ohne
Thema für heute/morgen, mit Opt-out je Gruppe ("Benötigt
Unterrichtsplanung"), Doppelstunden-Erkennung (keine doppelte Meldung
für die zweite Periode) und Berücksichtigung von Stundenausfall.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -26,14 +26,23 @@ public sealed class DashboardViewModelTests
|
||||
return new DashboardSettingsService(tempPath);
|
||||
}
|
||||
|
||||
private static SchoolCalendarSettingsService NewCalendarSettings()
|
||||
{
|
||||
var tempPath = System.IO.Path.Combine(
|
||||
System.IO.Path.GetTempPath(), $"lehrerapp-schoolcalendar-tests-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(tempPath);
|
||||
return new SchoolCalendarSettingsService(tempPath);
|
||||
}
|
||||
|
||||
private static DashboardViewModel BuildVm(LearningGroup group, Lesson lesson,
|
||||
FakeTimetableSlots? slots = null, PeriodScheduleService? periodSchedule = null,
|
||||
FakeExams? exams = null, FakeResults? results = null, FakeGrades? grades = null,
|
||||
FakeReportGrades? reportGrades = null, FakeMemberships? memberships = null,
|
||||
FakeWorkTasks? tasks = null, FakeStudents? students = null, FakeDocumentation? documentation = null,
|
||||
DashboardSettingsService? dashboardSettings = null)
|
||||
DashboardSettingsService? dashboardSettings = null, FakeSchoolHolidays? schoolHolidays = null,
|
||||
FakeLessons? lessons = null, FakeSubstitutionEntries? substitutions = null)
|
||||
{
|
||||
var lessons = new FakeLessons();
|
||||
lessons ??= new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
return new DashboardViewModel(
|
||||
new FakeGroups([group]), new FakeSubjects([]), lessons,
|
||||
@@ -42,7 +51,9 @@ public sealed class DashboardViewModelTests
|
||||
tasks ?? new FakeWorkTasks(), new FakeSessions([]), new FakeEntries(),
|
||||
students ?? new FakeStudents([]), documentation ?? new FakeDocumentation(),
|
||||
slots ?? new FakeTimetableSlots(), periodSchedule ?? NewPeriodSchedule(),
|
||||
new AttendanceBalanceService(), new SchoolYearService(), dashboardSettings ?? NewDashboardSettings());
|
||||
new AttendanceBalanceService(), new SchoolYearService(), dashboardSettings ?? NewDashboardSettings(),
|
||||
schoolHolidays ?? new FakeSchoolHolidays(), new PublicHolidayService(), NewCalendarSettings(),
|
||||
substitutions ?? new FakeSubstitutionEntries());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -227,4 +238,119 @@ public sealed class DashboardViewModelTests
|
||||
Assert.False(saved.Single(c => c.Key == "today").IsVisible);
|
||||
Assert.True(saved.FindIndex(c => c.Key == "tasks") > 1);
|
||||
}
|
||||
|
||||
// ── Ungeplante Stunden (Nutzer-Feedback) ──────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void UnplannedLessons_StundeplanSlotOhneLessonMitThema_ErscheintInDerListe()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
|
||||
// "lesson" liegt bewusst weit in der Vergangenheit, damit sie nicht in den heute/morgen-
|
||||
// Vorgriff fällt und den Test verfälscht.
|
||||
var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 1, Topic = "Alt" };
|
||||
|
||||
var vm = BuildVm(group, pastLesson, slots: slots);
|
||||
|
||||
var item = Assert.Single(vm.UnplannedLessons);
|
||||
Assert.Equal(group.Id, item.GroupId);
|
||||
Assert.Equal(1, item.PeriodNumber);
|
||||
Assert.Equal("Heute", item.DateDisplay);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnplannedLessons_LessonMitThemaVorhanden_ErscheintNicht()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 1, Topic = "Redoxreaktionen" };
|
||||
|
||||
var vm = BuildVm(group, lesson, slots: slots);
|
||||
|
||||
Assert.Empty(vm.UnplannedLessons);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnplannedLessons_GruppeOhnePlanungsbedarf_WirdIgnoriert()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "Klassenrat", RequiresLessonPlanning = false };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
|
||||
var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 1, Topic = "Alt" };
|
||||
|
||||
var vm = BuildVm(group, pastLesson, slots: slots);
|
||||
|
||||
Assert.Empty(vm.UnplannedLessons);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnplannedLessons_Feiertag_WirdUebersprungen()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 1 });
|
||||
var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 1, Topic = "Alt" };
|
||||
var schoolHolidays = new FakeSchoolHolidays();
|
||||
schoolHolidays.Add(new SchoolHoliday { Name = "Ferien", StartDate = today, EndDate = today.AddDays(1) });
|
||||
|
||||
var vm = BuildVm(group, pastLesson, slots: slots, schoolHolidays: schoolHolidays);
|
||||
|
||||
Assert.Empty(vm.UnplannedLessons);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnplannedLessons_DoppelstundeMitThemaAmErstenSlot_ZweiterSlotWirdNichtGemeldet()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 3 });
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 4 });
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "Redox" };
|
||||
|
||||
var vm = BuildVm(group, lesson, slots: slots);
|
||||
|
||||
Assert.Empty(vm.UnplannedLessons);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnplannedLessons_DoppelstundeOhneThema_BeideSlotsWerdenGemeldet()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 3 });
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 4 });
|
||||
var lesson = new Lesson { GroupId = group.Id, Date = today, LessonNumber = 3, Topic = "" };
|
||||
|
||||
var vm = BuildVm(group, lesson, slots: slots);
|
||||
|
||||
Assert.Equal(2, vm.UnplannedLessons.Count);
|
||||
Assert.Contains(vm.UnplannedLessons, i => i.PeriodNumber == 3);
|
||||
Assert.Contains(vm.UnplannedLessons, i => i.PeriodNumber == 4);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnplannedLessons_StundenausfallEingetragen_WirdNichtGemeldet()
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var group = new LearningGroup { Name = "9c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 5 });
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = today.DayOfWeek, PeriodNumber = 6 });
|
||||
var pastLesson = new Lesson { GroupId = group.Id, Date = today.AddYears(-1), LessonNumber = 5, Topic = "Alt" };
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry { Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 5 });
|
||||
substitutions.Add(new SubstitutionEntry { Date = today, Kind = SubstitutionKind.Cancelled, PeriodNumber = 6 });
|
||||
|
||||
var vm = BuildVm(group, pastLesson, slots: slots, substitutions: substitutions);
|
||||
|
||||
Assert.Empty(vm.UnplannedLessons);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user