Punkteeingabe: nur angefahrene Zeilen nicht mehr als 0 Punkte/Note 6 speichern
CI / build-and-test (push) Canceled after 0s
CI / build-and-test (push) Canceled after 0s
Das Verlassen eines unveränderten Punktefelds löste Autosave aus; leere Zellen wurden als 0 gespeichert und ergaben Note 6. Zellen melden nur noch echte Änderungen, Zeilen ohne Werte werden nicht angelegt und haben keine Note. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
41649b0a05
commit
d61a97de1e
3 files changed
+120
-5
No files matched your search
@@ -1,3 +1,4 @@
|
||||
using LehrerApp.Core.Interfaces;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
@@ -133,4 +134,95 @@ public sealed class ExamGradingDialogViewModelTests
|
||||
|
||||
Assert.Equal(["Aufgabe 1", "★ Bonus", "2. Redox"], vm.TaskHeaders);
|
||||
}
|
||||
|
||||
private sealed class RecordingResults : FakeResults, IExamResultRepository
|
||||
{
|
||||
public List<ExamResult> Saved { get; } = [];
|
||||
public new void Save(ExamResult result) => Saved.Add(result);
|
||||
}
|
||||
|
||||
private static (ExamGradingDialogViewModel Vm, RecordingResults Results) CreateRecordingGrid(
|
||||
ExamResult? existing = null)
|
||||
{
|
||||
var group = new LearningGroup { Name = "Q1 Chemie" };
|
||||
var student = new Student { FirstName = "Ben", LastName = "Cordes" };
|
||||
var exam = new Exam
|
||||
{
|
||||
GroupId = group.Id, Title = "Klausur 1", Tasks = [.. ThreeTasks()],
|
||||
GradingKey = GradingService.DefaultKey1To6(),
|
||||
};
|
||||
var results = new RecordingResults();
|
||||
if (existing is not null)
|
||||
{
|
||||
existing.ExamId = exam.Id; existing.StudentId = student.Id;
|
||||
results.Add(existing);
|
||||
}
|
||||
var vm = new ExamGradingDialogViewModel(results, new FakeStudents([student]),
|
||||
new FakeMemberships([new GroupMembership { GroupId = group.Id, StudentId = student.Id }]),
|
||||
new GradingService(), exam, group.Id);
|
||||
return (vm, results);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NurAngefahreneZelle_LegtKeinErgebnisMitNullPunktenAn()
|
||||
{
|
||||
var (vm, results) = CreateRecordingGrid();
|
||||
|
||||
// Fokus in die Zelle und wieder heraus ohne Eingabe: die View meldet das als TrySetValue(null).
|
||||
vm.Rows[0].Cells[0].TrySetValue(null);
|
||||
vm.Rows[0].Cells[1].TrySetValue(null);
|
||||
|
||||
Assert.Empty(results.Saved);
|
||||
Assert.Equal("", vm.Rows[0].GradeDisplay);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ErsterEingetragenerWert_LegtDasErgebnisAn()
|
||||
{
|
||||
var (vm, results) = CreateRecordingGrid();
|
||||
|
||||
vm.Rows[0].Cells[0].TrySetValue(4);
|
||||
|
||||
var saved = Assert.Single(results.Saved);
|
||||
Assert.Equal([4, 0, 0], saved.Points);
|
||||
Assert.Equal(4, saved.TotalPoints);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnveraenderterWert_SpeichertNichtErneut()
|
||||
{
|
||||
var (vm, results) = CreateRecordingGrid();
|
||||
vm.Rows[0].Cells[0].TrySetValue(4);
|
||||
|
||||
vm.Rows[0].Cells[0].TrySetValue(4);
|
||||
|
||||
Assert.Single(results.Saved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LeereZeileMitBestehendemErgebnis_WirdAlsUnkorrigiertGespeichertNichtAlsNull()
|
||||
{
|
||||
var (vm, results) = CreateRecordingGrid(
|
||||
new ExamResult { Points = [4], TotalPoints = 4, Grade = "4" });
|
||||
|
||||
vm.Rows[0].Cells[0].TrySetValue(null);
|
||||
|
||||
var saved = Assert.Single(results.Saved);
|
||||
Assert.Empty(saved.Points);
|
||||
Assert.Null(saved.Grade);
|
||||
Assert.False(ExamCoverSheetService.IsCorrected(saved));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AbwesendheitOhnePunkte_WirdOhnePunkteUndNoteGespeichert()
|
||||
{
|
||||
var (vm, results) = CreateRecordingGrid();
|
||||
|
||||
vm.Rows[0].Absent = true;
|
||||
|
||||
var saved = Assert.Single(results.Saved);
|
||||
Assert.True(saved.Absent);
|
||||
Assert.Empty(saved.Points);
|
||||
Assert.Null(saved.Grade);
|
||||
}
|
||||
}
|
||||
@@ -65,11 +65,21 @@ public partial class ExamGradingDialogViewModel : ObservableObject
|
||||
var row = new ExamResultRow(s.Id, s.FullName, Tasks, result, _exam.GradingKey,
|
||||
ExamScoringService.Maximum(Tasks, ExamVariant.A), _grading);
|
||||
row.OnChanged = SaveRow;
|
||||
row.HasStoredResult = result is not null;
|
||||
Rows.Add(row);
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveRow(ExamResultRow row) => _results.Save(row.ToModel(_exam.Id));
|
||||
/// Eine Zeile, die nur angefahren wurde (Fokus rein, Fokus raus), ist kein Ergebnis: ohne
|
||||
/// Punkte, Abwesenheit oder Kommentar und ohne bereits gespeichertes Ergebnis wird nichts
|
||||
/// angelegt — sonst stünde der Schüler später mit 0 Punkten und Note 6 in der Liste.
|
||||
private void SaveRow(ExamResultRow row)
|
||||
{
|
||||
if (!row.HasStoredResult && !row.HasPoints && !row.Absent && string.IsNullOrWhiteSpace(row.Comment))
|
||||
return;
|
||||
_results.Save(row.ToModel(_exam.Id));
|
||||
row.HasStoredResult = true;
|
||||
}
|
||||
|
||||
partial void OnEntryOrderNameChanged(string value) =>
|
||||
_settings?.Save(new ExamGradingSettings { EntryOrder = EntryOrder });
|
||||
@@ -160,6 +170,12 @@ public partial class ExamResultRow : ObservableObject
|
||||
|
||||
public Action<ExamResultRow>? OnChanged { get; set; }
|
||||
|
||||
/// Mindestens eine Aufgabe hat einen eingetragenen Wert (auch 0). Ohne Werte gibt es weder
|
||||
/// Summe noch Note.
|
||||
public bool HasPoints => Cells.Any(c => c.Value is not null);
|
||||
/// Für diesen Schüler liegt in der Datenbank bereits ein Ergebnis dieser Arbeit.
|
||||
public bool HasStoredResult { get; set; }
|
||||
|
||||
public ExamResultRow(Guid studentId, string name, List<ExamTask> tasks, ExamResult? existing,
|
||||
List<GradingKeyEntry> gradingKey, double examMaxPoints, GradingService grading)
|
||||
{
|
||||
@@ -202,7 +218,7 @@ public partial class ExamResultRow : ObservableObject
|
||||
{
|
||||
TotalPoints = Cells.Sum(c => c.IsApplicable && !c.IsInvalid ? (c.Value ?? 0) : 0);
|
||||
OnPropertyChanged(nameof(TotalPointsDisplay));
|
||||
GradeDisplay = Absent ? "abwesend" : _grading.CalculateGrade(TotalPoints,
|
||||
GradeDisplay = Absent ? "abwesend" : !HasPoints ? "" : _grading.CalculateGrade(TotalPoints,
|
||||
ExamScoringService.Maximum(_tasks, Variant), _gradingKey);
|
||||
}
|
||||
|
||||
@@ -211,9 +227,9 @@ public partial class ExamResultRow : ObservableObject
|
||||
Id = _resultId,
|
||||
ExamId = examId,
|
||||
StudentId = StudentId,
|
||||
Points = Cells.Select(c => c.Value ?? 0).ToList(),
|
||||
Points = HasPoints ? Cells.Select(c => c.Value ?? 0).ToList() : [],
|
||||
TotalPoints = TotalPoints,
|
||||
Grade = Absent ? null : GradeDisplay,
|
||||
Grade = Absent || !HasPoints ? null : GradeDisplay,
|
||||
Absent = Absent,
|
||||
Variant = Variant,
|
||||
Comment = string.IsNullOrWhiteSpace(Comment) ? null : Comment.Trim(),
|
||||
@@ -240,11 +256,14 @@ public partial class PointsCell : ObservableObject
|
||||
|
||||
/// Setzt den Wert; bei Punkten außerhalb 0..Maximalpunkte wird nur rot markiert,
|
||||
/// aber nicht gespeichert (1.4.5) — OnChanged (und damit Autosave) wird dann nicht ausgelöst.
|
||||
/// Auch unverändert gelassene Zellen melden sich beim Verlassen des Eingabefelds hier —
|
||||
/// gespeichert wird nur bei einer tatsächlichen Änderung.
|
||||
public void TrySetValue(double? value)
|
||||
{
|
||||
var changed = Value != value;
|
||||
Value = value;
|
||||
IsInvalid = IsOutOfRange(value);
|
||||
if (!IsInvalid) OnChanged?.Invoke();
|
||||
if (!IsInvalid && changed) OnChanged?.Invoke();
|
||||
}
|
||||
|
||||
private bool IsOutOfRange(double? value) =>
|
||||
|
||||
@@ -74,6 +74,10 @@ Diese Regeln gelten für jede Aufgabe unten und müssen nicht wiederholt werden.
|
||||
Niveaudifferenzierung (Checkbox-Spalte „Förder“ in der Schülerliste; in differenzierten
|
||||
Gruppen zählt das Förderniveau). Die Punkteeingabe/Deckblätter einer Förderarbeit zeigen nur
|
||||
Förderschüler (plus Schüler mit vorhandenem Ergebnis), das Kranke-Ausblenden entfällt damit.
|
||||
Fix Punkteeingabe: eine nur angefahrene Zeile (Fokus rein/raus ohne Eingabe) legt kein
|
||||
Ergebnis mit 0 Punkten und Note 6 mehr an (`ExamGradingDialogViewModel.SaveRow` speichert nur
|
||||
bei Wert, Abwesenheit, Kommentar oder vorhandenem Ergebnis; `PointsCell.TrySetValue` meldet
|
||||
nur echte Änderungen; Zeilen ohne Werte haben keine Note und speichern keine Punkteliste).
|
||||
|
||||
Modelle `Exam`, `ExamTask`, `GradingKeyEntry`, `ExamResult` existieren bereits in
|
||||
[Exam.cs](LehrerApp.Core/Models/Exam.cs), Repositories ebenfalls. Die komplette UI fehlt.
|
||||
|
||||
Reference in new issue
Block a user