Documents: Add Letterhead

This commit is contained in:
2023-09-28 19:38:29 +02:00
parent d4e5ac6753
commit ca1b68aa4f
6 changed files with 107 additions and 15 deletions

View File

@ -2,11 +2,15 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.IO; using System.IO;
using Elwig.Helpers; using Elwig.Helpers;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Linq;
namespace Elwig.Documents { namespace Elwig.Documents {
public abstract class Document : IDisposable { public abstract partial class Document : IDisposable {
private TempFile? PdfFile = null; private TempFile? _pdfFile = null;
private string? _renderedHtml = null;
public bool ShowFoldMarks = App.Config.Debug; public bool ShowFoldMarks = App.Config.Debug;
@ -35,17 +39,56 @@ namespace Elwig.Documents {
Date = DateTime.Today; Date = DateTime.Today;
} }
[GeneratedRegex(@"</body>.*?</footer>\s*</div>", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled)]
private static partial Regex GeneratedDocumentHeaderRegex();
private static readonly Regex DocumentHeaderRegex = GeneratedDocumentHeaderRegex();
[GeneratedRegex(@"<style>.*?/style>", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled)]
private static partial Regex GeneratedHtmlStyleRegex();
private static readonly Regex HtmlStyleRegex = GeneratedHtmlStyleRegex();
[GeneratedRegex(@"<link[^>]*>", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled)]
private static partial Regex GeneratedHtmlLinkRegex();
private static readonly Regex HtmlLinkRegex = GeneratedHtmlLinkRegex();
~Document() { ~Document() {
Dispose(); Dispose();
} }
public void Dispose() { public void Dispose() {
PdfFile?.Dispose(); _pdfFile?.Dispose();
PdfFile = null; _pdfFile = null;
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private Task<string> Render() { public static async Task<Document> Merge(IEnumerable<Document> docs) {
string html = "";
var styles = new List<string>();
foreach (var d in docs) {
var h = await d.Render();
var s = HtmlStyleRegex.Matches(h).Select(m => m.Value).ToList();
var l = HtmlLinkRegex.Matches(h).Select(m => m.Value).ToList();
if (s.All(styles.Contains)) {
h = HtmlStyleRegex.Replace(h, "");
} else {
styles.AddRange(s);
}
if (l.All(styles.Contains)) {
h = HtmlLinkRegex.Replace(h, "");
} else {
styles.AddRange(l);
}
html += h;
}
html = DocumentHeaderRegex.Replace(html, "<div class='document-break'/>");
return new InternalDocument("Mehrere Dokumente") {
_renderedHtml = html,
};
}
private async Task<string> Render() {
if (_renderedHtml != null)
return _renderedHtml;
string name; string name;
if (this is BusinessLetter) { if (this is BusinessLetter) {
name = "BusinessLetter"; name = "BusinessLetter";
@ -55,14 +98,17 @@ namespace Elwig.Documents {
name = "CreditNote"; name = "CreditNote";
} else if (this is DeliveryJournal) { } else if (this is DeliveryJournal) {
name = "DeliveryJournal"; name = "DeliveryJournal";
} else if (this is Letterhead) {
name = "Letterhead";
} else { } else {
throw new InvalidOperationException("Invalid document object"); throw new InvalidOperationException("Invalid document object");
} }
return Render(name); return await Render(name);
} }
private Task<string> Render(string name) { private async Task<string> Render(string name) {
return Html.CompileRenderAsync(name, this); _renderedHtml = await Html.CompileRenderAsync(name, this);
return _renderedHtml;
} }
public async Task Generate() { public async Task Generate() {
@ -71,22 +117,26 @@ namespace Elwig.Documents {
await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8); await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8);
await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath); await Pdf.Convert(tmpHtml.FilePath, pdf.FilePath);
} }
PdfFile = pdf; _pdfFile = pdf;
} }
public void SaveTo(string pdfPath) { public void SaveTo(string pdfPath) {
if (PdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet"); if (_pdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet");
File.Copy(PdfFile.FilePath, pdfPath); File.Copy(_pdfFile.FilePath, pdfPath);
} }
public async Task Print(int copies = 1) { public async Task Print(int copies = 1) {
if (PdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet"); if (_pdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet");
await Pdf.Print(PdfFile.FilePath, copies); await Pdf.Print(_pdfFile.FilePath, copies);
} }
public void Show() { public void Show() {
if (PdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet"); if (_pdfFile == null) throw new InvalidOperationException("Pdf file has not been generated yet");
Pdf.Show(PdfFile.NewReference(), Title); Pdf.Show(_pdfFile.NewReference(), Title);
}
private class InternalDocument : Document {
public InternalDocument(string title) : base(title) { }
} }
} }
} }

View File

@ -20,6 +20,7 @@ namespace Elwig.Documents {
await e.CompileTemplateAsync("DeliveryNote"); await e.CompileTemplateAsync("DeliveryNote");
await e.CompileTemplateAsync("CreditNote"); await e.CompileTemplateAsync("CreditNote");
await e.CompileTemplateAsync("DeliveryJournal"); await e.CompileTemplateAsync("DeliveryJournal");
await e.CompileTemplateAsync("Letterhead");
Engine = e; Engine = e;
evtHandler(); evtHandler();

View File

@ -0,0 +1,9 @@
@using RazorLight
@inherits TemplatePage<Elwig.Documents.Letterhead>
@model Elwig.Documents.Letterhead
@{ Layout = "BusinessDocument"; }
<style>
header, .footer-wrapper {
visibility: hidden;
}
</style>

View File

@ -0,0 +1,9 @@
using Elwig.Models;
namespace Elwig.Documents {
public class Letterhead : BusinessDocument {
public Letterhead(Member m) : base($"Briefkopf {m.Name}", m, true) {
Aside = "";
}
}
}

View File

@ -20,6 +20,9 @@
hr.page-break { hr.page-break {
display: none; display: none;
} }
.document-break {
break-before: page;
}
@page { @page {
size: A4; size: A4;

View File

@ -10,6 +10,7 @@ using Elwig.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using Elwig.Documents;
namespace Elwig.Windows { namespace Elwig.Windows {
public partial class MemberAdminWindow : AdministrationWindow { public partial class MemberAdminWindow : AdministrationWindow {
@ -269,6 +270,25 @@ namespace Elwig.Windows {
Utils.MailTo(((Member)MemberList.SelectedItem).EmailAddresses.Select(a => a.Address)); Utils.MailTo(((Member)MemberList.SelectedItem).EmailAddresses.Select(a => a.Address));
} }
private async void Menu_Print_Letterheads_MgNr_Click(object sender, RoutedEventArgs evt) {
using var d = await Document.Merge(Context.Members
.Where(m => m.IsActive)
.OrderBy(m => m.MgNr)
.Select(m => new Letterhead(m)));
await d.Generate();
d.Show();
}
private async void Menu_Print_Letterheads_Name_Click(object sender, RoutedEventArgs evt) {
using var d = await Document.Merge(Context.Members
.Where(m => m.IsActive)
.OrderBy(m => m.FamilyName)
.ThenBy(m => m.GivenName)
.Select(m => new Letterhead(m)));
await d.Generate();
d.Show();
}
private void FocusSearchInput(object sender, RoutedEventArgs evt) { private void FocusSearchInput(object sender, RoutedEventArgs evt) {
if (!IsEditing && !IsCreating) { if (!IsEditing && !IsCreating) {
SearchInput.Focus(); SearchInput.Focus();