Arbeitszeit: wiederkehrende Aufgaben + Auto-Aufgabe bei Klausurkorrektur (6.1.4/6.1.5)

Damit ist Kapitel 6 (Arbeitszeit & Aufgaben) vollständig abgeschlossen (bis auf den
Export der Auswertung, der an das noch fehlende Kapitel 11 hängt).

- WorkTask.Recurrence (None/Weekly/Monthly): beim Abschließen einer wiederkehrenden
  Aufgabe wird automatisch die nächste Instanz mit verschobenem Fälligkeitsdatum erzeugt.
- GroupDetailViewModel.SetExamStatus legt beim ersten Wechsel einer Klausur von
  "Geplant" auf "Durchgeführt" automatisch eine Korrektur-Aufgabe an.
This commit is contained in:
2026-08-16 00:39:18 +02:00
parent f9e398b225
commit 2b4fda7bb3
8 changed files with 259 additions and 9 deletions
@@ -93,6 +93,66 @@ public sealed class WorkTaskListViewModelTests
Assert.Equal(WorkTaskStatus.Open, vm.Tasks[0].Model.Status);
}
[Fact]
public void CycleStatus_WoechentlicheWiederholungAufErledigt_ErzeugtNaechsteInstanz()
{
var tasks = new FakeWorkTasks();
tasks.Add(new WorkTask
{
Title = "Wochenbericht", Status = WorkTaskStatus.InProgress,
Recurrence = TaskRecurrence.Weekly, DueDate = new DateOnly(2026, 3, 10),
Category = TaskCategory.Admin,
});
var vm = new WorkTaskListViewModel(tasks, new FakeGroups([]), new FakeTimeEntries())
{
StatusFilter = WorkTaskListViewModel.AllFilter,
};
vm.CycleStatusCommand.Execute(vm.Tasks[0]); // -> Done
var all = tasks.GetAll();
Assert.Equal(2, all.Count);
var next = all.Single(t => t.Status == WorkTaskStatus.Open);
Assert.Equal("Wochenbericht", next.Title);
Assert.Equal(new DateOnly(2026, 3, 17), next.DueDate);
Assert.Equal(TaskRecurrence.Weekly, next.Recurrence);
}
[Fact]
public void CycleStatus_MonatlicheWiederholung_AddiertEinenMonat()
{
var tasks = new FakeWorkTasks();
tasks.Add(new WorkTask
{
Title = "Bericht", Status = WorkTaskStatus.InProgress,
Recurrence = TaskRecurrence.Monthly, DueDate = new DateOnly(2026, 3, 10),
});
var vm = new WorkTaskListViewModel(tasks, new FakeGroups([]), new FakeTimeEntries())
{
StatusFilter = WorkTaskListViewModel.AllFilter,
};
vm.CycleStatusCommand.Execute(vm.Tasks[0]); // -> Done
var next = tasks.GetAll().Single(t => t.Status == WorkTaskStatus.Open);
Assert.Equal(new DateOnly(2026, 4, 10), next.DueDate);
}
[Fact]
public void CycleStatus_OhneWiederholung_ErzeugtKeineNeueAufgabe()
{
var tasks = new FakeWorkTasks();
tasks.Add(new WorkTask { Title = "T", Status = WorkTaskStatus.InProgress });
var vm = new WorkTaskListViewModel(tasks, new FakeGroups([]), new FakeTimeEntries())
{
StatusFilter = WorkTaskListViewModel.AllFilter,
};
vm.CycleStatusCommand.Execute(vm.Tasks[0]); // -> Done
Assert.Single(tasks.GetAll());
}
[Fact]
public void DeleteTask_EntferntAufgabeAusDerListe()
{
@@ -193,6 +253,37 @@ public sealed class AddEditWorkTaskDialogViewModelTests
Assert.Equal(source.Id, vm.Result!.Id);
Assert.Equal(WorkTaskStatus.InProgress, vm.Result.Status);
}
[Fact]
public void Save_WiederholungOhneFaelligkeitsdatum_SetztFehler()
{
var vm = new AddEditWorkTaskDialogViewModel(null, [])
{
Title = "T",
SelectedRecurrence = TaskRecurrenceDisplay.Label(TaskRecurrence.Weekly),
};
vm.SaveCommand.Execute(null);
Assert.Null(vm.Result);
Assert.NotEmpty(vm.DueDateError);
}
[Fact]
public void Save_WiederholungMitFaelligkeitsdatum_UebernimmtRecurrence()
{
var vm = new AddEditWorkTaskDialogViewModel(null, [])
{
Title = "T",
DueDateText = "10.03.2026",
SelectedRecurrence = TaskRecurrenceDisplay.Label(TaskRecurrence.Monthly),
};
vm.SaveCommand.Execute(null);
Assert.NotNull(vm.Result);
Assert.Equal(TaskRecurrence.Monthly, vm.Result!.Recurrence);
}
}
public sealed class TimeTrackingViewModelTests