Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edf7510765 |
@@ -294,4 +294,44 @@ public class PlanningTabViewModelTests
|
||||
Assert.Single(sessions.GetByGroup(groupId));
|
||||
Assert.Equal("Für diese Stunde existiert bereits eine Sitzung.", notified);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateParticipationSession_AndereStundeAmSelbenTagBereitsVerknuepft_LegtKeineZweiteSitzungAn()
|
||||
{
|
||||
// Nutzer-Feedback: eine dritte Stunde am selben Tag (z.B. Vertretung) soll die bereits
|
||||
// bestehende Sitzung des Tages weiterverwenden statt eine zweite anzulegen.
|
||||
var groupId = Guid.NewGuid();
|
||||
var group = new LearningGroup { Id = groupId, Name = "Testgruppe" };
|
||||
var groups = new FakeGroups([group]);
|
||||
var units = new FakeUnits();
|
||||
var lessons = new FakeLessons();
|
||||
var sessions = new FakeSessions([]);
|
||||
var vm = new PlanningTabViewModel(units, lessons, groups, new FakeSubjects([]),
|
||||
new FakeCompetencyDomains(), TestSupport.BuildAiSettingsService(), sessions);
|
||||
vm.Initialize(groupId);
|
||||
|
||||
var unit = new Unit { GroupId = groupId, Title = "Optik" };
|
||||
units.Add(unit);
|
||||
var date = new DateOnly(2025, 9, 1);
|
||||
var doubleLesson = new Lesson
|
||||
{ UnitId = unit.Id, GroupId = groupId, Date = date, Topic = "Brechung" };
|
||||
var thirdLesson = new Lesson
|
||||
{ UnitId = unit.Id, GroupId = groupId, Date = date, Topic = "Vertretung" };
|
||||
lessons.Add(doubleLesson);
|
||||
lessons.Add(thirdLesson);
|
||||
|
||||
vm.RefreshPlanning(unit.Id, doubleLesson.Id);
|
||||
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == doubleLesson.Id);
|
||||
vm.CreateParticipationSessionCommand.Execute(null);
|
||||
|
||||
string? notified = null;
|
||||
vm.OnNotify = m => notified = m;
|
||||
vm.RefreshPlanning(unit.Id, thirdLesson.Id);
|
||||
vm.SelectedLesson = vm.Lessons.Single(l => l.Id == thirdLesson.Id);
|
||||
vm.CreateParticipationSessionCommand.Execute(null);
|
||||
|
||||
var created = Assert.Single(sessions.GetByGroup(groupId));
|
||||
Assert.Equal(doubleLesson.Id, created.LessonId);
|
||||
Assert.Equal("Für diesen Tag existiert bereits eine Sitzung.", notified);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,6 +256,30 @@ public sealed class SeatingPlanViewModelTests
|
||||
Assert.Single(sessions.GetByGroup(groupId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectOrCreateSessionForLesson_AndereStundeAmSelbenTagBereitsVerknuepft_LegtKeineZweiteSitzungAn()
|
||||
{
|
||||
// Nutzer-Feedback: eine dritte Stunde am selben Tag (z.B. durch Vertretung, eigene Lesson-
|
||||
// Id) soll die bereits bestehende Sitzung der Doppelstunde weiterverwenden statt eine
|
||||
// zweite, unabhängige Sitzung für denselben Tag anzulegen.
|
||||
var groupId = Guid.NewGuid();
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var doubleLesson = new Lesson { GroupId = groupId, Date = today, Topic = "Redox" };
|
||||
var thirdLesson = new Lesson { GroupId = groupId, Date = today, Topic = "Vertretung" };
|
||||
var existingSession = new ParticipationSession
|
||||
{ GroupId = groupId, Date = today, LessonId = doubleLesson.Id, Comment = "Redox" };
|
||||
var sessions = new FakeSessions([existingSession]);
|
||||
var plan = new SeatingPlan { GroupId = groupId, Name = "Standard", Rows = 1, Columns = 1 };
|
||||
var vm = new SeatingPlanTabViewModel(new FakeSeatingPlans([plan]), new FakeStudents([]),
|
||||
new FakeMemberships([]), sessions, new FakeEntries(), new FakeAspects());
|
||||
vm.Initialize(groupId, isReadOnly: false);
|
||||
|
||||
vm.SelectOrCreateSessionForLesson(thirdLesson);
|
||||
|
||||
Assert.Equal(existingSession.Id, vm.SelectedSession?.Id);
|
||||
Assert.Single(sessions.GetByGroup(groupId));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectOrCreateSessionForLesson_KeineSitzungVorhanden_LegtVerknuepfteAnUndWaehltSieAus()
|
||||
{
|
||||
|
||||
@@ -364,16 +364,25 @@ public partial class PlanningTabViewModel : ObservableObject
|
||||
/// Übernimmt Datum + Thema der Stunde in eine neue Mitarbeitssitzung (3.3.1) — verknüpft über
|
||||
/// das bisher ungenutzte Lesson.LessonId-Feld auf ParticipationSession, damit ein zweiter Klick
|
||||
/// auf dieselbe Stunde keine doppelte Sitzung anlegt, sondern nur darauf hinweist.
|
||||
///
|
||||
/// Prüft dabei zusätzlich auf JEDE bereits an diesem Tag bestehende Sitzung, nicht nur eine
|
||||
/// exakt mit `lesson.Id` verknüpfte (Nutzer-Feedback, analog
|
||||
/// <see cref="SeatingPlanTabViewModel.SelectOrCreateSessionForLesson"/>): kommt neben einer
|
||||
/// Doppelstunde noch eine dritte Stunde desselben Tages hinzu (eigene `Lesson`, z.B. durch
|
||||
/// Vertretung), soll das nicht zu einer zweiten Mitarbeitssitzung für den Tag führen.
|
||||
[RelayCommand(CanExecute = nameof(HasSelectedLesson))]
|
||||
private void CreateParticipationSession()
|
||||
{
|
||||
if (SelectedLesson is null) return;
|
||||
var lesson = SelectedLesson.Model;
|
||||
var existing = _participationSessions.GetByGroup(lesson.GroupId)
|
||||
.FirstOrDefault(s => s.LessonId == lesson.Id);
|
||||
var sessionsForGroup = _participationSessions.GetByGroup(lesson.GroupId);
|
||||
var existing = sessionsForGroup.FirstOrDefault(s => s.LessonId == lesson.Id)
|
||||
?? sessionsForGroup.FirstOrDefault(s => s.Date == lesson.Date);
|
||||
if (existing is not null)
|
||||
{
|
||||
OnNotify?.Invoke("Für diese Stunde existiert bereits eine Sitzung.");
|
||||
OnNotify?.Invoke(existing.LessonId == lesson.Id
|
||||
? "Für diese Stunde existiert bereits eine Sitzung."
|
||||
: "Für diesen Tag existiert bereits eine Sitzung.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -141,10 +141,18 @@ public partial class SeatingPlanTabViewModel : ObservableObject
|
||||
/// erlaubt: welche Stunde gemeint ist, steht durch die explizite Auswahl der Lehrkraft
|
||||
/// (Klick auf "Unterrichtsmodus starten" für genau diese Stunde) bereits unzweideutig fest -
|
||||
/// keine Geistersitzungs-Gefahr wie beim bloßen Öffnen eines Tabs.
|
||||
///
|
||||
/// Fällt bewusst auf JEDE an diesem Tag bereits bestehende Sitzung zurück, nicht nur auf eine
|
||||
/// exakt mit `lesson.Id` verknüpfte (Nutzer-Feedback): kommt neben einer Doppelstunde noch eine
|
||||
/// dritte Stunde am selben Tag hinzu (z.B. Vertretung, eigene `Lesson` mit eigener Id), soll
|
||||
/// keine zweite Mitarbeitssitzung für denselben Tag entstehen — die Lehrkraft passt stattdessen
|
||||
/// die Einschätzung der bereits bestehenden Sitzung an. Das entspricht dem Verhalten von
|
||||
/// <see cref="EnsureTodaySession"/>, das schon immer pro Tag statt pro Stunde arbeitet.
|
||||
/// </summary>
|
||||
public void SelectOrCreateSessionForLesson(Lesson lesson)
|
||||
{
|
||||
var existing = TodaySessions.FirstOrDefault(s => s.LessonId == lesson.Id);
|
||||
var existing = TodaySessions.FirstOrDefault(s => s.LessonId == lesson.Id)
|
||||
?? TodaySessions.FirstOrDefault(s => s.Date == lesson.Date);
|
||||
if (existing is not null) { SelectedSession = existing; return; }
|
||||
if (!IsEditable) return;
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
<ComboBoxItem Content="Heute"/><ComboBoxItem Content="Letzte 7 Tage"/><ComboBoxItem Content="Letzte 30 Tage"/>
|
||||
</ComboBox>
|
||||
<TextBlock Grid.Column="1" Text="von" VerticalAlignment="Center" Opacity="0.6"/>
|
||||
<CalendarDatePicker Grid.Column="2" SelectedDate="{Binding StartDate}" HorizontalAlignment="Stretch"/>
|
||||
<DatePicker Grid.Column="2" SelectedDate="{Binding StartDate}" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="3" Text="bis" VerticalAlignment="Center" Opacity="0.6"/>
|
||||
<CalendarDatePicker Grid.Column="4" SelectedDate="{Binding EndDate}" HorizontalAlignment="Stretch"/>
|
||||
<DatePicker Grid.Column="4" SelectedDate="{Binding EndDate}" HorizontalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto,Auto" ColumnSpacing="8">
|
||||
<TextBox Grid.Column="0" Text="{Binding StudentFilter, Mode=TwoWay}" PlaceholderText="Schüler*in filtern…"/>
|
||||
|
||||
@@ -45,9 +45,9 @@
|
||||
<ComboBoxItem Content="Heute"/><ComboBoxItem Content="Letzte 7 Tage"/><ComboBoxItem Content="Letzte 30 Tage"/>
|
||||
</ComboBox>
|
||||
<TextBlock Grid.Column="1" Text="von" VerticalAlignment="Center" Opacity="0.6"/>
|
||||
<CalendarDatePicker Grid.Column="2" SelectedDate="{Binding StartDate}" HorizontalAlignment="Stretch"/>
|
||||
<DatePicker Grid.Column="2" SelectedDate="{Binding StartDate}" HorizontalAlignment="Stretch"/>
|
||||
<TextBlock Grid.Column="3" Text="bis" VerticalAlignment="Center" Opacity="0.6"/>
|
||||
<CalendarDatePicker Grid.Column="4" SelectedDate="{Binding EndDate}" HorizontalAlignment="Stretch"/>
|
||||
<DatePicker Grid.Column="4" SelectedDate="{Binding EndDate}" HorizontalAlignment="Stretch"/>
|
||||
</Grid>
|
||||
<Grid Grid.Row="1" ColumnDefinitions="*,Auto,Auto" ColumnSpacing="8">
|
||||
<TextBox Grid.Column="0" Text="{Binding StudentFilter, Mode=TwoWay}" PlaceholderText="Schüler*in filtern…"/>
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
FontSize="12" Opacity="0.65" TextWrapping="Wrap"/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="8">
|
||||
<DatePicker SelectedDate="{Binding StartDate}"/>
|
||||
<CalendarDatePicker SelectedDate="{Binding StartDate}"/>
|
||||
<TextBlock Text="bis" VerticalAlignment="Center"/>
|
||||
<DatePicker SelectedDate="{Binding EndDate}"/>
|
||||
<CalendarDatePicker SelectedDate="{Binding EndDate}"/>
|
||||
<Button Content="Fehlzeiten laden" Command="{Binding LoadCommand}" IsEnabled="{Binding !Busy}"/>
|
||||
</StackPanel>
|
||||
<Grid Grid.Row="2" ColumnDefinitions="Auto,1.2*,1.2*,80,80,1.1*,1.1*" ColumnSpacing="8" Margin="4,0">
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<DataTemplate x:DataType="svc:UntisTeacherDto"><TextBlock Text="{Binding DisplayName}"/></DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
<CalendarDatePicker Grid.Column="1" SelectedDate="{Binding WeekDate}"/>
|
||||
<DatePicker Grid.Column="1" SelectedDate="{Binding WeekDate}"/>
|
||||
<Button Grid.Column="2" Content="Woche laden" Command="{Binding LoadCommand}" IsEnabled="{Binding !Busy}"/>
|
||||
</Grid>
|
||||
<ScrollViewer Grid.Row="2">
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
Text="Nur eigene WebUntis-Einträge (Benutzer = eigener Login). Zeilen ohne automatische Zuordnung bitte manuell einem/einer Schüler*in zuweisen. Bereits lokal vorhandene Einträge sind gesperrt."/>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Spacing="8">
|
||||
<CalendarDatePicker SelectedDate="{Binding StartDate}"/>
|
||||
<DatePicker SelectedDate="{Binding StartDate}"/>
|
||||
<TextBlock Text="bis" VerticalAlignment="Center"/>
|
||||
<CalendarDatePicker SelectedDate="{Binding EndDate}"/>
|
||||
<DatePicker SelectedDate="{Binding EndDate}"/>
|
||||
<Button Content="Klassenbucheinträge laden" Command="{Binding LoadCommand}" IsEnabled="{Binding !Busy}"/>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
@@ -1393,16 +1393,13 @@ Zugangsdaten; Schülerdaten, Fehlzeiten und der unverschlüsselte CSV-Report pas
|
||||
unentschuldigt übernommen werden; fremde/ganztägige Abwesenheiten erzeugen keine lokale Stunde.
|
||||
|
||||
**Nachtrag (September 2026, Nutzer-Feedback) — Zeitraum-Datumsfelder ließen sich nicht ändern:**
|
||||
Avalonias WinUI-artiger Spinner-`DatePicker` übernimmt Änderungen im Tag/Monat/Jahr-Flyout nur über
|
||||
einen separaten Häkchen-Button; ein Klick daneben verwirft sie stillschweigend. Alle verbliebenen
|
||||
Zeitraum-Datumsfelder mit diesem `DatePicker` auf `CalendarDatePicker` umgestellt (gleiche
|
||||
`DateTimeOffset?`-Bindung, kein Typwechsel nötig) — bereits das etablierte Muster für frei wählbare
|
||||
Einzeldatumsfelder in dieser Codebasis (`WithdrawStudentDialog`, `CreateLetterDialog`, 7.2.3), dort
|
||||
ohne dieses Problem, da ein Klick auf einen Kalendertag sofort übernimmt statt einen
|
||||
Bestätigungsschritt zu verlangen. Betroffen: [ClassTeacherRegisterView.axaml](LehrerApp.Desktop/Views/ClassTeacher/ClassTeacherRegisterView.axaml),
|
||||
[ClassTeacherAbsencesView.axaml](LehrerApp.Desktop/Views/ClassTeacher/ClassTeacherAbsencesView.axaml),
|
||||
[WebUntisTimetableImportDialog.axaml](LehrerApp.Desktop/Views/Planning/WebUntisTimetableImportDialog.axaml)
|
||||
und [WebUntisDocumentationComparisonDialog.axaml](LehrerApp.Desktop/Views/Students/WebUntisDocumentationComparisonDialog.axaml).
|
||||
die beiden `DatePicker` oben im Dialog ([WebUntisLessonAbsenceComparisonDialog.axaml](LehrerApp.Desktop/Views/Groups/WebUntisLessonAbsenceComparisonDialog.axaml))
|
||||
sind Avalonias WinUI-artiger Spinner-`DatePicker` — Änderungen im Tag/Monat/Jahr-Flyout werden nur
|
||||
über einen separaten Häkchen-Button im Flyout übernommen, ein Klick daneben verwirft sie
|
||||
stillschweigend. Ersetzt durch `CalendarDatePicker` (gleiche `DateTimeOffset?`-Bindung, kein
|
||||
Typwechsel nötig) — bereits das etablierte Muster für frei wählbare Einzeldatumsfelder in dieser
|
||||
Codebasis (`WithdrawStudentDialog`, `CreateLetterDialog`, 7.2.3), dort ohne dieses Problem, da
|
||||
ein Klick auf einen Kalendertag sofort übernimmt statt einen Bestätigungsschritt zu verlangen.
|
||||
|
||||
**Nachtrag zu 4.3, Fehlzeiten je Unterricht (August 2026):** Der ursprüngliche Fehlzeitenabgleich
|
||||
rief `getTimetableWithAbsences` ohne Element auf und bekam damit den kompletten Lehrer-Stundenplan
|
||||
@@ -2153,6 +2150,22 @@ folgenden Punkte gehören direkt in `LehrerApp.Desktop`:
|
||||
den vollen Verlaufsplan-Editor öffnen müssen. Neue kleine
|
||||
`TeachingModeHomeworkViewModel : ObservableObject`, da `TeachingModeViewModel` selbst (wie
|
||||
`LessonViewerViewModel`) keine Bindable-Basisklasse hat.
|
||||
|
||||
**Nachtrag (September 2026, Nutzer-Feedback) — zweite Sitzung bei zusätzlicher Stunde am
|
||||
selben Tag:** `SelectOrCreateSessionForLesson` prüfte bisher ausschließlich auf eine bereits
|
||||
exakt mit `lesson.Id` verknüpfte Sitzung. Kam neben einer Doppelstunde noch eine dritte
|
||||
Stunde desselben Tages hinzu (eigene `Lesson`-Id, z.B. durch Vertretung), fand die Methode
|
||||
keinen Treffer und legte eine zweite, unabhängige Mitarbeitssitzung für denselben Tag an —
|
||||
dadurch zeigten Unterrichtsmodus und der normale Mitarbeit-Tab der Gruppe (der über
|
||||
`ParticipationTabViewModel.LoadSessions()` unabhängig davon die nach Datum jüngste Sitzung
|
||||
wählt) je nach Reihenfolge unterschiedliche Sitzungen für denselben Tag. Nutzer-Entscheidung:
|
||||
strikt nach Datum statt nach Stunde gehen — eine zusätzliche Stunde am selben Tag (Vertretung
|
||||
o.ä.) bekommt keine eigene Sitzung, sondern die Lehrkraft passt die Einschätzung der bereits
|
||||
bestehenden Sitzung des Tages an. `SelectOrCreateSessionForLesson` fällt jetzt, wenn keine
|
||||
exakt verknüpfte Sitzung existiert, zusätzlich auf jede andere Sitzung desselben Tages
|
||||
zurück (Verhalten analog zu `EnsureTodaySession`, das schon immer pro Tag statt pro Stunde
|
||||
arbeitet). Dieselbe Lücke bestand in `PlanningTabViewModel.CreateParticipationSession`
|
||||
("Sitzung aus der Stunde erstellen", 3.3.1) — gleicher Fallback dort ergänzt.
|
||||
- [x] **4.5.24** Popup-Menü im Wochenraster für Unterrichtsansicht/Sitzplan/Planung/Planungsviewer
|
||||
(August 2026, Nutzer-Feedback, zweite Runde). Die erste Fassung hatte das Problem am
|
||||
falschen Ort gelöst — ein Dropdown in der "Heute"-**Tagesliste** (unten angedockt), obwohl
|
||||
|
||||
Reference in New Issue
Block a user