CI / build-and-test (push) Waiting to run
Nachdem die Kalender-Breite jetzt an die reale DRAWBOX/FLOWDRAWBOX-Breite gekoppelt ist, konnte das (korrekt größer werdende) Raster die von der Vorlage deklarierte Höhe überschreiten - eine DRAWBOX bricht anders als eine FLOWDRAWBOX nicht automatisch auf Folgeseiten um, überschüssiger Inhalt wird von QuestTemplateRenderer stillschweigend am unteren Rand abgeschnitten. StudentAttendanceCalendarDrawingBuilder ermittelt jetzt vorab, wie viele Wochen die gewählten Monate brauchen, und verkleinert "Größe" automatisch so weit, dass der Kalender innerhalb der deklarierten Höhe bleibt (nur bei DRAWBOX/IsFixed - eine FLOWDRAWBOX darf weiterhin frei wachsen, da sie bei Bedarf auf weitere Seiten fließt statt abzuschneiden). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
85 lines
5.3 KiB
C#
85 lines
5.3 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-Größe 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). IsFixed unterscheidet DRAWBOX (feste,
|
|
/// nicht umbrechende Box - Inhalt, der die Höhe sprengt, wird von QuestTemplateRenderer
|
|
/// stillschweigend am unteren Rand abgeschnitten statt umzubrechen) von FLOWDRAWBOX (fließt bei
|
|
/// Bedarf automatisch auf weitere Seiten, siehe DrawingElementRenderer.Slice) - nur bei
|
|
/// IsFixed=true darf/muss ein Builder seine Höhe an Height anpassen.</summary>
|
|
public readonly record struct DeclaredDrawingBox(float Width, float Height, float MillimeterScale, bool IsFixed);
|
|
|
|
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 box = 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 Width, float Height, bool IsFixed)?)(draw.Width, draw.Height, true),
|
|
FlowDrawBoxElement flow when flow.Placeholder == placeholderName => (flow.Width, flow.Height, false),
|
|
_ => null,
|
|
})
|
|
.FirstOrDefault(b => b is not null);
|
|
if (box is null) return null;
|
|
var millimeterScale = UnitConverter.Points(1, "mm") / UnitConverter.Points(1, layout.Unit);
|
|
return new DeclaredDrawingBox(box.Value.Width, box.Value.Height, millimeterScale, box.Value.IsFixed);
|
|
}
|
|
}
|