diff --git a/LehrerApp.Core/Services/WorkloadSettingsService.cs b/LehrerApp.Core/Services/WorkloadSettingsService.cs
new file mode 100644
index 0000000..e129c53
--- /dev/null
+++ b/LehrerApp.Core/Services/WorkloadSettingsService.cs
@@ -0,0 +1,41 @@
+using System.Text.Json;
+
+namespace LehrerApp.Core.Services;
+
+internal class WorkloadSettingsConfig
+{
+ public double RequiredWeeklyHours { get; set; }
+}
+
+/// Pflichtstundenzahl pro Woche für den Ist-Soll-Abgleich in der Arbeitszeitauswertung (6.3.2).
+public class WorkloadSettingsService
+{
+ private readonly string _configPath;
+ private WorkloadSettingsConfig _config;
+
+ public double RequiredWeeklyHours => _config.RequiredWeeklyHours;
+
+ public WorkloadSettingsService(string appDataPath)
+ {
+ _configPath = Path.Combine(appDataPath, "workloadsettings.json");
+ _config = Load();
+ }
+
+ public void SetRequiredWeeklyHours(double hours)
+ {
+ _config.RequiredWeeklyHours = hours;
+ File.WriteAllText(_configPath, JsonSerializer.Serialize(_config));
+ }
+
+ private WorkloadSettingsConfig Load()
+ {
+ try
+ {
+ if (File.Exists(_configPath))
+ return JsonSerializer.Deserialize(File.ReadAllText(_configPath))
+ ?? new WorkloadSettingsConfig();
+ }
+ catch { /* beschädigte Konfiguration -> Standardwert */ }
+ return new WorkloadSettingsConfig();
+ }
+}
diff --git a/LehrerApp.Desktop.Tests/WorkloadViewModelTests.cs b/LehrerApp.Desktop.Tests/WorkloadViewModelTests.cs
index 8fc75cf..7955586 100644
--- a/LehrerApp.Desktop.Tests/WorkloadViewModelTests.cs
+++ b/LehrerApp.Desktop.Tests/WorkloadViewModelTests.cs
@@ -1,4 +1,5 @@
using LehrerApp.Core.Models;
+using LehrerApp.Core.Services;
using LehrerApp.Desktop.ViewModels.Workload;
using Xunit;
@@ -346,3 +347,102 @@ public sealed class AddTimeEntryDialogViewModelTests
Assert.NotEmpty(vm.TimeError);
}
}
+
+public sealed class WorkloadEvaluationViewModelTests
+{
+ private static WorkloadSettingsService BuildSettingsService()
+ {
+ var tempPath = System.IO.Path.Combine(
+ System.IO.Path.GetTempPath(), $"lehrerapp-workloadsettings-tests-{Guid.NewGuid():N}");
+ Directory.CreateDirectory(tempPath);
+ return new WorkloadSettingsService(tempPath);
+ }
+
+ [Fact]
+ public void MonatModus_ZeigtNurEintraegeDesGewaehltenMonats()
+ {
+ var entries = new FakeTimeEntries();
+ entries.Add(new TimeEntry { Category = "Korrektur", Date = new DateOnly(2026, 3, 10), DurationMinutes = 30 });
+ entries.Add(new TimeEntry { Category = "Korrektur", Date = new DateOnly(2026, 4, 1), DurationMinutes = 45 });
+
+ var vm = new WorkloadEvaluationViewModel(entries, new FakeGroups([]), BuildSettingsService(), new SchoolYearService())
+ {
+ SelectedYear = 2026,
+ };
+ vm.SelectedMonth = vm.MonthOptions[2]; // März
+
+ Assert.Equal("30 min (0.5 h)", vm.TotalMinutesDisplay);
+ }
+
+ [Fact]
+ public void CategorySummaries_GruppiertProKategorie()
+ {
+ var entries = new FakeTimeEntries();
+ entries.Add(new TimeEntry { Category = "Korrektur", Date = new DateOnly(2026, 3, 5), DurationMinutes = 30 });
+ entries.Add(new TimeEntry { Category = "Verwaltung", Date = new DateOnly(2026, 3, 6), DurationMinutes = 20 });
+
+ var vm = new WorkloadEvaluationViewModel(entries, new FakeGroups([]), BuildSettingsService(), new SchoolYearService())
+ {
+ SelectedYear = 2026,
+ };
+ vm.SelectedMonth = vm.MonthOptions[2];
+
+ Assert.Equal(2, vm.CategorySummaries.Count);
+ Assert.Contains(vm.CategorySummaries, s => s.Category == "Korrektur" && s.MinutesDisplay == "30 min");
+ }
+
+ [Fact]
+ public void GroupSummaries_ZeigtNurEintraegeMitGruppe()
+ {
+ var group = new LearningGroup { Name = "8a" };
+ var entries = new FakeTimeEntries();
+ entries.Add(new TimeEntry { Category = "X", GroupId = group.Id, Date = new DateOnly(2026, 3, 5), DurationMinutes = 30 });
+ entries.Add(new TimeEntry { Category = "X", GroupId = null, Date = new DateOnly(2026, 3, 6), DurationMinutes = 20 });
+
+ var vm = new WorkloadEvaluationViewModel(entries, new FakeGroups([group]), BuildSettingsService(), new SchoolYearService())
+ {
+ SelectedYear = 2026,
+ };
+ vm.SelectedMonth = vm.MonthOptions[2];
+
+ var summary = Assert.Single(vm.GroupSummaries);
+ Assert.Equal("8a", summary.GroupName);
+ Assert.Equal("30 min", summary.MinutesDisplay);
+ }
+
+ [Fact]
+ public void SchuljahrModus_UmfasstAugustBisJuli()
+ {
+ var entries = new FakeTimeEntries();
+ entries.Add(new TimeEntry { Category = "X", Date = new DateOnly(2025, 9, 1), DurationMinutes = 30 }); // im Schuljahr 2025/26
+ entries.Add(new TimeEntry { Category = "X", Date = new DateOnly(2026, 7, 30), DurationMinutes = 15 }); // im Schuljahr 2025/26
+ entries.Add(new TimeEntry { Category = "X", Date = new DateOnly(2026, 8, 5), DurationMinutes = 99 }); // schon Schuljahr 2026/27
+
+ var vm = new WorkloadEvaluationViewModel(entries, new FakeGroups([]), BuildSettingsService(), new SchoolYearService())
+ {
+ SelectedYear = 2025,
+ PeriodMode = WorkloadEvaluationViewModel.SchoolYearMode,
+ };
+
+ Assert.Equal("45 min (0.8 h)", vm.TotalMinutesDisplay);
+ }
+
+ [Fact]
+ public void SaveRequiredWeeklyHours_AktualisiertSollIstVergleich()
+ {
+ var entries = new FakeTimeEntries();
+ entries.Add(new TimeEntry { Category = "X", Date = new DateOnly(2026, 3, 2), DurationMinutes = 60 });
+
+ var vm = new WorkloadEvaluationViewModel(entries, new FakeGroups([]), BuildSettingsService(), new SchoolYearService())
+ {
+ SelectedYear = 2026,
+ RequiredWeeklyHoursText = "10",
+ };
+ vm.SelectedMonth = vm.MonthOptions[2];
+
+ vm.SaveRequiredWeeklyHoursCommand.Execute(null);
+
+ Assert.Contains("Soll:", vm.RequiredVsActualDisplay);
+ Assert.Contains("Ist: 1 h", vm.RequiredVsActualDisplay);
+ }
+}
diff --git a/LehrerApp.Desktop/AppBootstrapper.cs b/LehrerApp.Desktop/AppBootstrapper.cs
index 9cc20c3..cd26652 100644
--- a/LehrerApp.Desktop/AppBootstrapper.cs
+++ b/LehrerApp.Desktop/AppBootstrapper.cs
@@ -143,6 +143,7 @@ public static class AppBootstrapper
services.AddSingleton();
services.AddSingleton(_ => new SchoolCalendarSettingsService(appData));
services.AddSingleton(_ => new PeriodScheduleService(appData));
+ services.AddSingleton(_ => new WorkloadSettingsService(appData));
// ── Sync (optional – nur wenn Server konfiguriert) ────────────────────
services.AddSingleton(_ => new EventQueue(queuePath));
@@ -190,6 +191,7 @@ public static class AppBootstrapper
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
// Transient: neue Instanz pro Navigation (für Detailseiten)
diff --git a/LehrerApp.Desktop/ViewModels/MainWindowViewModel.cs b/LehrerApp.Desktop/ViewModels/MainWindowViewModel.cs
index 25178e0..03d504e 100644
--- a/LehrerApp.Desktop/ViewModels/MainWindowViewModel.cs
+++ b/LehrerApp.Desktop/ViewModels/MainWindowViewModel.cs
@@ -94,6 +94,7 @@ public partial class MainWindowViewModel : ObservableObject
var workload = _services.GetRequiredService();
workload.Tasks.Load();
workload.TimeTracking.Load();
+ workload.Evaluation.Load();
workload.ActiveTabIndex = 0;
return workload;
}
diff --git a/LehrerApp.Desktop/ViewModels/Workload/WorkloadViewModels.cs b/LehrerApp.Desktop/ViewModels/Workload/WorkloadViewModels.cs
index 0369226..aaeca16 100644
--- a/LehrerApp.Desktop/ViewModels/Workload/WorkloadViewModels.cs
+++ b/LehrerApp.Desktop/ViewModels/Workload/WorkloadViewModels.cs
@@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using LehrerApp.Core.Interfaces;
using LehrerApp.Core.Models;
+using LehrerApp.Core.Services;
using System.Collections.ObjectModel;
using System.Globalization;
@@ -299,7 +300,7 @@ public partial class TimeTrackingViewModel : ObservableObject
: "";
public ObservableCollection WeekEntries { get; } = [];
- public ObservableCollection CategorySummaries { get; } = [];
+ public ObservableCollection CategorySummaries { get; } = [];
public string TotalWeekMinutesDisplay => $"{WeekEntries.Sum(e => e.Model.DurationMinutes)} min diese Woche";
public Func>? OnAddEntry { get; set; }
@@ -343,7 +344,7 @@ public partial class TimeTrackingViewModel : ObservableObject
foreach (var group in weekEntries.GroupBy(e => e.Category).OrderByDescending(g => g.Sum(e => e.DurationMinutes)))
{
var minutes = group.Sum(e => e.DurationMinutes);
- CategorySummaries.Add(new CategoryWeekSummary(
+ CategorySummaries.Add(new CategoryTimeSummary(
string.IsNullOrWhiteSpace(group.Key) ? "Ohne Kategorie" : group.Key,
minutes, maxMinutes > 0 ? minutes / (double)maxMinutes : 0));
}
@@ -413,7 +414,7 @@ public class TimeEntryListItem(TimeEntry model, string? taskTitle)
public string Description => Model.Description ?? "";
}
-public class CategoryWeekSummary(string category, int minutes, double barFraction)
+public class CategoryTimeSummary(string category, int minutes, double barFraction)
{
public string Category { get; } = category;
public string MinutesDisplay { get; } = $"{minutes} min";
@@ -489,12 +490,154 @@ public partial class AddTimeEntryDialogViewModel : ObservableObject
}
}
-// ── Container: Arbeitszeit-Seite mit Tabs "Aufgaben"/"Zeiterfassung" ─────────
+// ── Auswertung (6.3) ─────────────────────────────────────────────────────────
-public partial class WorkloadViewModel(WorkTaskListViewModel tasks, TimeTrackingViewModel timeTracking) : ObservableObject
+public partial class WorkloadEvaluationViewModel : ObservableObject
+{
+ private readonly ITimeEntryRepository _entries;
+ private readonly IGroupRepository _groups;
+ private readonly WorkloadSettingsService _workloadSettings;
+ private readonly SchoolYearService _schoolYear;
+
+ public const string MonthMode = "Monat";
+ public const string SchoolYearMode = "Schuljahr";
+
+ [ObservableProperty] private string _periodMode = MonthMode;
+ [ObservableProperty] private string _selectedMonth;
+ [ObservableProperty] private int _selectedYear = DateTime.Today.Year;
+ [ObservableProperty] private string _requiredWeeklyHoursText = "";
+
+ public List PeriodModeOptions { get; } = [MonthMode, SchoolYearMode];
+ public bool IsMonthMode => PeriodMode == MonthMode;
+ public List MonthOptions { get; } = Enumerable.Range(1, 12)
+ .Select(m => CultureInfo.GetCultureInfo("de-DE").DateTimeFormat.GetMonthName(m)).ToList();
+ public List YearOptions { get; } = Enumerable.Range(DateTime.Today.Year - 4, 5).Reverse().ToList();
+
+ public ObservableCollection CategorySummaries { get; } = [];
+ public ObservableCollection GroupSummaries { get; } = [];
+ public string TotalMinutesDisplay { get; private set; } = "0 min";
+ public string RequiredVsActualDisplay { get; private set; } = "";
+
+ public WorkloadEvaluationViewModel(ITimeEntryRepository entries, IGroupRepository groups,
+ WorkloadSettingsService workloadSettings, SchoolYearService schoolYear)
+ {
+ _entries = entries;
+ _groups = groups;
+ _workloadSettings = workloadSettings;
+ _schoolYear = schoolYear;
+ _selectedMonth = MonthOptions[DateTime.Today.Month - 1];
+ Load();
+ }
+
+ partial void OnPeriodModeChanged(string value)
+ {
+ OnPropertyChanged(nameof(IsMonthMode));
+ Refresh();
+ }
+ partial void OnSelectedMonthChanged(string value) => Refresh();
+ partial void OnSelectedYearChanged(int value) => Refresh();
+
+ public void Load()
+ {
+ RequiredWeeklyHoursText = _workloadSettings.RequiredWeeklyHours > 0
+ ? _workloadSettings.RequiredWeeklyHours.ToString(CultureInfo.InvariantCulture) : "";
+ Refresh();
+ }
+
+ [RelayCommand]
+ private void SaveRequiredWeeklyHours()
+ {
+ if (double.TryParse(RequiredWeeklyHoursText, NumberStyles.Number, CultureInfo.InvariantCulture, out var hours) && hours >= 0)
+ _workloadSettings.SetRequiredWeeklyHours(hours);
+ Refresh();
+ }
+
+ private (DateOnly From, DateOnly To) CurrentPeriod()
+ {
+ if (PeriodMode == SchoolYearMode)
+ {
+ // SelectedYear ist hier das Startjahr des Schuljahres (z.B. 2025 -> Schuljahr 2025/26).
+ var sy = _schoolYear.FormatSchoolYear(SelectedYear);
+ return (_schoolYear.SchoolYearStart(sy), _schoolYear.SchoolYearEnd(sy));
+ }
+
+ var month = MonthOptions.IndexOf(SelectedMonth) + 1;
+ var from = new DateOnly(SelectedYear, month, 1);
+ return (from, from.AddMonths(1).AddDays(-1));
+ }
+
+ private void Refresh()
+ {
+ var (from, to) = CurrentPeriod();
+ var periodEntries = _entries.GetByDateRange(from, to);
+ var groupNames = _groups.GetAll(includeInactive: true).ToDictionary(g => g.Id, g => g.Name);
+
+ CategorySummaries.Clear();
+ var maxCategoryMinutes = periodEntries.Count > 0
+ ? periodEntries.GroupBy(e => e.Category).Max(g => g.Sum(e => e.DurationMinutes)) : 0;
+ foreach (var group in periodEntries.GroupBy(e => e.Category).OrderByDescending(g => g.Sum(e => e.DurationMinutes)))
+ {
+ var minutes = group.Sum(e => e.DurationMinutes);
+ CategorySummaries.Add(new CategoryTimeSummary(
+ string.IsNullOrWhiteSpace(group.Key) ? "Ohne Kategorie" : group.Key,
+ minutes, maxCategoryMinutes > 0 ? minutes / (double)maxCategoryMinutes : 0));
+ }
+
+ GroupSummaries.Clear();
+ var withGroup = periodEntries.Where(e => e.GroupId.HasValue).ToList();
+ var maxGroupMinutes = withGroup.Count > 0
+ ? withGroup.GroupBy(e => e.GroupId).Max(g => g.Sum(e => e.DurationMinutes)) : 0;
+ foreach (var group in withGroup.GroupBy(e => e.GroupId).OrderByDescending(g => g.Sum(e => e.DurationMinutes)))
+ {
+ var minutes = group.Sum(e => e.DurationMinutes);
+ GroupSummaries.Add(new GroupTimeSummary(
+ groupNames.GetValueOrDefault(group.Key!.Value, "Unbekannte Gruppe"),
+ minutes, maxGroupMinutes > 0 ? minutes / (double)maxGroupMinutes : 0));
+ }
+
+ var totalMinutes = periodEntries.Sum(e => e.DurationMinutes);
+ TotalMinutesDisplay = $"{totalMinutes} min ({(totalMinutes / 60.0).ToString("0.#", CultureInfo.InvariantCulture)} h)";
+
+ // Pflichtstunden-Abgleich (6.3.2): auf die Anzahl Wochen im Zeitraum hochgerechnet.
+ if (_workloadSettings.RequiredWeeklyHours > 0)
+ {
+ var weeks = (to.DayNumber - from.DayNumber + 1) / 7.0;
+ var requiredHours = _workloadSettings.RequiredWeeklyHours * weeks;
+ var actualHours = totalMinutes / 60.0;
+ var diff = actualHours - requiredHours;
+ var diffText = diff >= 0
+ ? $"+{diff.ToString("0.#", CultureInfo.InvariantCulture)} h"
+ : $"{diff.ToString("0.#", CultureInfo.InvariantCulture)} h";
+ RequiredVsActualDisplay =
+ $"Soll: {requiredHours.ToString("0.#", CultureInfo.InvariantCulture)} h · " +
+ $"Ist: {actualHours.ToString("0.#", CultureInfo.InvariantCulture)} h · {diffText}";
+ }
+ else
+ {
+ RequiredVsActualDisplay = "";
+ }
+
+ OnPropertyChanged(nameof(TotalMinutesDisplay));
+ OnPropertyChanged(nameof(RequiredVsActualDisplay));
+ }
+}
+
+public class GroupTimeSummary(string groupName, int minutes, double barFraction)
+{
+ public string GroupName { get; } = groupName;
+ public string MinutesDisplay { get; } = $"{minutes} min";
+ public double BarFraction { get; } = barFraction;
+}
+
+// ── Container: Arbeitszeit-Seite mit Tabs "Aufgaben"/"Zeiterfassung"/"Auswertung" ─
+
+public partial class WorkloadViewModel(
+ WorkTaskListViewModel tasks, TimeTrackingViewModel timeTracking, WorkloadEvaluationViewModel evaluation)
+ : ObservableObject
{
[ObservableProperty] private int _activeTabIndex;
public WorkTaskListViewModel Tasks { get; } = tasks;
public TimeTrackingViewModel TimeTracking { get; } = timeTracking;
+ public WorkloadEvaluationViewModel Evaluation { get; } = evaluation;
}
diff --git a/LehrerApp.Desktop/Views/Workload/TimeTrackingView.axaml b/LehrerApp.Desktop/Views/Workload/TimeTrackingView.axaml
index aac8f91..a1e0b99 100644
--- a/LehrerApp.Desktop/Views/Workload/TimeTrackingView.axaml
+++ b/LehrerApp.Desktop/Views/Workload/TimeTrackingView.axaml
@@ -49,7 +49,7 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/LehrerApp.Desktop/Views/Workload/WorkloadEvaluationView.axaml.cs b/LehrerApp.Desktop/Views/Workload/WorkloadEvaluationView.axaml.cs
new file mode 100644
index 0000000..c9f2869
--- /dev/null
+++ b/LehrerApp.Desktop/Views/Workload/WorkloadEvaluationView.axaml.cs
@@ -0,0 +1,8 @@
+using Avalonia.Controls;
+
+namespace LehrerApp.Desktop.Views.Workload;
+
+public partial class WorkloadEvaluationView : UserControl
+{
+ public WorkloadEvaluationView() => InitializeComponent();
+}
diff --git a/LehrerApp.Desktop/Views/Workload/WorkloadView.axaml b/LehrerApp.Desktop/Views/Workload/WorkloadView.axaml
index 5ffa92f..f97c04e 100644
--- a/LehrerApp.Desktop/Views/Workload/WorkloadView.axaml
+++ b/LehrerApp.Desktop/Views/Workload/WorkloadView.axaml
@@ -19,6 +19,9 @@
+
+
+
diff --git a/TODO.md b/TODO.md
index 52bc55a..db035f1 100644
--- a/TODO.md
+++ b/TODO.md
@@ -778,9 +778,10 @@ dadurch faktisch schon (Quick-Input-Dialog bzw. "Offene Entschuldigungen" im Das
## 6. Arbeitszeit & Aufgaben
Modelle `WorkTask` und `TimeEntry` existieren, Repositories ebenfalls.
-Navigationspunkt "Arbeitszeit" zeigt inzwischen Aufgabenverwaltung (6.1) und Zeiterfassung (6.2)
-als zwei Tabs (`WorkloadViewModel`/`WorkloadView`, gleiches Container-Tab-Muster wie
-`GroupDetailViewModel`); nur die Monats-/Jahresauswertung (6.3) ist noch offen.
+Navigationspunkt "Arbeitszeit" zeigt Aufgabenverwaltung (6.1), Zeiterfassung (6.2) und Auswertung
+(6.3) als drei Tabs (`WorkloadViewModel`/`WorkloadView`, gleiches Container-Tab-Muster wie
+`GroupDetailViewModel`) — nur der Export der Auswertung (6.3.3) ist noch offen, da er an das noch
+nicht existierende Kapitel 11 (Export-Infrastruktur) hängt.
### 6.1 Aufgabenverwaltung
- [x] **6.1.1** Aufgabenliste mit Filter nach Status, Kategorie, Gruppe und Fälligkeit —
@@ -809,7 +810,7 @@ als zwei Tabs (`WorkloadViewModel`/`WorkloadView`, gleiches Container-Tab-Muster
`AddTimeEntryDialog`: entweder Dauer direkt eintragen oder Von/Bis angeben (daraus wird
die Dauer berechnet); mindestens eines von beidem ist Pflicht.
- [x] **6.2.3** Wochenübersicht der erfassten Zeit, Summen je Kategorie — Balken je Kategorie
- (`CategoryWeekSummary`, `TimeEntry.Category` ist bewusst ein freier String statt
+ (`CategoryTimeSummary`, `TimeEntry.Category` ist bewusst ein freier String statt
`TaskCategory`-Enum wie bei `WorkTask`, da das Domänenmodell das schon so vorgab; die
ComboBox schlägt trotzdem dieselben Kategorienamen vor). Nur die aktuelle Kalenderwoche,
keine Wochennavigation wie im Stundenplan — bei Bedarf später ergänzbar.
@@ -819,9 +820,22 @@ als zwei Tabs (`WorkloadViewModel`/`WorkloadView`, gleiches Container-Tab-Muster
`ITimeEntryRepository`.
### 6.3 Auswertung
-- [ ] **6.3.1** Monats-/Jahresauswertung nach Kategorie und Gruppe (Diagramm + Tabelle).
-- [ ] **6.3.2** Abgleich mit der Pflichtstundenzahl (in Einstellungen hinterlegt).
-- [ ] **6.3.3** Export der Arbeitszeitauswertung (siehe 11.2).
+- [x] **6.3.1** Monats-/Jahresauswertung nach Kategorie und Gruppe (Diagramm + Tabelle) —
+ dritter Tab "Auswertung" (`WorkloadEvaluationViewModel`). Zeitraum wahlweise Monat
+ (Jahr+Monat) oder Schuljahr (nutzt `SchoolYearService`, 1.8.–31.7. wie überall sonst in der
+ App — bewusst kein Kalenderjahr, um konsistent zur restlichen Schuljahres-Logik zu bleiben);
+ "Diagramm" als Balken je Kategorie/Gruppe (`ProgressBar`, gleiches Muster wie die
+ Wochenübersicht in 6.2.3), keine eigene Chart-Bibliothek.
+- [x] **6.3.2** Abgleich mit der Pflichtstundenzahl — neuer `WorkloadSettingsService`
+ (`workloadsettings.json`, gleiches Muster wie `PeriodScheduleService`/
+ `SchoolCalendarSettingsService`). Bewusst **nicht** in den Einstellungen (Kapitel 12)
+ hinterlegt, sondern direkt auf dem Auswertungs-Tab editierbar: das Feld wird ausschließlich
+ dort verwendet, ein Sprung in eine andere Seite für eine einzelne Zahl wäre reine
+ Indirektion (siehe auch das Nutzer-Feedback zum "Stundenplan bearbeiten"-Knopf oben). Die
+ Pflichtstunden pro Woche werden auf die Wochenzahl des gewählten Zeitraums hochgerechnet und
+ der erfassten Ist-Zeit gegenübergestellt.
+- [ ] **6.3.3** Export der Arbeitszeitauswertung (siehe 11.2) — noch offen, da Kapitel 11
+ (Export-Infrastruktur) noch nicht existiert.
---