Stundenplan: verwaiste Stunden per "FixIt"-Button verknüpfen
CI / build-and-test (push) Canceled after 0s

Der Stundenplan erkennt eine Stunde nur über Datum+Stundennummer
(kein gespeichertes Verknüpfungsfeld). Per JSON-Import oder KI ohne
Stundenplan-Bezug angelegte Stunden ohne passende Stundennummer
tauchten dort nie auf - ein Klick auf den Termin bot nur "neu anlegen"
an und hätte eine Dublette erzeugt. Neuer Button im Anlegen-Dialog
sucht stattdessen nach einer passenden vorhandenen Stunde und hängt
sie auf den geklickten Termin um.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 21:27:06 +02:00
co-authored by Claude Sonnet 5
parent 3cccb3226c
commit 94946ffefb
6 changed files with 265 additions and 0 deletions
@@ -745,8 +745,10 @@ public partial class LessonDialogViewModel : ObservableObject
// gespeicherte Stunden sinnvoll, eine gerade erst angelegte, noch ungespeicherte Stunde hat
// keine echte Id, auf die sich die KI beziehen könnte.
public Guid UnitId => _unitId;
public Guid GroupId => _groupId;
public Lesson? EditingLesson => _editingLesson;
public bool CanAiAssist => _editingLesson is not null;
public bool IsNewLesson => _editingLesson is null;
/// Vom Code-Behind nach einer über die KI angewendeten Änderung aufgerufen: der Dialog schließt
/// sich danach mit Result != null, damit die aufrufende Liste neu lädt — die eigenen, jetzt
@@ -1258,6 +1260,56 @@ public partial class ChangeLessonUnitDialogViewModel : ObservableObject
}
}
// ── Dialog: Vorhandene Stunde zum Verknüpfen auswählen (Stundenplan-FixIt) ────
/// <summary>
/// Sucht Stunden, die zu einem Stundenplan-Termin gehören könnten, aber (z.B. durch JSON-Import
/// oder KI-Übernahme ohne Stundenplan-Bezug) keine passende <see cref="Lesson.LessonNumber"/>
/// haben und deshalb von <c>TimetableViewModel.FindLessonForSlot</c> nicht gefunden werden.
/// Als eigene, von Avalonia unabhängige Methode extrahiert, damit die Zuordnungslogik ohne Fenster
/// testbar ist — Aufrufer ist <c>LessonDialog.axaml.cs</c> (Button "Vorhandene Stunde verknüpfen").
/// </summary>
public static class LessonFixItSearch
{
public static List<Lesson> FindCandidates(ILessonRepository lessons, Guid groupId, DateOnly date) =>
[.. lessons.GetByGroupAndRange(groupId, date.AddDays(-14), date.AddDays(14))
.Where(l => l.LessonNumber is null || l.Date == date)
.OrderBy(l => l.Date == date ? 0 : 1)
.ThenBy(l => Math.Abs(l.Date.DayNumber - date.DayNumber))];
}
public sealed class LessonLinkOption(Lesson lesson, string unitTitle)
{
public Lesson Model { get; } = lesson;
public string Label { get; } = string.IsNullOrWhiteSpace(lesson.Topic) ? "(ohne Thema)" : lesson.Topic;
public string Detail { get; } =
$"{lesson.Date:dd.MM.yyyy} · {(lesson.LessonNumber is int n ? $"{n}. Stunde" : "keine Stundennummer")} · Einheit „{unitTitle}“";
}
public partial class LinkExistingLessonDialogViewModel : ObservableObject
{
[ObservableProperty] private LessonLinkOption? _selectedOption;
[ObservableProperty] private string _error = "";
public ObservableCollection<LessonLinkOption> Options { get; } = [];
public Lesson? Result { get; private set; }
public LinkExistingLessonDialogViewModel(List<Lesson> candidates, IUnitRepository units)
{
foreach (var lesson in candidates)
Options.Add(new LessonLinkOption(lesson, units.GetById(lesson.UnitId)?.Title ?? "?"));
SelectedOption = Options.FirstOrDefault();
}
[RelayCommand]
private void Save()
{
Error = "";
if (SelectedOption is null) { Error = "Bitte eine Stunde auswählen."; return; }
Result = SelectedOption.Model;
}
}
// ── Dialog: Stunden serienweise aus dem Stundenplan erzeugen (4.2.5) ────────
public partial class GenerateLessonSeriesDialogViewModel : ObservableObject