fix: Klassenlehrer-Übersicht rief Schülerreport bislang live statt gecacht ab
Die Übersicht wirkte beim Öffnen spürbar zeitverzögert, obwohl Fehlzeiten und Klassenbuch schon lokal gecacht waren (Nutzer-Feedback: "fühlt sich an wie ein Live-Pull mit CSV-Parsing"). Grund: genau das passierte - der Schülerreport (Namen fürs Roster) lief komplett am Cache vorbei bei jedem Öffnen live gegen WebUntis. Neues UntisStudentRosterCacheEntry/UntisStudentRosterCacheRepository (gleiches "kein db.OnChange"-Prinzip wie die bestehenden Caches) plus UntisReportCacheService.GetStudentRosterAsync - ohne heißes/kaltes Fenster, da eine Klassenliste keine Historie hat, nur dieselbe Stundenschwelle "gilt der letzte Abruf noch als frisch". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -172,6 +172,7 @@ public static class AppBootstrapper
|
||||
services.AddSingleton<IUntisAbsenceCacheRepository, UntisAbsenceCacheRepository>();
|
||||
services.AddSingleton<IUntisClassRegisterCacheRepository, UntisClassRegisterCacheRepository>();
|
||||
services.AddSingleton<IUntisCacheFetchStateRepository, UntisCacheFetchStateRepository>();
|
||||
services.AddSingleton<IUntisStudentRosterCacheRepository, UntisStudentRosterCacheRepository>();
|
||||
|
||||
// ── Services ──────────────────────────────────────────────────────────
|
||||
services.AddSingleton<GradingService>();
|
||||
|
||||
@@ -29,11 +29,39 @@ public sealed class UntisReportCacheService(
|
||||
WebUntisIntegrationService untis,
|
||||
IUntisAbsenceCacheRepository absenceCache,
|
||||
IUntisClassRegisterCacheRepository classRegisterCache,
|
||||
IUntisStudentRosterCacheRepository rosterCache,
|
||||
IUntisCacheFetchStateRepository fetchState)
|
||||
{
|
||||
private static readonly TimeSpan HotWindowRefreshInterval = TimeSpan.FromHours(1);
|
||||
private const int HotWindowDays = 14;
|
||||
|
||||
/// Roster hat anders als Fehlzeiten/Klassenbuch keine Historie (nur "aktueller Stand einer
|
||||
/// Klasse") - deshalb ohne heißes/kaltes Fenster, nur "gilt der letzte Abruf noch als frisch".
|
||||
/// Behebt, dass die Klassenlehrer-Übersicht bei jedem Öffnen spürbar verzögert wirkte: der
|
||||
/// Roster-Abruf lief bislang komplett am Cache vorbei live gegen WebUntis (Nutzer-Feedback).
|
||||
public async Task<IReadOnlyList<UntisStudentRosterCacheEntry>> GetStudentRosterAsync(string className,
|
||||
bool forceRefresh = false, CancellationToken token = default)
|
||||
{
|
||||
var state = fetchState.Get(className, UntisCacheKind.Roster);
|
||||
if (NeedsRefresh(state, forceRefresh, DateTime.UtcNow))
|
||||
{
|
||||
var fresh = await untis.GetStudentsAsync(className, token);
|
||||
rosterCache.ReplaceAll(className, fresh.Students.Select(s => new UntisStudentRosterCacheEntry
|
||||
{
|
||||
ClassName = className, ExternKey = s.ExternKey, DisplayName = s.DisplayName,
|
||||
}));
|
||||
fetchState.Save(new UntisCacheFetchState
|
||||
{
|
||||
Id = state?.Id ?? Guid.NewGuid(), ClassName = className, Kind = UntisCacheKind.Roster,
|
||||
HotWindowFetchedAt = DateTime.UtcNow,
|
||||
});
|
||||
}
|
||||
return rosterCache.GetByClass(className);
|
||||
}
|
||||
|
||||
private static bool NeedsRefresh(UntisCacheFetchState? state, bool forceRefresh, DateTime utcNow) =>
|
||||
forceRefresh || state?.HotWindowFetchedAt is null || utcNow - state.HotWindowFetchedAt >= HotWindowRefreshInterval;
|
||||
|
||||
public async Task<IReadOnlyList<UntisClassAbsenceEntryDto>> GetAbsencesAsync(string className,
|
||||
DateOnly start, DateOnly end, bool forceRefresh = false, CancellationToken token = default)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.Services;
|
||||
|
||||
namespace LehrerApp.Desktop.ViewModels.ClassTeacher;
|
||||
@@ -22,7 +23,7 @@ public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, b
|
||||
/// (siehe UntisForeignClassRegisterEventDto) — WebUntis liefert Namen dort in anderer
|
||||
/// Reihenfolge als im Schülerreport.</summary>
|
||||
public static IReadOnlyList<ClassTeacherRosterRow> Build(
|
||||
IReadOnlyList<UntisStudentDto> students,
|
||||
IReadOnlyList<UntisStudentRosterCacheEntry> students,
|
||||
IReadOnlyList<ClassAbsenceDaySummaryRow> todayAbsences,
|
||||
IReadOnlyList<UntisForeignClassRegisterEventDto> recentClassRegisterEntries)
|
||||
{
|
||||
@@ -64,7 +65,6 @@ public sealed record ClassTeacherRosterRow(string StudentName, int? ExternKey, b
|
||||
public partial class ClassTeacherOverviewViewModel : ObservableObject
|
||||
{
|
||||
private readonly WebUntisSettingsService _settings;
|
||||
private readonly WebUntisIntegrationService _untis;
|
||||
private readonly UntisReportCacheService _cache;
|
||||
|
||||
public ClassTeacherDetailsViewModel DetailsTab { get; }
|
||||
@@ -81,10 +81,10 @@ public partial class ClassTeacherOverviewViewModel : ObservableObject
|
||||
/// keine Klasse gewählt ist.
|
||||
public Func<Task>? OnNavigateToSettings { get; set; }
|
||||
|
||||
public ClassTeacherOverviewViewModel(WebUntisSettingsService settings, WebUntisIntegrationService untis,
|
||||
public ClassTeacherOverviewViewModel(WebUntisSettingsService settings,
|
||||
UntisReportCacheService cache, ClassTeacherDetailsViewModel detailsTab)
|
||||
{
|
||||
_settings = settings; _untis = untis; _cache = cache; DetailsTab = detailsTab;
|
||||
_settings = settings; _cache = cache; DetailsTab = detailsTab;
|
||||
}
|
||||
|
||||
partial void OnHomeroomClassNameChanged(string? value) => OnPropertyChanged(nameof(HomeroomClassConfigured));
|
||||
@@ -107,13 +107,13 @@ public partial class ClassTeacherOverviewViewModel : ObservableObject
|
||||
try
|
||||
{
|
||||
var today = DateOnly.FromDateTime(DateTime.Today);
|
||||
var studentsTask = _untis.GetStudentsAsync(className);
|
||||
var studentsTask = _cache.GetStudentRosterAsync(className);
|
||||
var absencesTask = _cache.GetAbsencesAsync(className, today, today);
|
||||
var classRegisterTask = _cache.GetClassRegisterEventsAsync(className, today.AddDays(-7), today);
|
||||
await Task.WhenAll(studentsTask, absencesTask, classRegisterTask);
|
||||
|
||||
var todayAbsences = ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absencesTask.Result);
|
||||
foreach (var row in ClassTeacherRosterRow.Build(studentsTask.Result.Students, todayAbsences,
|
||||
foreach (var row in ClassTeacherRosterRow.Build(studentsTask.Result, todayAbsences,
|
||||
classRegisterTask.Result))
|
||||
Roster.Add(row);
|
||||
Status = $"{Roster.Count} Schüler*innen · Stand heute.";
|
||||
|
||||
Reference in New Issue
Block a user