From a30aab0fa58e8ca5bd1b2dbb437a96bc90581658 Mon Sep 17 00:00:00 2001 From: Sebastian Hedtrich Date: Mon, 24 Aug 2026 11:43:19 +0200 Subject: [PATCH] Fix: doppelte Mitarbeits-Sitzung durch Unterrichtsmodus-Instanz MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EnsureTodaySession() prüfte nur den lokalen Instanz-Zustand, nicht das Repository. Da der Unterrichtsmodus eine eigene, per DI frisch aufgelöste SeatingPlanTabViewModel-Instanz nutzt (getrennt vom Sitzplan-Tab der Kursübersicht), legte eine spätere Bewertung im normalen Tab eine zweite, unverknüpfte Sitzung für denselben Tag an, statt die bereits über den Unterrichtsmodus verknüpfte zu erkennen. Co-Authored-By: Claude Sonnet 5 --- .../SeatingPlanViewModelTests.cs | 44 +++++++++++++++++++ .../Groups/SeatingPlanViewModels.cs | 25 ++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/LehrerApp.Desktop.Tests/SeatingPlanViewModelTests.cs b/LehrerApp.Desktop.Tests/SeatingPlanViewModelTests.cs index ea67586..571e333 100644 --- a/LehrerApp.Desktop.Tests/SeatingPlanViewModelTests.cs +++ b/LehrerApp.Desktop.Tests/SeatingPlanViewModelTests.cs @@ -543,6 +543,50 @@ public sealed class SeatingPlanViewModelTests Assert.Equal(session.Id, vm.SelectedSession?.Id); } + [Fact] + public async Task AssessStudent_SitzungBereitsUeberAndereInstanzVerknuepft_LegtKeineZweiteAn() + { + // Regressionstest: Der Unterrichtsmodus läuft auf einer eigenen, per DI frisch aufgelösten + // SeatingPlanTabViewModel-Instanz (TimetableView.ShowTeachingMode), getrennt von der + // Instanz im Sitzplan-Tab der Kursübersicht — beide teilen sich nur das Repository. Wurde + // (wie hier simuliert) die Sitzung bereits über den Unterrichtsmodus mit der Stunde + // verknüpft, bevor im echten Sitzplan-Tab bewertet wird, darf letzterer keine zweite, + // unverknüpfte "Sitzplan"-Sitzung für denselben Tag anlegen. + var groupId = Guid.NewGuid(); + var student = new Student { FirstName = "Anna", LastName = "A" }; + var plan = new SeatingPlan + { + GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1, + Assignments = [new SeatAssignment { StudentId = student.Id }], + }; + var sessions = new FakeSessions([]); + var memberships = new FakeMemberships([new GroupMembership { GroupId = groupId, StudentId = student.Id }]); + + // Instanz A: Sitzplan-Tab der Kursübersicht, bereits initialisiert (kein SelectedSession). + var tabVm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]), + memberships, sessions, new FakeEntries(), new FakeAspects()); + tabVm.Initialize(groupId, isReadOnly: false); + tabVm.OnAssessStudent = _ => Task.CompletedTask; + + // Instanz B: eigene, frische Instanz wie im Unterrichtsmodus — verknüpft die Sitzung mit + // der Stunde, ohne dass Instanz A davon erfährt. + var lesson = new Lesson { GroupId = groupId, Date = DateOnly.FromDateTime(DateTime.Today), Topic = "Redox" }; + var teachingModeVm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([student]), + memberships, sessions, new FakeEntries(), new FakeAspects()); + teachingModeVm.Initialize(groupId, isReadOnly: false); + teachingModeVm.SelectOrCreateSessionForLesson(lesson); + + var linked = Assert.Single(sessions.GetByGroup(groupId)); + Assert.Equal(lesson.Id, linked.LessonId); + + await tabVm.AssessStudent(tabVm.Seats.Single()); + + var session = Assert.Single(sessions.GetByGroup(groupId)); + Assert.Equal(linked.Id, session.Id); + Assert.Equal(lesson.Id, session.LessonId); + Assert.Equal(session.Id, tabVm.SelectedSession?.Id); + } + [Fact] public void ShuffleSeats_ErzeugtNeuenPlanMitVertauschtenPlaetzenUndBehaeltDenUrsprungsplan() { diff --git a/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs b/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs index 1174670..ac15337 100644 --- a/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs +++ b/LehrerApp.Desktop/ViewModels/Groups/SeatingPlanViewModels.cs @@ -93,14 +93,37 @@ public partial class SeatingPlanTabViewModel : ObservableObject /// Legt bei Bedarf die "Sitzung für heute" an, mit der Sitzplan-Bewertungen/-Markierungen /// verknüpft werden — aber erst, wenn tatsächlich etwas bewertet/markiert wird (AssessStudent/ /// ToggleSituationTag), nicht schon beim Öffnen des Tabs. + /// + /// Prüft dabei bewusst gegen das Repository und nicht nur gegen das lokal geladene + /// / : Der Unterrichtsmodus + /// () läuft auf einer eigenen, per DI frisch + /// aufgelösten -Instanz (siehe TimetableView.ShowTeachingMode), + /// getrennt von der Instanz, die im Sitzplan-Tab der Kursübersicht hängt. Eine dort bereits + /// heute angelegte (ggf. verknüpfte) Sitzung ist dieser Instanz also unbekannt, solange sie nur + /// im lokalen Feld nachschaut — das führte dazu, dass eine zweite, unverknüpfte "Sitzplan"- + /// Sitzung für denselben Tag entstand, sobald im echten Sitzplan-Tab bewertet wurde. private ParticipationSessionOption? EnsureTodaySession() { if (SelectedSession is not null) return SelectedSession; if (!IsEditable) return null; + var today = DateOnly.FromDateTime(DateTime.Today); + var existing = _sessions.GetByGroup(_groupId).FirstOrDefault(s => s.Date == today); + if (existing is not null) + { + var found = TodaySessions.FirstOrDefault(o => o.Id == existing.Id); + if (found is null) + { + found = new ParticipationSessionOption(existing); + TodaySessions.Add(found); + } + SelectedSession = found; + return found; + } + var created = new ParticipationSession { - GroupId = _groupId, Date = DateOnly.FromDateTime(DateTime.Today), Comment = "Sitzplan", + GroupId = _groupId, Date = today, Comment = "Sitzplan", }; _sessions.Save(created); var option = new ParticipationSessionOption(created);