TimetableSlotDialogViewModel baute die Gruppenauswahl als Dictionary,
geschlüsselt nach LearningGroup.Name. Bei zwei Lerngruppen mit demselben
Namen (dieselbe Klasse in zwei Fächern unterrichtet, z.B. zwei "10c") warf
ToDictionary eine ArgumentException, der Zuweisen-Dialog ließ sich gar
nicht mehr öffnen.
Behoben durch eindeutige Anzeige-Labels statt des rohen Namens: bei einem
Namenskonflikt wird das Fach angehängt ("10c (Chemie)" vs. "10c
(Mathematik)"), mit nummeriertem Fallback für den Restfall gleicher Name
und gleiches Fach.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
111 lines
4.2 KiB
C#
111 lines
4.2 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using LehrerApp.Core.Interfaces;
|
|
using LehrerApp.Core.Models;
|
|
using LehrerApp.Core.Services;
|
|
|
|
namespace LehrerApp.Desktop.ViewModels.Planning;
|
|
|
|
/// <summary>Zuweisen/Bearbeiten/Entfernen eines Stundenplan-Termins (4.3.3).</summary>
|
|
public partial class TimetableSlotDialogViewModel : ObservableObject
|
|
{
|
|
private readonly ITimetableSlotRepository _slots;
|
|
private readonly Dictionary<string, Guid> _groupIdsByLabel;
|
|
private readonly TimetableSlot? _editing;
|
|
|
|
public DayOfWeek Weekday { get; }
|
|
public int PeriodNumber { get; }
|
|
public string WeekdayLabel { get; }
|
|
public string DialogTitle { get; }
|
|
public bool IsEditing => _editing is not null;
|
|
|
|
[ObservableProperty] private string _selectedGroupName = "";
|
|
[ObservableProperty] private string _room = "";
|
|
[ObservableProperty] private string _groupError = "";
|
|
|
|
public string[] GroupOptions { get; }
|
|
|
|
/// null = unverändert/Abbruch, sonst das neue/aktualisierte Ergebnis.
|
|
public TimetableSlot? Result { get; private set; }
|
|
public bool Deleted { get; private set; }
|
|
|
|
public TimetableSlotDialogViewModel(ITimetableSlotRepository slots, IGroupRepository groups,
|
|
ISubjectRepository subjects, SchoolYearService schoolYear, DayOfWeek weekday, int periodNumber,
|
|
TimetableSlot? editing)
|
|
{
|
|
_slots = slots;
|
|
_editing = editing;
|
|
Weekday = weekday;
|
|
PeriodNumber = periodNumber;
|
|
WeekdayLabel = weekday switch
|
|
{
|
|
DayOfWeek.Monday => "Montag", DayOfWeek.Tuesday => "Dienstag",
|
|
DayOfWeek.Wednesday => "Mittwoch", DayOfWeek.Thursday => "Donnerstag",
|
|
DayOfWeek.Friday => "Freitag", _ => weekday.ToString(),
|
|
};
|
|
DialogTitle = $"{WeekdayLabel}, {periodNumber}. Stunde";
|
|
|
|
var availableGroups = groups.GetBySchoolYear(schoolYear.CurrentSchoolYear()).OrderBy(g => g.Name).ToList();
|
|
var subjectNames = subjects.GetAll().ToDictionary(s => s.Id, s => s.Name);
|
|
var nameCounts = availableGroups.GroupBy(g => g.Name).ToDictionary(g => g.Key, g => g.Count());
|
|
|
|
// Der Gruppenname allein ist nicht eindeutig: dieselbe Klasse in mehreren Fächern hat
|
|
// mehrere LearningGroup-Datensätze mit demselben Namen. Bei einem Namenskonflikt wird
|
|
// deshalb das Fach angehängt ("10c (Chemie)") — ohne diese Absicherung würde die
|
|
// Dictionary-Befüllung unten mit einer ArgumentException abstürzen (echter Nutzer-Fehler).
|
|
_groupIdsByLabel = new Dictionary<string, Guid>();
|
|
var labeledGroups = new List<(string Label, Guid Id)>();
|
|
foreach (var g in availableGroups)
|
|
{
|
|
var label = nameCounts[g.Name] > 1
|
|
? $"{g.Name} ({(g.SubjectId is { } sid && subjectNames.TryGetValue(sid, out var sn) ? sn : "ohne Fach")})"
|
|
: g.Name;
|
|
var uniqueLabel = label;
|
|
var suffix = 2;
|
|
while (_groupIdsByLabel.ContainsKey(uniqueLabel)) uniqueLabel = $"{label} ({suffix++})";
|
|
_groupIdsByLabel[uniqueLabel] = g.Id;
|
|
labeledGroups.Add((uniqueLabel, g.Id));
|
|
}
|
|
GroupOptions = labeledGroups.Select(x => x.Label).ToArray();
|
|
|
|
if (editing is not null)
|
|
{
|
|
SelectedGroupName = labeledGroups.FirstOrDefault(x => x.Id == editing.GroupId).Label ?? "";
|
|
Room = editing.Room ?? "";
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Save()
|
|
{
|
|
GroupError = "";
|
|
if (string.IsNullOrWhiteSpace(SelectedGroupName) || !_groupIdsByLabel.TryGetValue(SelectedGroupName, out var groupId))
|
|
{
|
|
GroupError = "Bitte eine Gruppe auswählen.";
|
|
return;
|
|
}
|
|
|
|
var slot = _editing ?? new TimetableSlot { Weekday = Weekday, PeriodNumber = PeriodNumber };
|
|
slot.GroupId = groupId;
|
|
slot.Room = string.IsNullOrWhiteSpace(Room) ? null : Room.Trim();
|
|
|
|
try
|
|
{
|
|
_slots.Save(slot);
|
|
Result = slot;
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
GroupError = ex.Message;
|
|
}
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Delete()
|
|
{
|
|
if (_editing is null) return;
|
|
_slots.Delete(_editing.Id);
|
|
Deleted = true;
|
|
}
|
|
}
|