CI / build-and-test (push) Waiting to run
Die DrawingValue-Builder gingen bisher immer von einer festen Breite (170) aus, unabhängig davon, wie breit die DRAWBOX/FLOWDRAWBOX in der jeweiligen Vorlage tatsächlich deklariert ist. Bei einer breiteren Box (oder einer in "pt" statt "mm" deklarierten Seite, wie im echten Fehlzeitenbrief-Template) blieb dadurch ein Großteil der eigentlich verfügbaren Fläche ungenutzt leer - sichtbar als "Luft" rechts und unterhalb des Kalenders im generierten PDF. CreateLetterDialogViewModel/LetterTemplateTools ermitteln jetzt die real deklarierte Boxbreite samt Maßeinheit aus dem geladenen Template (LetterPlaceholderBuilder.FindDeclaredDrawingBox) und reichen sie an die Builder durch, die ihr mm-entworfenes Raster entsprechend skalieren. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
81 lines
4.8 KiB
C#
81 lines
4.8 KiB
C#
using LehrerApp.Core.Models;
|
|
using LehrerApp.Templating;
|
|
|
|
namespace LehrerApp.Desktop.Services;
|
|
|
|
/// <summary>
|
|
/// Baut die Standard-Platzhalterwerte für Elternbrief-Vorlagen (Datum, Anrede, Kontaktadresse,
|
|
/// Schülerdaten, Brieftext, ...). Ursprünglich Teil von
|
|
/// <see cref="ViewModels.Students.CreateLetterDialogViewModel"/>, hierher extrahiert, damit
|
|
/// <c>LetterTemplateTools</c> (MCP, siehe Planungsdokument) dieselbe Logik nutzt statt sie zu
|
|
/// duplizieren — beide Aufrufer müssen exakt dieselben Platzhalternamen befüllen, sonst driften
|
|
/// Dialog-generierte und KI-generierte Briefe unbemerkt auseinander.
|
|
/// </summary>
|
|
public static class LetterPlaceholderBuilder
|
|
{
|
|
public static Dictionary<string, PlaceholderValue> BuildStandardValues(
|
|
Student student, Contact? contact, LearningGroup? group, DateOnly date,
|
|
string letterText, string teacherName, DrawingValue? attendanceCalendar = null,
|
|
DrawingValue? absenceDays = null)
|
|
{
|
|
var address = FormatAddress(contact);
|
|
return new Dictionary<string, PlaceholderValue>(StringComparer.Ordinal)
|
|
{
|
|
["Datum"] = new DateValue(date), ["CurrentDate"] = new DateValue(date),
|
|
["Empfaenger"] = new TextValue(contact?.Name ?? ""), ["Anrede"] = new TextValue(contact?.LetterSalutation ?? ""),
|
|
["Brieftext"] = new MultilineValue(letterText), ["LehrerName"] = new TextValue(teacherName),
|
|
["Student.FirstName"] = new TextValue(student.FirstName), ["Student.LastName"] = new TextValue(student.LastName),
|
|
["Student.Name"] = new TextValue(student.FullName),
|
|
["Contact.Name"] = new TextValue(contact?.Name ?? ""), ["Contact.Address"] = new MultilineValue(address),
|
|
["Contact.Street"] = new TextValue(contact?.Street ?? ""), ["Contact.PostalCode"] = new TextValue(contact?.PostalCode ?? ""),
|
|
["Contact.City"] = new TextValue(contact?.City ?? ""), ["Letter.Salutation"] = new TextValue(contact?.LetterSalutation ?? ""),
|
|
["Group.Name"] = new TextValue(group?.Name ?? ""), ["SchoolYear"] = new TextValue(group?.SchoolYear ?? ""),
|
|
[StudentAttendanceCalendarDrawingBuilder.PlaceholderName] = attendanceCalendar ?? new DrawingValue([], 0),
|
|
[StudentAbsenceDayListDrawingBuilder.PlaceholderName] = absenceDays ?? new DrawingValue([], 0),
|
|
};
|
|
}
|
|
|
|
/// <summary>Mehrzeilige Anschrift (Name/Straße/PLZ Ort) für Adressvorschau im Dialog und
|
|
/// den <c>Contact.Address</c>-Platzhalter - eine Formatierung für beide Verwendungen.</summary>
|
|
public static string FormatAddress(Contact? contact)
|
|
{
|
|
var cityLine = string.Join(" ", new[] { contact?.PostalCode, contact?.City }.Where(x => !string.IsNullOrWhiteSpace(x)));
|
|
return string.Join(Environment.NewLine, new[] { contact?.Name, contact?.Street, cityLine }.Where(x => !string.IsNullOrWhiteSpace(x)));
|
|
}
|
|
|
|
public static bool IsEmpty(PlaceholderValue value) => value switch
|
|
{
|
|
TextValue x => string.IsNullOrWhiteSpace(x.Value),
|
|
MultilineValue x => string.IsNullOrWhiteSpace(x.Value),
|
|
DrawingValue x => x.ContentHeight <= 0 || x.Commands.Count == 0,
|
|
_ => false,
|
|
};
|
|
|
|
/// <summary>Tatsächliche DRAWBOX/FLOWDRAWBOX-Breite eines Platzhalters (in der Maßeinheit der
|
|
/// Vorlage) plus dem Umrechnungsfaktor "1mm in dieser Maßeinheit". DrawingValue-Builder wie
|
|
/// <see cref="StudentAttendanceCalendarDrawingBuilder"/> entwerfen ihr Raster in Millimetern;
|
|
/// ohne MillimeterScale bliebe das bei einer in "pt" (statt "mm") deklarierten Vorlage viel zu
|
|
/// klein (1 "mm-Einheit" würde als 1pt ≈ 0.35mm gerendert).</summary>
|
|
public readonly record struct DeclaredDrawingBox(float Width, float MillimeterScale);
|
|
|
|
public static DeclaredDrawingBox? FindDeclaredDrawingBox(LoadedTemplate template, string placeholderName) =>
|
|
FindDeclaredDrawingBox(template.Layout, placeholderName) ??
|
|
(template.ContinuationLayout is not null ? FindDeclaredDrawingBox(template.ContinuationLayout, placeholderName) : null);
|
|
|
|
private static DeclaredDrawingBox? FindDeclaredDrawingBox(TemplateLayout layout, string placeholderName)
|
|
{
|
|
var width = layout.Elements.Concat(layout.PageTemplates.SelectMany(p => p.Elements))
|
|
.Concat(layout.ContentFlows.SelectMany(f => f.Elements))
|
|
.Select(e => e switch
|
|
{
|
|
DrawBoxElement draw when draw.Placeholder == placeholderName => (float?)draw.Width,
|
|
FlowDrawBoxElement flow when flow.Placeholder == placeholderName => (float?)flow.Width,
|
|
_ => null,
|
|
})
|
|
.FirstOrDefault(w => w is not null);
|
|
if (width is null) return null;
|
|
var millimeterScale = UnitConverter.Points(1, "mm") / UnitConverter.Points(1, layout.Unit);
|
|
return new DeclaredDrawingBox(width.Value, millimeterScale);
|
|
}
|
|
}
|