107 lines
6.0 KiB
C#
107 lines
6.0 KiB
C#
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<LetterGroupChoice> Groups { get; } = [];
|
|
public ObservableCollection<StudentReportTemplateChoice> Templates { get; } = [];
|
|
public ObservableCollection<StudentReportInterimChoice> 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<TemplateStore> 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<bool> 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}";
|
|
}
|