feat: Fehlzeiten Tabelle für Templates
This commit is contained in:
@@ -127,6 +127,32 @@ public sealed class ClassTeacherViewModelsTests
|
|||||||
Assert.True(large.ContentHeight > small.ContentHeight);
|
Assert.True(large.ContentHeight > small.ContentHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ElternbriefFehltage_ListetDatumUmfangUndEntschuldigungsstatus()
|
||||||
|
{
|
||||||
|
var options = new AttendanceCalendarOptions(new DateOnly(2026, 9, 1), 1);
|
||||||
|
var absences = new[]
|
||||||
|
{
|
||||||
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 3), "Müller Ada", 1, 6, 270,
|
||||||
|
["Deu"], [1, 2, 3, 4, 5, 6], ["entsch."], ["Krank"], null, null, true),
|
||||||
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 8), "Müller Ada", 1, 2, 90,
|
||||||
|
["Mathe"], [1, 2], ["nicht entsch."], ["Absent"], null, null, false),
|
||||||
|
new ClassAbsenceDaySummaryRow(new DateOnly(2026, 9, 9), "Schmidt Ben", 2, 4, 180,
|
||||||
|
["Eng"], [1, 2, 3, 4], ["entsch."], ["Krank"], null, null, true),
|
||||||
|
};
|
||||||
|
|
||||||
|
var drawing = StudentAbsenceDayListDrawingBuilder.Build("Ada Müller", options, absences);
|
||||||
|
var labels = drawing.Commands.OfType<DrawStringEx>().Select(c => c.Text).ToList();
|
||||||
|
|
||||||
|
Assert.Contains("03.09.2026", labels);
|
||||||
|
Assert.Contains("Ganzer Fehltag", labels);
|
||||||
|
Assert.Contains("08.09.2026", labels);
|
||||||
|
Assert.Contains("Fehlzeit · 2 Std.", labels);
|
||||||
|
Assert.Contains("Entschuldigt", labels);
|
||||||
|
Assert.Contains("Unentschuldigt", labels);
|
||||||
|
Assert.DoesNotContain("09.09.2026", labels);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void GroupByStudentAndDay_FasstFehlstundenProSchuelerUndTagZusammen()
|
public void GroupByStudentAndDay_FasstFehlstundenProSchuelerUndTagZusammen()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -76,6 +76,23 @@ public sealed class CreateLetterDialogViewModelTests : IDisposable
|
|||||||
Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(File.ReadAllBytes(output), 0, 4));
|
Assert.Equal("%PDF", System.Text.Encoding.ASCII.GetString(File.ReadAllBytes(output), 0, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void AdvancedContent_Fehlzeitenliste_AktiviertKonfigurationsschritt()
|
||||||
|
{
|
||||||
|
var store = StoreWithTemplate(new PlaceholderDefinition(
|
||||||
|
StudentAbsenceDayListDrawingBuilder.PlaceholderName, PlaceholderType.Drawing, true));
|
||||||
|
var drawing = StudentAbsenceDayListDrawingBuilder.Build("Lena Beispiel",
|
||||||
|
new AttendanceCalendarOptions(new DateOnly(2026, 9, 1), 1), []);
|
||||||
|
var vm = new CreateLetterDialogViewModel(StudentWithContact("Sehr geehrte Frau Muster,"), store,
|
||||||
|
new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([]),
|
||||||
|
attendanceCalendarFactory: null, absenceDayListFactory: _ => drawing);
|
||||||
|
|
||||||
|
Assert.False(vm.UsesAttendanceCalendar);
|
||||||
|
Assert.True(vm.UsesAbsenceDayList);
|
||||||
|
Assert.True(vm.UsesAttendanceAdvancedContent);
|
||||||
|
Assert.True(vm.CanGenerate);
|
||||||
|
}
|
||||||
|
|
||||||
private CreateLetterDialogViewModel Build(Student student, TemplateStore store) =>
|
private CreateLetterDialogViewModel Build(Student student, TemplateStore store) =>
|
||||||
new(student, store, new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([]));
|
new(student, store, new QuestTemplateRenderer(), new FakeMemberships([]), new FakeGroups([]));
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,9 @@ public static class LetterDialogs
|
|||||||
App.Services.GetRequiredService<ITemplateRenderer>(),
|
App.Services.GetRequiredService<ITemplateRenderer>(),
|
||||||
App.Services.GetRequiredService<IGroupMembershipRepository>(),
|
App.Services.GetRequiredService<IGroupMembershipRepository>(),
|
||||||
App.Services.GetRequiredService<IGroupRepository>(),
|
App.Services.GetRequiredService<IGroupRepository>(),
|
||||||
options => App.Services.GetRequiredService<StudentAttendanceCalendarService>().Build(student, options));
|
options => App.Services.GetRequiredService<StudentAttendanceCalendarService>().Build(student, options),
|
||||||
|
options => App.Services.GetRequiredService<StudentAttendanceCalendarService>()
|
||||||
|
.BuildAbsenceDayList(student, options));
|
||||||
var dialog = new CreateLetterDialog { DataContext = vm };
|
var dialog = new CreateLetterDialog { DataContext = vm };
|
||||||
var path = await dialog.ShowDialog<string?>(owner);
|
var path = await dialog.ShowDialog<string?>(owner);
|
||||||
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ public static class LetterPlaceholderBuilder
|
|||||||
{
|
{
|
||||||
public static Dictionary<string, PlaceholderValue> BuildStandardValues(
|
public static Dictionary<string, PlaceholderValue> BuildStandardValues(
|
||||||
Student student, Contact? contact, LearningGroup? group, DateOnly date,
|
Student student, Contact? contact, LearningGroup? group, DateOnly date,
|
||||||
string letterText, string teacherName, DrawingValue? attendanceCalendar = null)
|
string letterText, string teacherName, DrawingValue? attendanceCalendar = null,
|
||||||
|
DrawingValue? absenceDays = null)
|
||||||
{
|
{
|
||||||
var cityLine = string.Join(" ", new[] { contact?.PostalCode, contact?.City }.Where(x => !string.IsNullOrWhiteSpace(x)));
|
var cityLine = string.Join(" ", new[] { contact?.PostalCode, contact?.City }.Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||||
var address = string.Join(Environment.NewLine, new[] { contact?.Name, contact?.Street, cityLine }.Where(x => !string.IsNullOrWhiteSpace(x)));
|
var address = string.Join(Environment.NewLine, new[] { contact?.Name, contact?.Street, cityLine }.Where(x => !string.IsNullOrWhiteSpace(x)));
|
||||||
@@ -30,6 +31,7 @@ public static class LetterPlaceholderBuilder
|
|||||||
["Contact.City"] = new TextValue(contact?.City ?? ""), ["Letter.Salutation"] = new TextValue(contact?.LetterSalutation ?? ""),
|
["Contact.City"] = new TextValue(contact?.City ?? ""), ["Letter.Salutation"] = new TextValue(contact?.LetterSalutation ?? ""),
|
||||||
["Group.Name"] = new TextValue(group?.Name ?? ""), ["SchoolYear"] = new TextValue(group?.SchoolYear ?? ""),
|
["Group.Name"] = new TextValue(group?.Name ?? ""), ["SchoolYear"] = new TextValue(group?.SchoolYear ?? ""),
|
||||||
[StudentAttendanceCalendarDrawingBuilder.PlaceholderName] = attendanceCalendar ?? new DrawingValue([], 0),
|
[StudentAttendanceCalendarDrawingBuilder.PlaceholderName] = attendanceCalendar ?? new DrawingValue([], 0),
|
||||||
|
[StudentAbsenceDayListDrawingBuilder.PlaceholderName] = absenceDays ?? new DrawingValue([], 0),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,8 @@ public class LetterTemplateTools(
|
|||||||
var date = letterDate ?? DateOnly.FromDateTime(DateTime.Today);
|
var date = letterDate ?? DateOnly.FromDateTime(DateTime.Today);
|
||||||
|
|
||||||
var values = LetterPlaceholderBuilder.BuildStandardValues(student, contact, group, date, letterText,
|
var values = LetterPlaceholderBuilder.BuildStandardValues(student, contact, group, date, letterText,
|
||||||
teacherName, attendanceCalendars?.Build(student, date));
|
teacherName, attendanceCalendars?.Build(student, date),
|
||||||
|
attendanceCalendars?.BuildAbsenceDayList(student, new AttendanceCalendarOptions(date, 1)));
|
||||||
foreach (var (name, raw) in extraValues ?? [])
|
foreach (var (name, raw) in extraValues ?? [])
|
||||||
{
|
{
|
||||||
var definition = loaded.Manifest.Placeholders.FirstOrDefault(p => p.Name == name);
|
var definition = loaded.Manifest.Placeholders.FirstOrDefault(p => p.Name == name);
|
||||||
|
|||||||
@@ -91,6 +91,87 @@ public static class StudentAttendanceCalendarDrawingBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Chronologische, portable Fehlzeitenliste für Elternbriefvorlagen.</summary>
|
||||||
|
public static class StudentAbsenceDayListDrawingBuilder
|
||||||
|
{
|
||||||
|
public const string PlaceholderName = "Student.AbsenceDays";
|
||||||
|
|
||||||
|
public static DrawingValue Build(string studentName, AttendanceCalendarOptions options,
|
||||||
|
IReadOnlyList<ClassAbsenceDaySummaryRow> absences)
|
||||||
|
{
|
||||||
|
const float width = 170;
|
||||||
|
var scale = options.Size switch
|
||||||
|
{
|
||||||
|
AttendanceCalendarSize.Small => .75f,
|
||||||
|
AttendanceCalendarSize.Large => 1f,
|
||||||
|
_ => .88f,
|
||||||
|
};
|
||||||
|
var contentWidth = width * scale;
|
||||||
|
var rowHeight = 9 * scale;
|
||||||
|
var start = options.NormalizedStartMonth;
|
||||||
|
var end = start.AddMonths(options.NormalizedMonthCount).AddDays(-1);
|
||||||
|
var rows = absences
|
||||||
|
.Where(a => a.Date >= start && a.Date <= end &&
|
||||||
|
UntisNameMatching.NamesMatch(a.StudentName, studentName))
|
||||||
|
.OrderBy(a => a.Date)
|
||||||
|
.ToList();
|
||||||
|
var commands = new List<DrawingCommand>
|
||||||
|
{
|
||||||
|
new DrawStringEx(0, 0, 7 * scale, contentWidth, "Fehltage",
|
||||||
|
DrawingTextAlignment.AlignLeft, 11 * scale, Color: "#1F2937", Bold: true),
|
||||||
|
new DrawStringEx(0, 7 * scale, 6 * scale, contentWidth, studentName,
|
||||||
|
DrawingTextAlignment.AlignLeft, 8 * scale, Color: "#6B7280"),
|
||||||
|
};
|
||||||
|
var y = 16 * scale;
|
||||||
|
commands.Add(new DrawRectangle(0, y, contentWidth, rowHeight, "#CBD5E1", .4f, "#F3F4F6"));
|
||||||
|
commands.Add(new DrawStringEx(2 * scale, y + scale, rowHeight - 2 * scale, 31 * scale, "Datum",
|
||||||
|
DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151", Bold: true));
|
||||||
|
commands.Add(new DrawStringEx(36 * scale, y + scale, rowHeight - 2 * scale, 75 * scale, "Umfang",
|
||||||
|
DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151", Bold: true));
|
||||||
|
commands.Add(new DrawStringEx(113 * scale, y + scale, rowHeight - 2 * scale, 55 * scale, "Status",
|
||||||
|
DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151", Bold: true));
|
||||||
|
y += rowHeight;
|
||||||
|
|
||||||
|
if (rows.Count == 0)
|
||||||
|
{
|
||||||
|
commands.Add(new DrawStringEx(2 * scale, y + 2 * scale, rowHeight, contentWidth - 4 * scale,
|
||||||
|
"Keine Fehltage im gewählten Zeitraum", DrawingTextAlignment.AlignLeft, 8 * scale,
|
||||||
|
Color: "#6B7280", Italic: true));
|
||||||
|
y += rowHeight + 3 * scale;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (var index = 0; index < rows.Count; index++)
|
||||||
|
{
|
||||||
|
var row = rows[index];
|
||||||
|
var fill = index % 2 == 0 ? "#FFFFFF" : "#F9FAFB";
|
||||||
|
var extent = row.CountsAsFullDay
|
||||||
|
? "Ganzer Fehltag"
|
||||||
|
: row.TotalAbsentPeriods > 0
|
||||||
|
? $"Fehlzeit · {row.TotalAbsentPeriods} Std."
|
||||||
|
: $"Fehlzeit · {row.TotalAbsentMinutes} Min.";
|
||||||
|
var status = row.IsUnexcused
|
||||||
|
? "Unentschuldigt"
|
||||||
|
: row.FriendlyStatusLabel.Contains("Entschuldigt", StringComparison.OrdinalIgnoreCase)
|
||||||
|
? "Entschuldigt"
|
||||||
|
: row.FriendlyStatusLabel;
|
||||||
|
var statusColor = row.IsUnexcused ? "#C62828" : "#2E7D32";
|
||||||
|
commands.Add(new DrawRectangle(0, y, contentWidth, rowHeight, "#E5E7EB", .3f, fill));
|
||||||
|
commands.Add(new DrawStringEx(2 * scale, y + scale, rowHeight - 2 * scale, 31 * scale,
|
||||||
|
row.Date.ToString("dd.MM.yyyy"), DrawingTextAlignment.AlignLeft, 7.5f * scale,
|
||||||
|
Color: "#374151"));
|
||||||
|
commands.Add(new DrawStringEx(36 * scale, y + scale, rowHeight - 2 * scale, 75 * scale,
|
||||||
|
extent, DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: "#374151"));
|
||||||
|
commands.Add(new DrawStringEx(113 * scale, y + scale, rowHeight - 2 * scale, 55 * scale,
|
||||||
|
status, DrawingTextAlignment.AlignLeft, 7.5f * scale, Color: statusColor,
|
||||||
|
Bold: row.IsUnexcused));
|
||||||
|
y += rowHeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new DrawingValue(commands, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Liest ausschließlich den lokalen WebUntis-Cache. Das Öffnen oder Rendern eines
|
/// <summary>Liest ausschließlich den lokalen WebUntis-Cache. Das Öffnen oder Rendern eines
|
||||||
/// Elternbriefs löst dadurch keinen unerwarteten Netzwerkabruf aus.</summary>
|
/// Elternbriefs löst dadurch keinen unerwarteten Netzwerkabruf aus.</summary>
|
||||||
public sealed class StudentAttendanceCalendarService(
|
public sealed class StudentAttendanceCalendarService(
|
||||||
@@ -126,4 +207,24 @@ public sealed class StudentAttendanceCalendarService(
|
|||||||
return StudentAttendanceCalendarDrawingBuilder.Build(rosterName, options,
|
return StudentAttendanceCalendarDrawingBuilder.Build(rosterName, options,
|
||||||
ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absences), register);
|
ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absences), register);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DrawingValue BuildAbsenceDayList(Student student, AttendanceCalendarOptions options)
|
||||||
|
{
|
||||||
|
var className = settings.HomeroomClassName;
|
||||||
|
if (string.IsNullOrWhiteSpace(className)) return new DrawingValue([], 0);
|
||||||
|
|
||||||
|
var first = options.NormalizedStartMonth;
|
||||||
|
var last = first.AddMonths(options.NormalizedMonthCount).AddDays(-1);
|
||||||
|
var rosterName = rosterCache.GetByClass(className)
|
||||||
|
.FirstOrDefault(r => UntisNameMatching.NamesMatch(r.DisplayName, student.FullName))?.DisplayName
|
||||||
|
?? student.FullName;
|
||||||
|
var start = first.Year * 10000 + first.Month * 100 + first.Day;
|
||||||
|
var end = last.Year * 10000 + last.Month * 100 + last.Day;
|
||||||
|
var absences = absenceCache.GetByClassAndRange(className, start, end)
|
||||||
|
.Select(e => new UntisClassAbsenceEntryDto(e.StudentName, e.ExternKey, e.ClassName, e.Date,
|
||||||
|
e.AbsentPeriods, e.AbsentMinutes, e.TeacherUsernames, e.Subject, e.AbsenceReason, e.Note,
|
||||||
|
e.EntryId, e.HandledOn, e.Counts, e.ExcuseNote, e.PeriodNumber, e.Status, e.CountsAsFullDay));
|
||||||
|
return StudentAbsenceDayListDrawingBuilder.Build(rosterName, options,
|
||||||
|
ClassAbsenceDaySummaryRow.GroupByStudentAndDay(absences));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
|||||||
private readonly TemplateStore _templates;
|
private readonly TemplateStore _templates;
|
||||||
private readonly ITemplateRenderer _renderer;
|
private readonly ITemplateRenderer _renderer;
|
||||||
private readonly Func<AttendanceCalendarOptions, DrawingValue>? _attendanceCalendarFactory;
|
private readonly Func<AttendanceCalendarOptions, DrawingValue>? _attendanceCalendarFactory;
|
||||||
|
private readonly Func<AttendanceCalendarOptions, DrawingValue>? _absenceDayListFactory;
|
||||||
private AttendanceCalendarOptions _attendanceCalendarOptions = new(
|
private AttendanceCalendarOptions _attendanceCalendarOptions = new(
|
||||||
new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1), 1);
|
new DateOnly(DateTime.Today.Year, DateTime.Today.Month, 1), 1);
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
|||||||
[ObservableProperty] private string _generationError = "";
|
[ObservableProperty] private string _generationError = "";
|
||||||
[ObservableProperty] private bool _canGenerate;
|
[ObservableProperty] private bool _canGenerate;
|
||||||
[ObservableProperty] private bool _usesAttendanceCalendar;
|
[ObservableProperty] private bool _usesAttendanceCalendar;
|
||||||
|
[ObservableProperty] private bool _usesAbsenceDayList;
|
||||||
[ObservableProperty] private bool _attendanceCalendarConfigured;
|
[ObservableProperty] private bool _attendanceCalendarConfigured;
|
||||||
[ObservableProperty] private string _attendanceCalendarSummary = "1 Monat · Standardgröße";
|
[ObservableProperty] private string _attendanceCalendarSummary = "1 Monat · Standardgröße";
|
||||||
|
|
||||||
@@ -36,15 +38,18 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
|||||||
public bool HasIssues => Issues.Count > 0;
|
public bool HasIssues => Issues.Count > 0;
|
||||||
public bool HasNoTemplates => Templates.Count == 0;
|
public bool HasNoTemplates => Templates.Count == 0;
|
||||||
public bool HasNoContacts => Contacts.Count == 0;
|
public bool HasNoContacts => Contacts.Count == 0;
|
||||||
|
public bool UsesAttendanceAdvancedContent => UsesAttendanceCalendar || UsesAbsenceDayList;
|
||||||
public string SuggestedFileName => SanitizeFileName(
|
public string SuggestedFileName => SanitizeFileName(
|
||||||
$"{SelectedTemplate?.Name ?? "Elternbrief"}_{_student.LastName}_{_student.FirstName}.pdf");
|
$"{SelectedTemplate?.Name ?? "Elternbrief"}_{_student.LastName}_{_student.FirstName}.pdf");
|
||||||
|
|
||||||
public CreateLetterDialogViewModel(Student student, TemplateStore templates, ITemplateRenderer renderer,
|
public CreateLetterDialogViewModel(Student student, TemplateStore templates, ITemplateRenderer renderer,
|
||||||
IGroupMembershipRepository memberships, IGroupRepository groups,
|
IGroupMembershipRepository memberships, IGroupRepository groups,
|
||||||
Func<AttendanceCalendarOptions, DrawingValue>? attendanceCalendarFactory = null)
|
Func<AttendanceCalendarOptions, DrawingValue>? attendanceCalendarFactory = null,
|
||||||
|
Func<AttendanceCalendarOptions, DrawingValue>? absenceDayListFactory = null)
|
||||||
{
|
{
|
||||||
_student = student; _templates = templates; _renderer = renderer;
|
_student = student; _templates = templates; _renderer = renderer;
|
||||||
_attendanceCalendarFactory = attendanceCalendarFactory;
|
_attendanceCalendarFactory = attendanceCalendarFactory;
|
||||||
|
_absenceDayListFactory = absenceDayListFactory;
|
||||||
foreach (var template in templates.GetTemplates()) Templates.Add(new(template));
|
foreach (var template in templates.GetTemplates()) Templates.Add(new(template));
|
||||||
foreach (var contact in student.Contacts.Where(c => !c.InvalidSince.HasValue).OrderBy(c => c.Name)) Contacts.Add(new(contact));
|
foreach (var contact in student.Contacts.Where(c => !c.InvalidSince.HasValue).OrderBy(c => c.Name)) Contacts.Add(new(contact));
|
||||||
foreach (var membership in memberships.GetByStudent(student.Id))
|
foreach (var membership in memberships.GetByStudent(student.Id))
|
||||||
@@ -56,7 +61,11 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
|||||||
partial void OnSelectedTemplateChanged(LetterTemplateChoice? value)
|
partial void OnSelectedTemplateChanged(LetterTemplateChoice? value)
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(SuggestedFileName));
|
OnPropertyChanged(nameof(SuggestedFileName));
|
||||||
UsesAttendanceCalendar = TemplateUsesAttendanceCalendar(value);
|
UsesAttendanceCalendar = TemplateUsesPlaceholder(value,
|
||||||
|
StudentAttendanceCalendarDrawingBuilder.PlaceholderName);
|
||||||
|
UsesAbsenceDayList = TemplateUsesPlaceholder(value,
|
||||||
|
StudentAbsenceDayListDrawingBuilder.PlaceholderName);
|
||||||
|
OnPropertyChanged(nameof(UsesAttendanceAdvancedContent));
|
||||||
AttendanceCalendarConfigured = false;
|
AttendanceCalendarConfigured = false;
|
||||||
ResetAttendanceCalendarOptions();
|
ResetAttendanceCalendarOptions();
|
||||||
RefreshValidation();
|
RefreshValidation();
|
||||||
@@ -111,7 +120,8 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
|||||||
private IReadOnlyDictionary<string, PlaceholderValue> BuildValues() => LetterPlaceholderBuilder.BuildStandardValues(
|
private IReadOnlyDictionary<string, PlaceholderValue> BuildValues() => LetterPlaceholderBuilder.BuildStandardValues(
|
||||||
_student, SelectedContact?.Model, SelectedGroup?.Model,
|
_student, SelectedContact?.Model, SelectedGroup?.Model,
|
||||||
DateOnly.FromDateTime((LetterDate ?? DateTimeOffset.Now).LocalDateTime), LetterText, TeacherName,
|
DateOnly.FromDateTime((LetterDate ?? DateTimeOffset.Now).LocalDateTime), LetterText, TeacherName,
|
||||||
UsesAttendanceCalendar ? _attendanceCalendarFactory?.Invoke(_attendanceCalendarOptions) : null);
|
UsesAttendanceCalendar ? _attendanceCalendarFactory?.Invoke(_attendanceCalendarOptions) : null,
|
||||||
|
UsesAbsenceDayList ? _absenceDayListFactory?.Invoke(_attendanceCalendarOptions) : null);
|
||||||
|
|
||||||
public AttendanceCalendarOptions GetAttendanceCalendarOptions() => _attendanceCalendarOptions;
|
public AttendanceCalendarOptions GetAttendanceCalendarOptions() => _attendanceCalendarOptions;
|
||||||
|
|
||||||
@@ -131,14 +141,14 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
|||||||
AttendanceCalendarSummary = FormatAttendanceCalendarSummary(_attendanceCalendarOptions);
|
AttendanceCalendarSummary = FormatAttendanceCalendarSummary(_attendanceCalendarOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TemplateUsesAttendanceCalendar(LetterTemplateChoice? choice)
|
private bool TemplateUsesPlaceholder(LetterTemplateChoice? choice, string placeholderName)
|
||||||
{
|
{
|
||||||
if (choice is null) return false;
|
if (choice is null) return false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var loaded = _templates.Load(choice.Model);
|
var loaded = _templates.Load(choice.Model);
|
||||||
return UsesPlaceholder(loaded.Layout) ||
|
return UsesPlaceholder(loaded.Layout, placeholderName) ||
|
||||||
(loaded.ContinuationLayout is not null && UsesPlaceholder(loaded.ContinuationLayout));
|
(loaded.ContinuationLayout is not null && UsesPlaceholder(loaded.ContinuationLayout, placeholderName));
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is IOException or InvalidDataException or TemplateValidationException)
|
catch (Exception ex) when (ex is IOException or InvalidDataException or TemplateValidationException)
|
||||||
{
|
{
|
||||||
@@ -146,11 +156,11 @@ public partial class CreateLetterDialogViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool UsesPlaceholder(TemplateLayout layout) =>
|
private static bool UsesPlaceholder(TemplateLayout layout, string placeholderName) =>
|
||||||
layout.Elements.Concat(layout.PageTemplates.SelectMany(p => p.Elements))
|
layout.Elements.Concat(layout.PageTemplates.SelectMany(p => p.Elements))
|
||||||
.Concat(layout.ContentFlows.SelectMany(f => f.Elements))
|
.Concat(layout.ContentFlows.SelectMany(f => f.Elements))
|
||||||
.Any(e => e is DrawBoxElement { Placeholder: StudentAttendanceCalendarDrawingBuilder.PlaceholderName }
|
.Any(e => e is DrawBoxElement draw && draw.Placeholder == placeholderName ||
|
||||||
or FlowDrawBoxElement { Placeholder: StudentAttendanceCalendarDrawingBuilder.PlaceholderName });
|
e is FlowDrawBoxElement flow && flow.Placeholder == placeholderName);
|
||||||
|
|
||||||
private static string FormatAttendanceCalendarSummary(AttendanceCalendarOptions options)
|
private static string FormatAttendanceCalendarSummary(AttendanceCalendarOptions options)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
xmlns:conv="clr-namespace:LehrerApp.Desktop.Converters"
|
xmlns:conv="clr-namespace:LehrerApp.Desktop.Converters"
|
||||||
x:Class="LehrerApp.Desktop.Views.Students.AttendanceCalendarConfigurationDialog"
|
x:Class="LehrerApp.Desktop.Views.Students.AttendanceCalendarConfigurationDialog"
|
||||||
x:DataType="vm:AttendanceCalendarConfigurationViewModel"
|
x:DataType="vm:AttendanceCalendarConfigurationViewModel"
|
||||||
Title="Anwesenheitskalender konfigurieren" Width="440" Height="360"
|
Title="Anwesenheitsdaten konfigurieren" Width="440" Height="360"
|
||||||
CanResize="False" WindowStartupLocation="CenterOwner">
|
CanResize="False" WindowStartupLocation="CenterOwner">
|
||||||
<Grid RowDefinitions="*,Auto" Margin="24,20">
|
<Grid RowDefinitions="*,Auto" Margin="24,20">
|
||||||
<StackPanel Spacing="16">
|
<StackPanel Spacing="16">
|
||||||
<TextBlock Text="Anwesenheitskalender" Classes="dialogtitle"/>
|
<TextBlock Text="Anwesenheitsdaten" Classes="dialogtitle"/>
|
||||||
<TextBlock Text="Diese Angaben steuern den dynamischen Kalender im Elternbrief. Der Starttag wird automatisch auf den ersten Tag des gewählten Monats gesetzt."
|
<TextBlock Text="Diese Angaben steuern Kalender und Fehlzeitenliste im Elternbrief. Der Starttag wird automatisch auf den ersten Tag des gewählten Monats gesetzt."
|
||||||
FontSize="12" Opacity="0.65" TextWrapping="Wrap"/>
|
FontSize="12" Opacity="0.65" TextWrapping="Wrap"/>
|
||||||
<StackPanel Spacing="4">
|
<StackPanel Spacing="4">
|
||||||
<TextBlock Text="Erster Monat" FontSize="12" Opacity="0.7"/>
|
<TextBlock Text="Erster Monat" FontSize="12" Opacity="0.7"/>
|
||||||
|
|||||||
@@ -44,11 +44,11 @@
|
|||||||
<CalendarDatePicker SelectedDate="{Binding LetterDate, Converter={x:Static conv:DateTimeOffsetToDateTimeConverter.Instance}}" HorizontalAlignment="Stretch"/>
|
<CalendarDatePicker SelectedDate="{Binding LetterDate, Converter={x:Static conv:DateTimeOffsetToDateTimeConverter.Instance}}" HorizontalAlignment="Stretch"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Border IsVisible="{Binding UsesAttendanceCalendar}" BorderBrush="{DynamicResource AppCardBorderBrush}"
|
<Border IsVisible="{Binding UsesAttendanceAdvancedContent}" BorderBrush="{DynamicResource AppCardBorderBrush}"
|
||||||
BorderThickness="1" CornerRadius="6" Padding="12">
|
BorderThickness="1" CornerRadius="6" Padding="12">
|
||||||
<Grid ColumnDefinitions="*,Auto">
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
<StackPanel Spacing="3">
|
<StackPanel Spacing="3">
|
||||||
<TextBlock Text="Anwesenheitskalender im Brief" FontWeight="SemiBold"/>
|
<TextBlock Text="Anwesenheitsdaten im Brief" FontWeight="SemiBold"/>
|
||||||
<TextBlock Text="{Binding AttendanceCalendarSummary}" FontSize="12" Opacity="0.65"/>
|
<TextBlock Text="{Binding AttendanceCalendarSummary}" FontSize="12" Opacity="0.65"/>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<Button Grid.Column="1" Content="Konfigurieren …" Click="OnConfigureAttendanceCalendar"
|
<Button Grid.Column="1" Content="Konfigurieren …" Click="OnConfigureAttendanceCalendar"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public partial class CreateLetterDialog : Window
|
|||||||
private async void OnGenerate(object? sender, RoutedEventArgs e)
|
private async void OnGenerate(object? sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
if (DataContext is not CreateLetterDialogViewModel vm) return;
|
if (DataContext is not CreateLetterDialogViewModel vm) return;
|
||||||
if (vm.UsesAttendanceCalendar && !vm.AttendanceCalendarConfigured &&
|
if (vm.UsesAttendanceAdvancedContent && !vm.AttendanceCalendarConfigured &&
|
||||||
!await ConfigureAttendanceCalendar(vm)) return;
|
!await ConfigureAttendanceCalendar(vm)) return;
|
||||||
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
var file = await StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ public partial class DesignerViewModel : ObservableObject
|
|||||||
new("Brieftext", PlaceholderType.Multiline, true, "hiermit informieren wir Sie über einen wichtigen Termin.\n\nMit freundlichen Grüßen"),
|
new("Brieftext", PlaceholderType.Multiline, true, "hiermit informieren wir Sie über einen wichtigen Termin.\n\nMit freundlichen Grüßen"),
|
||||||
new("LehrerName", PlaceholderType.Text, true, "M. Mustermann"),
|
new("LehrerName", PlaceholderType.Text, true, "M. Mustermann"),
|
||||||
new("Student.AttendanceCalendar", PlaceholderType.Drawing, false, ""),
|
new("Student.AttendanceCalendar", PlaceholderType.Drawing, false, ""),
|
||||||
|
new("Student.AbsenceDays", PlaceholderType.Drawing, false, ""),
|
||||||
];
|
];
|
||||||
public ObservableCollection<DesignerMetadata> MetadataItems { get; } =
|
public ObservableCollection<DesignerMetadata> MetadataItems { get; } =
|
||||||
[
|
[
|
||||||
|
|||||||
Reference in New Issue
Block a user