183 lines
7.6 KiB
C#
183 lines
7.6 KiB
C#
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using LehrerApp.Core.Interfaces;
|
|
using LehrerApp.Core.Models;
|
|
using LehrerApp.Core.Services;
|
|
using System.Collections.ObjectModel;
|
|
using System.Globalization;
|
|
|
|
namespace LehrerApp.Desktop.ViewModels.Students;
|
|
|
|
public partial class CreateLetterDialogViewModel : ObservableObject
|
|
{
|
|
private readonly Student _student;
|
|
private readonly LetterTemplateService _templates;
|
|
|
|
[ObservableProperty] private LetterTemplateChoice? _selectedTemplate;
|
|
[ObservableProperty] private LetterContactChoice? _selectedContact;
|
|
[ObservableProperty] private LetterGroupChoice? _selectedGroup;
|
|
[ObservableProperty] private DateTimeOffset? _letterDate = DateTimeOffset.Now;
|
|
[ObservableProperty] private string _generationError = "";
|
|
[ObservableProperty] private bool _canGenerate;
|
|
|
|
public string StudentName => _student.FullName;
|
|
public ObservableCollection<LetterTemplateChoice> Templates { get; } = [];
|
|
public ObservableCollection<LetterContactChoice> Contacts { get; } = [];
|
|
public ObservableCollection<LetterGroupChoice> Groups { get; } = [];
|
|
public ObservableCollection<LetterGenerationIssue> Issues { get; } = [];
|
|
public bool HasIssues => Issues.Count > 0;
|
|
public bool HasNoTemplates => Templates.Count == 0;
|
|
public bool HasNoContacts => Contacts.Count == 0;
|
|
public string SuggestedFileName => SanitizeFileName(
|
|
$"{SelectedTemplate?.Name ?? "Elternbrief"}_{_student.LastName}_{_student.FirstName}.docx");
|
|
|
|
public CreateLetterDialogViewModel(Student student, LetterTemplateService templates,
|
|
IGroupMembershipRepository memberships, IGroupRepository groups)
|
|
{
|
|
_student = student;
|
|
_templates = templates;
|
|
|
|
foreach (var template in templates.GetTemplates())
|
|
Templates.Add(new LetterTemplateChoice(template));
|
|
foreach (var contact in student.Contacts.Where(c => !c.InvalidSince.HasValue).OrderBy(c => c.Name))
|
|
Contacts.Add(new LetterContactChoice(contact));
|
|
foreach (var membership in memberships.GetByStudent(student.Id))
|
|
{
|
|
var group = groups.GetById(membership.GroupId);
|
|
if (group is not null) Groups.Add(new LetterGroupChoice(group));
|
|
}
|
|
|
|
SelectedTemplate = Templates.FirstOrDefault();
|
|
SelectedContact = Contacts.FirstOrDefault();
|
|
SelectedGroup = Groups.FirstOrDefault();
|
|
RefreshValidation();
|
|
}
|
|
|
|
partial void OnSelectedTemplateChanged(LetterTemplateChoice? value)
|
|
{
|
|
OnPropertyChanged(nameof(SuggestedFileName));
|
|
RefreshValidation();
|
|
}
|
|
partial void OnSelectedContactChanged(LetterContactChoice? value) => RefreshValidation();
|
|
partial void OnSelectedGroupChanged(LetterGroupChoice? value) => RefreshValidation();
|
|
partial void OnLetterDateChanged(DateTimeOffset? value) => RefreshValidation();
|
|
|
|
public bool Generate(string outputPath)
|
|
{
|
|
RefreshValidation();
|
|
if (!CanGenerate || SelectedTemplate is null) return false;
|
|
GenerationError = "";
|
|
try
|
|
{
|
|
_templates.Generate(_templates.GetTemplatePath(SelectedTemplate.Model), outputPath, BuildValues());
|
|
return true;
|
|
}
|
|
catch (Exception ex) when (ex is IOException or InvalidDataException or UnauthorizedAccessException)
|
|
{
|
|
GenerationError = $"Der Brief konnte nicht erzeugt werden: {ex.Message}";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void RefreshValidation()
|
|
{
|
|
Issues.Clear();
|
|
GenerationError = "";
|
|
|
|
if (SelectedTemplate is null)
|
|
Issues.Add(new("Bitte zuerst in den Einstellungen eine DOCX-Briefvorlage importieren.", true));
|
|
if (SelectedContact is null)
|
|
Issues.Add(new("Für den Schüler ist kein aktueller Kontakt ausgewählt.", true));
|
|
if (LetterDate is null)
|
|
Issues.Add(new("Bitte ein Briefdatum auswählen.", true));
|
|
|
|
if (SelectedTemplate is not null)
|
|
{
|
|
var validation = _templates.Validate(SelectedTemplate.Model);
|
|
foreach (var issue in validation.Issues)
|
|
Issues.Add(new(issue.Message, issue.Severity is TemplateIssueSeverity.StrongWarning or TemplateIssueSeverity.Error));
|
|
|
|
var tags = validation.Tags.ToHashSet(StringComparer.Ordinal);
|
|
var values = BuildValues();
|
|
foreach (var tag in tags.Where(t => values.TryGetValue(t, out var value) && string.IsNullOrWhiteSpace(value)))
|
|
{
|
|
var message = tag switch
|
|
{
|
|
"Letter.Salutation" => "Beim ausgewählten Kontakt fehlt die Briefanrede.",
|
|
"Contact.Address" or "Contact.Street" or "Contact.PostalCode" or "Contact.City" =>
|
|
$"Beim ausgewählten Kontakt fehlt der Wert für „{tag}“.",
|
|
"Group.Name" or "SchoolYear" => "Die Vorlage verwendet Gruppendaten; bitte eine Lerngruppe auswählen.",
|
|
_ => $"Für das Vorlagenfeld „{tag}“ ist kein Wert vorhanden.",
|
|
};
|
|
Issues.Add(new(message, true));
|
|
}
|
|
}
|
|
|
|
CanGenerate = SelectedTemplate is not null && SelectedContact is not null && LetterDate is not null
|
|
&& Issues.Count == 0;
|
|
OnPropertyChanged(nameof(HasIssues));
|
|
}
|
|
|
|
private IReadOnlyDictionary<string, string?> BuildValues()
|
|
{
|
|
var contact = SelectedContact?.Model;
|
|
var group = SelectedGroup?.Model;
|
|
var cityLine = string.Join(" ", new[] { contact?.PostalCode, contact?.City }
|
|
.Where(v => !string.IsNullOrWhiteSpace(v)));
|
|
var address = string.Join(Environment.NewLine, new[] { contact?.Street, cityLine }
|
|
.Where(v => !string.IsNullOrWhiteSpace(v)));
|
|
var date = LetterDate is null ? null : DateOnly.FromDateTime(LetterDate.Value.LocalDateTime)
|
|
.ToString("dd.MM.yyyy", CultureInfo.GetCultureInfo("de-DE"));
|
|
|
|
return new Dictionary<string, string?>
|
|
{
|
|
["Student.FirstName"] = _student.FirstName,
|
|
["Student.LastName"] = _student.LastName,
|
|
["Contact.Name"] = contact?.Name,
|
|
["Contact.Address"] = address,
|
|
["Contact.Street"] = contact?.Street,
|
|
["Contact.PostalCode"] = contact?.PostalCode,
|
|
["Contact.City"] = contact?.City,
|
|
["Letter.Salutation"] = contact?.LetterSalutation,
|
|
["Group.Name"] = group?.Name,
|
|
["SchoolYear"] = group?.SchoolYear,
|
|
["CurrentDate"] = date,
|
|
};
|
|
}
|
|
|
|
private static string SanitizeFileName(string value)
|
|
{
|
|
foreach (var character in Path.GetInvalidFileNameChars()) value = value.Replace(character, '_');
|
|
return value;
|
|
}
|
|
}
|
|
|
|
public sealed class LetterTemplateChoice
|
|
{
|
|
public LetterTemplateInfo Model { get; }
|
|
public string Name => Model.Name;
|
|
public LetterTemplateChoice(LetterTemplateInfo model) => Model = model;
|
|
}
|
|
|
|
public sealed class LetterContactChoice
|
|
{
|
|
public Contact Model { get; }
|
|
public string Display => string.IsNullOrWhiteSpace(Model.Relation)
|
|
? Model.Name
|
|
: $"{Model.Name} · {Model.Relation}";
|
|
public LetterContactChoice(Contact model) => Model = model;
|
|
}
|
|
|
|
public sealed class LetterGroupChoice
|
|
{
|
|
public LearningGroup Model { get; }
|
|
public string Display => $"{Model.Name} · {Model.SchoolYear}";
|
|
public LetterGroupChoice(LearningGroup model) => Model = model;
|
|
}
|
|
|
|
public sealed class LetterGenerationIssue(string message, bool isStrong)
|
|
{
|
|
public string Icon { get; } = isStrong ? "⚠" : "ⓘ";
|
|
public string Message { get; } = message;
|
|
public string Color { get; } = isStrong ? "#D97706" : "#6B7280";
|
|
}
|