From fb38b6bb04269fcec99d06cd2479a05d3b4db241 Mon Sep 17 00:00:00 2001 From: Baddi86 Date: Mon, 9 Nov 2026 21:50:36 +0100 Subject: [PATCH] Fix unit persistence before saving timetable lessons --- .../LessonDialogViewModelTests.cs | 37 +++++++++++++++++++ .../ViewModels/Groups/PlanningViewModels.cs | 5 +++ .../Views/Groups/LessonDialog.axaml.cs | 1 + .../Views/Planning/TimetableView.axaml.cs | 6 ++- TODO.md | 5 +++ 5 files changed, 53 insertions(+), 1 deletion(-) diff --git a/LehrerApp.Desktop.Tests/LessonDialogViewModelTests.cs b/LehrerApp.Desktop.Tests/LessonDialogViewModelTests.cs index 6cef547..73b420d 100644 --- a/LehrerApp.Desktop.Tests/LessonDialogViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/LessonDialogViewModelTests.cs @@ -8,6 +8,43 @@ namespace LehrerApp.Desktop.Tests; public sealed class LessonDialogViewModelTests { + [Fact] + public void Save_PersistsParentBeforeLesson() + { + var lessons = new FakeLessons(); + var unitId = Guid.NewGuid(); + var vm = new LessonDialogViewModel(lessons, new FakeShorthandCodes([]), + new FakeAlternativeLessonPaths([]), new FakeTimetableSlots(), NewPeriodSchedule(), + new FakeAttachmentStorage(), unitId, Guid.NewGuid(), "10c", "Chemie", [], [], null); + vm.Topic = "Test"; + vm.DateText = "09.11.2026"; + var called = false; + vm.BeforeSave = () => + { + Assert.Empty(lessons.GetByUnit(unitId)); + called = true; + }; + + vm.SaveCommand.Execute(null); + + Assert.True(called); + Assert.Equal(unitId, Assert.Single(lessons.GetByUnit(unitId)).UnitId); + } + + [Fact] + public void Save_InvalidInputDoesNotPersistParent() + { + var vm = BuildVm(Guid.NewGuid(), Guid.NewGuid()); + var called = false; + vm.BeforeSave = () => called = true; + vm.DateText = "invalid"; + + vm.SaveCommand.Execute(null); + + Assert.False(called); + Assert.Null(vm.Result); + } + private static PeriodScheduleService NewPeriodSchedule() { var tempPath = System.IO.Path.Combine( diff --git a/LehrerApp.Desktop/ViewModels/Groups/PlanningViewModels.cs b/LehrerApp.Desktop/ViewModels/Groups/PlanningViewModels.cs index a79ff12..2414e34 100644 --- a/LehrerApp.Desktop/ViewModels/Groups/PlanningViewModels.cs +++ b/LehrerApp.Desktop/ViewModels/Groups/PlanningViewModels.cs @@ -789,6 +789,10 @@ public partial class LessonDialogViewModel : ObservableObject // Für die KI-Unterstützung mit Fokus auf genau diese Stunde (4.5.22) — nur für bereits // gespeicherte Stunden sinnvoll, eine gerade erst angelegte, noch ungespeicherte Stunde hat // keine echte Id, auf die sich die KI beziehen könnte. + // Persist a pending parent only after validation and before writing its first lesson. + public Action? BeforeSave { get; set; } + public void EnsureParentSaved() => BeforeSave?.Invoke(); + public Guid UnitId => _unitId; public Guid GroupId => _groupId; public Lesson? EditingLesson => _editingLesson; @@ -1079,6 +1083,7 @@ public partial class LessonDialogViewModel : ObservableObject if (!valid) return; + EnsureParentSaved(); Result = _editingLesson ?? new Lesson { UnitId = _unitId, GroupId = _groupId }; Result.UnitId = _unitId; Result.GroupId = _groupId; diff --git a/LehrerApp.Desktop/Views/Groups/LessonDialog.axaml.cs b/LehrerApp.Desktop/Views/Groups/LessonDialog.axaml.cs index aa85a1e..e4417f8 100644 --- a/LehrerApp.Desktop/Views/Groups/LessonDialog.axaml.cs +++ b/LehrerApp.Desktop/Views/Groups/LessonDialog.axaml.cs @@ -265,6 +265,7 @@ public partial class LessonDialog : Window chosen = picked; } + vm.EnsureParentSaved(); chosen.UnitId = vm.UnitId; chosen.Date = date; chosen.LessonNumber = vm.LessonNumber; diff --git a/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs b/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs index 76dbcaa..183534e 100644 --- a/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs +++ b/LehrerApp.Desktop/Views/Planning/TimetableView.axaml.cs @@ -121,10 +121,14 @@ public partial class TimetableView : UserControl App.Services.GetRequiredService(), unit.Id, group.Id, group.Name, subject?.Name ?? "", materials, shorthands, editingLesson: null, suggestedDate: request.Date, suggestedPeriod: request.PeriodNumber); + if (pickerVm.ResultIsNew) + lessonVm.BeforeSave = () => + { + if (units.GetById(unit.Id) is null) units.Save(unit); + }; var lessonDialog = new LessonDialog { DataContext = lessonVm }; if (await lessonDialog.ShowDialog(owner) && lessonVm.Result is not null) { - if (pickerVm.ResultIsNew) units.Save(unit); (DataContext as TimetableViewModel)?.Load(); App.Services.GetRequiredService().ShowSuccess("Stunde angelegt."); } diff --git a/TODO.md b/TODO.md index ee74717..36939a5 100644 --- a/TODO.md +++ b/TODO.md @@ -2541,6 +2541,11 @@ zurückgestellt): vorgeschalteter Dialog sortiert laufende Einheiten zuerst, lässt aber auch direkt eine neue Einheit anlegen; damit ist die frühere offene Unit-Frage ohne stillschweigende Zuordnung gelöst. Neue Direktanlagen starten als `Entwurf`. + Nachtrag (09.11.2026): Neue Einheit erst nach erfolgreicher Eingabevalidierung, aber vor + der ersten Stunde speichern (auch beim Verknuepfen einer vorhandenen Stunde). Damit + entfaellt das verwaiste Zeitfenster bis zum Schliessen des Dialogs; Abbruch bleibt ohne + neue Einheit. 119 betroffene Desktop-Tests bestanden. Bereits verwaiste Stunden werden + dadurch nicht automatisch neu zugeordnet. - [x] **4.5.3** Aus diesem Lesson-Viewer heraus weiter verzweigen können: in die Zeugnisnote/ Bewertung der Gruppe, und in die Schnelldialoge für Mitarbeit sowie Anwesenheit/Hausaufgaben. **Umsetzung:** Zwei neue Buttons im Footer von `LessonViewerDialog` ("Zur Mitarbeit"/