Niveaudifferenzierung (E/G/Förder) für Klassen und Kurse
Für Fächer mit Binnendifferenzierung (z.B. Mathematik E/G-Niveau, Förderniveau) innerhalb derselben Lerngruppe: - LearningGroup.IsDifferentiated: neue Checkbox in den Stammdaten, aktiviert die Niveau-Funktionen optional für Klassen und Kurse. - Enrollment.Niveau: Niveau-Zuordnung je Schüler innerhalb der Gruppe, editierbar über eine neue Spalte im Schüler-Tab (nur sichtbar bei differenzierter Gruppe). - Exam.Niveau: Klausuren können optional einem Niveau zugeordnet werden. Workflow nutzt die bestehende Duplizieren-Funktion (1.1.4): G-Klausur anlegen, für die E-Variante duplizieren und eigene Aufgaben/Punkte vergeben — Niveau wird beim Duplizieren bewusst zurückgesetzt, damit nicht versehentlich zwei gleiche entstehen. - Punkteeingabe und Auswertung filtern automatisch auf die Schüler mit passendem Niveau; Klausuren ohne Niveau-Zuordnung verhalten sich unverändert (gesamte Gruppe). Beide Dialoge zeigen das Niveau als Badge im Titel, die Klausurenliste als eigene Spalte. - Individuelle Förderklausuren (nur für einen Schüler) laufen pragmatisch über die bestehende Abwesend-Markierung der übrigen Schüler statt über eine eigene Pro-Schüler-Zuordnung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ public partial class ExamEvaluationDialogViewModel : ObservableObject
|
||||
|
||||
public string ExamTitle => _exam.Title;
|
||||
public string ExamDateLabel => _exam.Date.ToString("dd.MM.yyyy");
|
||||
public string NiveauLabel => _exam.Niveau.HasValue ? NiveauDisplay.ToName(_exam.Niveau) : "";
|
||||
|
||||
[ObservableProperty] private int _gradedCount;
|
||||
[ObservableProperty] private int _absentCount;
|
||||
|
||||
@@ -18,6 +18,7 @@ public partial class ExamGradingDialogViewModel : ObservableObject
|
||||
|
||||
public string ExamTitle => _exam.Title;
|
||||
public string ExamDateLabel => _exam.Date.ToString("dd.MM.yyyy");
|
||||
public string NiveauLabel => _exam.Niveau.HasValue ? NiveauDisplay.ToName(_exam.Niveau) : "";
|
||||
public List<ExamTask> Tasks { get; }
|
||||
|
||||
public ObservableCollection<ExamResultRow> Rows { get; } = [];
|
||||
@@ -38,6 +39,10 @@ public partial class ExamGradingDialogViewModel : ObservableObject
|
||||
var enrollment = enrollmentList.FirstOrDefault(e => e.StudentId == s.Id);
|
||||
if (enrollment is not null && !IsEnrolledAtDate(enrollment, exam.Date)) continue;
|
||||
|
||||
// Niveau-Klausur: nur Schüler mit passendem Niveau zeigen. Klausuren ohne
|
||||
// Niveau-Zuordnung gelten weiterhin für die ganze Gruppe.
|
||||
if (exam.Niveau.HasValue && enrollment?.Niveau != exam.Niveau) continue;
|
||||
|
||||
existing.TryGetValue(s.Id, out var result);
|
||||
var row = new ExamResultRow(s.Id, s.FullName, Tasks, result, _exam.GradingKey, _examMaxPoints, _grading);
|
||||
row.OnChanged = SaveRow;
|
||||
|
||||
@@ -35,6 +35,10 @@ public partial class ExamDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private GradingKeyTemplate? _selectedTemplate;
|
||||
[ObservableProperty] private string _newTemplateName = "";
|
||||
[ObservableProperty] private string _gradingKeyValidation = "";
|
||||
[ObservableProperty] private string _selectedNiveauName = "–";
|
||||
|
||||
public bool IsDifferentiated { get; }
|
||||
public string[] NiveauOptions => NiveauDisplay.Options;
|
||||
|
||||
public bool TotalPointsWarning => TotalPoints <= 0;
|
||||
public string TotalPointsDisplay =>
|
||||
@@ -55,7 +59,7 @@ public partial class ExamDialogViewModel : ObservableObject
|
||||
public ExamDialogViewModel(IExamRepository exams, ICompetencyDomainRepository competencyDomains,
|
||||
IGradingKeyTemplateRepository gradingKeyTemplates, GradingService grading,
|
||||
Guid groupId, Guid? subjectId, int gradeLevel, GradingSystem gradingSystem,
|
||||
string defaultSubjectName, Exam? editingExam, Exam? duplicateSource)
|
||||
string defaultSubjectName, bool isDifferentiated, Exam? editingExam, Exam? duplicateSource)
|
||||
{
|
||||
_exams = exams; _competencyDomains = competencyDomains;
|
||||
_gradingKeyTemplates = gradingKeyTemplates; _grading = grading;
|
||||
@@ -63,6 +67,7 @@ public partial class ExamDialogViewModel : ObservableObject
|
||||
_gradingSystem = gradingSystem;
|
||||
_editingExam = editingExam;
|
||||
_subjectName = defaultSubjectName;
|
||||
IsDifferentiated = isDifferentiated;
|
||||
|
||||
HasCompetencyCatalog = subjectId.HasValue
|
||||
&& _competencyDomains.GetBySubjectAndGrade(subjectId.Value, gradeLevel).Count > 0;
|
||||
@@ -81,6 +86,9 @@ public partial class ExamDialogViewModel : ObservableObject
|
||||
ExamNumber = source.ExamNumber;
|
||||
Notes = source.Notes ?? "";
|
||||
ReturnedAtText = isDuplicate ? "" : source.ReturnedAt?.ToString("dd.MM.yyyy") ?? "";
|
||||
// Beim Duplizieren bewusst kein Niveau übernehmen — meist wird dupliziert, um
|
||||
// gerade eine andere Niveau-Variante (E/G/Förder) derselben Klausur anzulegen.
|
||||
SelectedNiveauName = isDuplicate ? "–" : NiveauDisplay.ToName(source.Niveau);
|
||||
foreach (var t in source.Tasks.OrderBy(t => t.Nr))
|
||||
AddTaskInternal(t.Title, t.MaxPoints, t.Weight, [.. t.CompetencyCodes]);
|
||||
UseWeighting = Tasks.Any(t => Math.Abs(t.Weight - 1.0) > 0.0001);
|
||||
@@ -285,6 +293,7 @@ public partial class ExamDialogViewModel : ObservableObject
|
||||
Result.ExamNumber = ExamNumber;
|
||||
Result.Notes = string.IsNullOrWhiteSpace(Notes) ? null : Notes.Trim();
|
||||
Result.ReturnedAt = returnedAt;
|
||||
Result.Niveau = NiveauDisplay.FromName(SelectedNiveauName);
|
||||
Result.Tasks = Tasks.Select(t => t.ToModel()).ToList();
|
||||
Result.GradingKey = gradingKey;
|
||||
_exams.Save(Result);
|
||||
|
||||
@@ -221,10 +221,21 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
foreach (var s in enrolled)
|
||||
{
|
||||
enrollments.TryGetValue(s.Id, out var enr);
|
||||
Students.Add(new StudentSummary(s, enr));
|
||||
var summary = new StudentSummary(s, enr) { OnChanged = SaveStudentNiveau };
|
||||
Students.Add(summary);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveStudentNiveau(StudentSummary summary)
|
||||
{
|
||||
if (Group is null) return;
|
||||
var enrollment = _enrollments.GetByGroupAndYear(Group.Id, Group.SchoolYear)
|
||||
.FirstOrDefault(e => e.StudentId == summary.Id);
|
||||
if (enrollment is null) return;
|
||||
enrollment.Niveau = summary.Niveau;
|
||||
_enrollments.Save(enrollment);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task AddStudent()
|
||||
{
|
||||
@@ -363,12 +374,47 @@ public partial class GroupDetailViewModel : ObservableObject
|
||||
[RelayCommand] private void Refresh() { if (Group is not null) LoadGroup(Group.Id); }
|
||||
}
|
||||
|
||||
public class StudentSummary
|
||||
// ── Niveau-Anzeige (Niveaudifferenzierung) ────────────────────────────────────
|
||||
|
||||
public static class NiveauDisplay
|
||||
{
|
||||
public static string[] Options { get; } = ["–", "E-Niveau", "G-Niveau", "Förderniveau"];
|
||||
|
||||
public static string ToName(Niveau? n) => n switch
|
||||
{
|
||||
Core.Models.Niveau.E => "E-Niveau",
|
||||
Core.Models.Niveau.G => "G-Niveau",
|
||||
Core.Models.Niveau.Foerder => "Förderniveau",
|
||||
_ => "–",
|
||||
};
|
||||
|
||||
public static Niveau? FromName(string? name) => name switch
|
||||
{
|
||||
"E-Niveau" => Core.Models.Niveau.E,
|
||||
"G-Niveau" => Core.Models.Niveau.G,
|
||||
"Förderniveau" => Core.Models.Niveau.Foerder,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
public partial class StudentSummary : ObservableObject
|
||||
{
|
||||
public static string[] NiveauOptions => NiveauDisplay.Options;
|
||||
|
||||
public Guid Id { get; }
|
||||
public string FullName { get; }
|
||||
public string PeriodLabel { get; }
|
||||
|
||||
[ObservableProperty] private Niveau? _niveau;
|
||||
|
||||
public string NiveauName
|
||||
{
|
||||
get => NiveauDisplay.ToName(Niveau);
|
||||
set => Niveau = NiveauDisplay.FromName(value);
|
||||
}
|
||||
|
||||
public Action<StudentSummary>? OnChanged { get; set; }
|
||||
|
||||
public StudentSummary(Core.Models.Student s, Enrollment? e)
|
||||
{
|
||||
Id = s.Id;
|
||||
@@ -380,6 +426,13 @@ public class StudentSummary
|
||||
EnrollmentPeriod.Custom => BuildCustomLabel(e),
|
||||
_ => "",
|
||||
};
|
||||
_niveau = e?.Niveau;
|
||||
}
|
||||
|
||||
partial void OnNiveauChanged(Niveau? value)
|
||||
{
|
||||
OnPropertyChanged(nameof(NiveauName));
|
||||
OnChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
private static string BuildCustomLabel(Enrollment e)
|
||||
@@ -400,11 +453,13 @@ public class ExamSummary
|
||||
public string StatusLabel { get; }
|
||||
public string StatusColorHex { get; }
|
||||
public string ReturnedAtDisplay { get; }
|
||||
public string NiveauLabel { get; }
|
||||
|
||||
public ExamSummary(Core.Models.Exam e)
|
||||
{
|
||||
Id = e.Id; Title = e.Title; Date = e.Date.ToString("dd.MM.yyyy"); Status = e.Status;
|
||||
ReturnedAtDisplay = e.ReturnedAt?.ToString("dd.MM.yyyy") ?? "";
|
||||
NiveauLabel = e.Niveau.HasValue ? NiveauDisplay.ToName(e.Niveau) : "alle";
|
||||
StatusLabel = e.Status switch
|
||||
{
|
||||
ExamStatus.Planned => "Geplant",
|
||||
@@ -552,6 +607,7 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
||||
[ObservableProperty] private string _selectedSchoolYear = "";
|
||||
[ObservableProperty] private int? _hoursPerWeek;
|
||||
[ObservableProperty] private bool _isOwnClass;
|
||||
[ObservableProperty] private bool _isDifferentiated;
|
||||
[ObservableProperty] private string _validationMessage = "";
|
||||
|
||||
public List<string> SchoolYears { get; }
|
||||
@@ -589,6 +645,7 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
||||
SelectedSchoolYear = group.SchoolYear;
|
||||
HoursPerWeek = group.HoursPerWeek;
|
||||
IsOwnClass = group.IsOwnClass;
|
||||
IsDifferentiated = group.IsDifferentiated;
|
||||
OnPropertyChanged(nameof(DialogTitle));
|
||||
OnPropertyChanged(nameof(SaveButtonText));
|
||||
}
|
||||
@@ -629,6 +686,7 @@ public partial class AddGroupDialogViewModel : ObservableObject
|
||||
Result.SchoolYear = SelectedSchoolYear;
|
||||
Result.HoursPerWeek = HoursPerWeek;
|
||||
Result.IsOwnClass = IsOwnClass;
|
||||
Result.IsDifferentiated = IsDifferentiated;
|
||||
_groups.Save(Result);
|
||||
|
||||
if (_editingGroup is not null && _originalSchoolYear != SelectedSchoolYear)
|
||||
|
||||
Reference in New Issue
Block a user