using System.Collections.ObjectModel; using System.Globalization; using CommunityToolkit.Mvvm.ComponentModel; using LehrerApp.Core.Interfaces; using LehrerApp.Core.Models; using LehrerApp.Core.Services; using LehrerApp.Desktop.Services; using LehrerApp.Templating; namespace LehrerApp.Desktop.ViewModels.Students; public partial class StudentReportDialogViewModel : ObservableObject { private readonly Student _student; private readonly StudentReportService _reports; private readonly ITemplateRenderer _renderer; private readonly IInterimGradeRepository _interims; private readonly SchoolYearService _schoolYears; private readonly TemplateStore _loader; public string StudentName => _student.FullName; public string SuggestedFileName => string.Concat($"Schülerbericht_{_student.LastName}_{_student.FirstName}.pdf" .Select(c => Path.GetInvalidFileNameChars().Contains(c) ? '_' : c)); public ObservableCollection Groups { get; } = []; public ObservableCollection Templates { get; } = []; public ObservableCollection Interims { get; } = []; [ObservableProperty] private LetterGroupChoice? _selectedGroup; [ObservableProperty] private StudentReportTemplateChoice? _selectedTemplate; [ObservableProperty] private StudentReportInterimChoice? _selectedInterim; [ObservableProperty] private string _from = ""; [ObservableProperty] private string _to = DateTime.Today.ToString("dd.MM.yyyy"); [ObservableProperty] private bool _includeInterim = true; [ObservableProperty] private bool _includeParticipation = true; [ObservableProperty] private bool _includeCompetencies = true; [ObservableProperty] private bool _includeHomework = true; [ObservableProperty] private bool _includeDocumentation = true; [ObservableProperty] private bool _includeConfidential; [ObservableProperty] private string _comment = ""; [ObservableProperty] private string _statusMessage = ""; [ObservableProperty, NotifyPropertyChangedFor(nameof(CanGenerate))] private bool _isGenerating; public bool CanGenerate => !IsGenerating && SelectedGroup is not null && SelectedTemplate is not null; public StudentReportDialogViewModel(Student student, IGroupMembershipRepository memberships, IGroupRepository groups, IInterimGradeRepository interims, StudentReportService reports, ITemplateRenderer renderer, SchoolYearService schoolYears, IReadOnlyList stores) { _student = student; _interims = interims; _reports = reports; _renderer = renderer; _schoolYears = schoolYears; _loader = stores[0]; Templates.Add(new(null)); foreach (var template in stores.SelectMany(s => s.GetTemplates()).Where(t => t.IsStudentReport) .DistinctBy(t => t.PackagePath).OrderBy(t => t.Name)) Templates.Add(new(template)); foreach (var membership in memberships.GetByStudent(student.Id)) if (groups.GetById(membership.GroupId) is { } group && !Groups.Any(g => g.Model.Id == group.Id)) Groups.Add(new(group)); SelectedTemplate = Templates[0]; SelectedGroup = Groups.OrderByDescending(g => g.Model.SchoolYear).FirstOrDefault(); } partial void OnSelectedGroupChanged(LetterGroupChoice? value) { Interims.Clear(); SelectedInterim = null; if (value is not null) { From = _schoolYears.SchoolYearStart(value.Model.SchoolYear).ToString("dd.MM.yyyy"); To = _schoolYears.SchoolYearEnd(value.Model.SchoolYear).ToString("dd.MM.yyyy"); var today = DateOnly.FromDateTime(DateTime.Today); if (_schoolYears.SchoolYearEnd(value.Model.SchoolYear) > today) To = today.ToString("dd.MM.yyyy"); foreach (var grade in _interims.GetByStudentAndGroup(_student.Id, value.Model.Id) .OrderByDescending(g => g.Date).ThenBy(g => g.Occasion)) Interims.Add(new(grade)); } OnPropertyChanged(nameof(CanGenerate)); } partial void OnSelectedTemplateChanged(StudentReportTemplateChoice? value) => OnPropertyChanged(nameof(CanGenerate)); public async Task GenerateAsync(string outputPath) { if (!CanGenerate) return false; StatusMessage = ""; if (!DateOnly.TryParseExact(From, "dd.MM.yyyy", CultureInfo.GetCultureInfo("de-DE"), DateTimeStyles.None, out var from) || !DateOnly.TryParseExact(To, "dd.MM.yyyy", CultureInfo.GetCultureInfo("de-DE"), DateTimeStyles.None, out var to)) { StatusMessage = "Bitte Beginn und Ende als TT.MM.JJJJ eingeben."; return false; } try { var template = SelectedTemplate!.Model is { } installed ? _loader.Load(installed) : StudentReportTemplate.Create(); // Datenbankzugriffe auf dem UI-Thread; nur das Setzen und Schreiben läuft im Hintergrund. var values = _reports.Build(_student, SelectedGroup!.Model, template, new(from, to, SelectedInterim?.Model, IncludeInterim, IncludeParticipation, IncludeCompetencies, IncludeHomework, IncludeDocumentation, IncludeConfidential, Comment)); IsGenerating = true; await Task.Run(() => File.WriteAllBytes(outputPath, _renderer.RenderToPdf(template, new LetterDataProvider(values)))); StatusMessage = "Schülerbericht erzeugt."; return true; } catch (Exception ex) when (ex is IOException or InvalidDataException or UnauthorizedAccessException or TemplateValidationException or QuestPDF.Drawing.Exceptions.DocumentLayoutException) { StatusMessage = $"Der Schülerbericht konnte nicht erzeugt werden: {ex.Message}"; return false; } finally { IsGenerating = false; } } } public sealed record StudentReportTemplateChoice(InstalledTemplate? Model) { public string Name => Model?.Name ?? "Schülerbericht Standard"; } public sealed record StudentReportInterimChoice(InterimGrade Model) { public string Label => $"{Model.Date:dd.MM.yyyy} · {Model.Occasion}"; }