diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs index 5732d6f..a11be14 100644 --- a/Elwig/App.xaml.cs +++ b/Elwig/App.xaml.cs @@ -32,9 +32,6 @@ namespace Elwig { public static readonly string MailsPath = Path.Combine(DataPath, "mails"); public static readonly string ConfigPath = Path.Combine(DataPath, "config.ini"); public static readonly string InstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Elwig"); - public static readonly string DocumentsPath = (Assembly.GetEntryAssembly()?.Location.Contains(@"\bin\") ?? false) ? - Path.Combine(Assembly.GetEntryAssembly()!.Location.Split(@"\bin\")[0], "../Elwig/Documents") : - Path.Combine(InstallPath, "resources/Documents"); public static readonly string TempPath = Path.Combine(Path.GetTempPath(), "Elwig"); public static Config Config { get; private set; } = new(ConfigPath); @@ -123,7 +120,6 @@ namespace Elwig { return Task.CompletedTask; }); - Utils.RunBackground("HTML Initialization", () => Html.Init()); Utils.RunBackground("PDF Initialization", () => Pdf.Init()); Utils.RunBackground("JSON Schema Initialization", BillingData.Init); diff --git a/Elwig/Documents/BusinessDocument.cs b/Elwig/Documents/BusinessDocument.cs index b7d74e0..ccd2d94 100644 --- a/Elwig/Documents/BusinessDocument.cs +++ b/Elwig/Documents/BusinessDocument.cs @@ -1,34 +1,27 @@ using Elwig.Helpers; using Elwig.Models; using Elwig.Models.Entities; +using iText.Kernel.Colors; +using iText.Kernel.Pdf; +using iText.Kernel.Pdf.Action; +using iText.Kernel.Pdf.Canvas; +using iText.Layout; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; +using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; namespace Elwig.Documents { - public abstract class BusinessDocument : Document { + public class BusinessDocument : Document { public Member Member; public string? Location; public bool IncludeSender = false; public bool UseBillingAddress = false; public bool ShowDateAndLocation = false; - public string Aside; - - public BusinessDocument(string title, Member m, bool includeSender = false) : base(title) { - Member = m; - Location = App.BranchLocation; - IncludeSender = includeSender; - var c = App.Client; - Header = $"
{c.Name}
{c.NameSuffix}
{c.NameTypeFull}
"; - var uid = (m.UstIdNr ?? "-") + (m.IsBuchführend ? "" : " (pauschaliert)"); - Aside = $"" + - $"" + - $"" + - $"" + - $"" + - $"
Mitglied
Mitglieds-Nr.:{m.MgNr}
Betriebs-Nr.:{m.LfbisNr}
UID:{uid}
"; - } + protected Table? Aside; public string Address { get { @@ -38,11 +31,115 @@ namespace Elwig.Documents { } } - private static string GetColGroup(IEnumerable cols) { - return "\n" + string.Join("\n", cols.Select(g => $"")) + "\n\n"; + protected Cell NewAsideCell(Paragraph text, int colspan = 1, bool isName = false) { + var cell = NewCell(text, colspan: colspan).SetPaddingsMM(0.25f, 0.5f, 0.25f, isName ? 1 : 0); + if (colspan > 1) { + cell.SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)) + .SetBorderTop(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness)) + .SetPaddingsMM(0.5f, 1, 0.5f, 1); + } + return cell; } - public static string PrintSortenaufteilung(List stats) { + protected Cell NewAsideCell(string text, int colspan = 1, bool isName = false) { + return NewAsideCell(new KernedParagraph(text, 10), colspan, isName); + } + + public BusinessDocument(string title, Member m, bool includeSender = false) : + base(title) { + Member = m; + Location = App.BranchLocation; + IncludeSender = includeSender; + } + + protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderHeader(doc, pdf); + + var uid = new KernedParagraph(Member.UstIdNr ?? "-", 10); + if (!Member.IsBuchführend) uid.Add(Normal(" ")).Add(Italic("(pauschaliert)")); + Aside = new Table(ColsMM(22.5, 42.5)) + .SetFont(NF).SetFontSize(10) + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetBorder(new SolidBorder(new DeviceRgb(0x80, 0x80, 0x80), BorderThickness)) + .AddCell(NewAsideCell("Mitglied", 2)) + .AddCell(NewAsideCell("Mitglieds-Nr.:", isName: true)).AddCell(NewAsideCell($"{Member.MgNr}")) + .AddCell(NewAsideCell("Betriebs-Nr.:", isName: true)).AddCell(NewAsideCell(Member.LfbisNr ?? "")) + .AddCell(NewAsideCell("UID:", isName: true)).AddCell(NewAsideCell(uid)); + } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + + var page = pdf.AddNewPage(); + var pageSize = page.GetPageSize(); + var pdfCanvas = new PdfCanvas(page.NewContentStreamBefore(), page.GetResources(), pdf); + using (var canvas = new Canvas(pdfCanvas, pageSize)) { + // header + var header = new Div() + .SetFixedPositionMM(25, 10, pageSize.GetWidth() / PtInMM - 45, 45, pageSize) + .SetTextAlignment(TextAlignment.CENTER); + header.Add(new KernedParagraph(App.Client.Name, 18).SetFont(BF).SetMarginsMM(8, 0, 0, 0)); + if (App.Client.NameSuffix != null) header.Add(new KernedParagraph(App.Client.NameSuffix, 14).SetFont(BF).SetMargin(0)); + header.Add(new KernedParagraph(App.Client.NameTypeFull, 12).SetFont(NF).SetMargin(0)); + canvas.Add(header); + // address + canvas.Add(new Div() + .SetFixedPositionMM(25, 50, 80, 45, pageSize) + .SetFont(NF) + .Add(new KernedParagraph(IncludeSender ? $"{App.Client.Sender1}\n{App.Client.Sender2}" : "", 8).SetMargin(0).SetHeight(16).SetPaddings(8, 0, 8, 0)) + .Add(new KernedParagraph(Address, 12).SetMargin(0).SetHeight(12 * 5))); + // aside + if (Aside != null) { + canvas.Add(new Div() + .SetFixedPositionMM(125, 50, 65, 50, pageSize) + .Add(Aside)); + } + } + + doc.Add(new KernedParagraph(ShowDateAndLocation ? $"{Location}, am {Date:dd.MM.yyyy}" : "", 12).SetTextAlignment(TextAlignment.RIGHT).SetVerticalAlignment(VerticalAlignment.MIDDLE).SetHeight(24).SetMargin(0)); + doc.Add(new KernedParagraph(Title, 12).SetFont(BF).SetMargins(0, 0, 12, 0)); + } + + protected override Paragraph GetFooter() { + var c = App.Client; + var link1 = new Link(c.EmailAddress ?? "", PdfAction.CreateURI($"mailto:{Uri.EscapeDataString(c.Name)}%20<{c.EmailAddress}>")); + var link2 = new Link(c.Website ?? "", PdfAction.CreateURI($"http://{c.Website}/")); + link1.GetLinkAnnotation().SetBorder(new PdfAnnotationBorder(0, 0, 0)); + link2.GetLinkAnnotation().SetBorder(new PdfAnnotationBorder(0, 0, 0)); + return new KernedParagraph(10) + .AddAll(Utils.GenerateFooter("\n", " \u00b7 ") + .Item(c.NameFull).NextLine() + .Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine() + .Item(c.EmailAddress != null ? link1 : null) + .Item(c.Website != null ? link2 : null) + .Item("Betriebs-Nr.", c.LfbisNr).Item("Bio-KSt.", c.OrganicAuthority).NextLine() + .Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban) + .ToLeafElements()); + } + + protected Cell NewWeightsHdr(Paragraph p) { + return NewCell(p) + .SetPaddingsMM(0.125f, 0, 0.125f, 0); + } + + protected Cell NewWeightsHdr(string text) { + return NewCell(new KernedParagraph(text, 8).SetFont(IF).SetTextAlignment(TextAlignment.RIGHT)) + .SetPaddingsMM(0.125f, 0, 0.125f, 0); + } + + protected Cell NewWeightsTh(string name) { + return NewCell(new KernedParagraph(name, 10).SetFont(IF)) + .SetPaddingsMM(0.125f, 0, 0.125f, 0); + } + + protected Cell NewWeightsTd(string text, bool sum = false) { + return NewCell(new KernedParagraph(text, 10).SetFont(sum ? BF : NF).SetTextAlignment(TextAlignment.RIGHT)) + .SetPaddingsMM(sum ? 0.25f : 0.125f, 0, sum ? 0.25f : 0.125f, 0) + .SetBorderTop(sum ? new SolidBorder(BorderThickness) : Border.NO_BORDER); + } + + protected Table NewWeightsTable(List stats) { List discrs = [""]; List names = ["ohne Attr./Bewirt."]; List bucketAttrs = [ @@ -58,82 +155,136 @@ namespace Elwig.Documents { List cols = [40]; cols.AddRange(names.Select(_ => 125.0 / names.Count)); - string tbl = GetColGroup(cols); - tbl += "" + - $"Sortenaufteilung [kg]" + - string.Join("", names.Select(c => $"{c}")) + - ""; + var tbl = new Table(ColsMM([.. cols])) + .AddHeaderCell(NewWeightsHdr(new KernedParagraph(8).Add(BoldItalic("Sortenaufteilung ")).Add(Italic("[kg]")))); + foreach (var name in names) + tbl.AddHeaderCell(NewWeightsHdr(name)); + + foreach (var g in stats.GroupBy(b => b.Variety).OrderBy(b => b.Key)) { + var dict = g.ToDictionary(a => a.Discr, a => a.Weight); + var vals = discrs.Select(a => dict.GetValueOrDefault(a, 0)).ToList(); + tbl.AddCell(NewWeightsTh(g.Key)); + foreach (var v in vals) + tbl.AddCell(NewWeightsTd(v == 0 ? "-" : $"{v:N0}")); + tbl.AddCell(NewWeightsTd($"{dict.Values.Sum():N0}")); + } - tbl += string.Join("\n", stats - .GroupBy(b => b.Variety) - .OrderBy(b => b.Key) - .Select(g => { - var dict = g.ToDictionary(a => a.Discr, a => a.Weight); - var vals = discrs.Select(a => dict.GetValueOrDefault(a, 0)).ToList(); - return $"{g.Key}" + string.Join("", vals.Select(v => "" + (v == 0 ? "-" : $"{v:N0}") + "")) + - $"{dict.Values.Sum():N0}"; - }) - ); var totalDict = stats.GroupBy(s => s.Discr).ToDictionary(g => g.Key, g => g.Sum(a => a.Weight)); var totals = discrs.Select(a => totalDict.TryGetValue(a, out int value) ? value : 0); - tbl += "" + string.Join("", totals.Select(v => $"{v:N0}")) + - $"{totalDict.Values.Sum():N0}"; + tbl.AddCell(NewWeightsTd("", true)); + foreach (var v in totals) + tbl.AddCell(NewWeightsTd($"{v:N0}", true)); + tbl.AddCell(NewWeightsTd($"{totalDict.Values.Sum():N0}", true)); - return "" + tbl + "
"; + return tbl; } - private static string FormatRow( - int obligation, int right, int delivery, int? totalDelivery = null, int? payment = null, int? area = null, - bool isGa = false, bool showPayment = false, bool showArea = false + protected Cell NewBucketHdr(Paragraph p, int rowspan = 1, bool left = false, bool unit = false) { + p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true); + p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN); + return NewCell(p, rowspan: rowspan) + .SetPaddingsMM(0.125f, unit ? 2 : 0, 0.125f, 0) + .SetTextAlignment(left ? TextAlignment.LEFT : TextAlignment.RIGHT).SetVerticalAlignment(VerticalAlignment.MIDDLE) + .SetFont(IF); + } + + protected Cell NewBucketHdr(string text, int rowspan = 1, bool left = false, bool unit = false) { + return NewBucketHdr(new KernedParagraph(text, 8), rowspan, left, unit); + } + + protected Cell NewBucketSubHdr(string text, int colspan, bool isTiny = false) { + var p = new KernedParagraph(text, 8); + p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true); + p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN); + return NewCell(p, colspan: colspan) + .SetBorderTop(!isTiny ? new SolidBorder(BorderThickness) : Border.NO_BORDER) + .SetPaddingsMM(isTiny ? 0 : 0.125f, 0, isTiny ? 0 : 0.125f, 0).SetVerticalAlignment(VerticalAlignment.MIDDLE) + .SetTextAlignment(TextAlignment.LEFT).SetFont(BI); + } + + protected Cell NewBucketTh(string text, bool isTiny = false) { + var p = new KernedParagraph(text, isTiny ? 8 : 10); + p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true); + p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN); + return NewCell(p) + .SetPaddingsMM(isTiny ? 0 : 0.125f, 0, isTiny ? 0 : 0.125f, 0) + .SetTextAlignment(TextAlignment.LEFT).SetFont(IF); + } + + protected Cell NewBucketTd(string text, bool bold = false, bool isTiny = false) { + var p = new KernedParagraph(text, isTiny ? 8 : 10); + p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true); + p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN); + return NewCell(p) + .SetPaddingsMM(isTiny ? 0 : 0.125f, 0, isTiny ? 0 : 0.125f, 0) + .SetTextAlignment(TextAlignment.RIGHT).SetFont(bold ? BF : NF); + } + + protected Cell[] FormatRow( + int obligation, int right, int delivery, int? totalDelivery = null, int? payment = null, int? area = null, + bool isGa = false, bool showPayment = false, bool showArea = false, bool isTiny = false ) { totalDelivery ??= delivery; payment ??= delivery; if (showArea) { - return $"{(area == null ? "" : $"{area:N0}")}" + - $"{obligation:N0}" + - $"{right:N0}"; + return [ + NewBucketTd(area == null ? "" : $"{area:N0}", isTiny: isTiny), + NewBucketTd($"{obligation:N0}", isTiny: isTiny), + NewBucketTd($"{right:N0}", isTiny: isTiny), + ]; } - return $"{(obligation == 0 ? "-" : $"{obligation:N0}")}" + - $"{(right == 0 ? "-" : $"{right:N0}")}" + - $"{(totalDelivery < obligation ? $"{obligation - totalDelivery:N0}" : "-")}" + - $"{(delivery <= right ? $"{right - delivery:N0}" : "-")}" + - $"{(obligation == 0 && right == 0 ? "-" : (delivery > right ? ((isGa ? "" : "") + $"{delivery - right:N0}" + (isGa ? "" : "")) : "-"))}" + - (showPayment ? $"{(isGa ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}" : "") + - $"{totalDelivery:N0}"; + return [ + NewBucketTd(obligation == 0 ? "-" : $"{obligation:N0}", isTiny: isTiny), + NewBucketTd(right == 0 ? "-" : $"{right:N0}", isTiny: isTiny), + NewBucketTd(totalDelivery < obligation ? $"{obligation - totalDelivery:N0}" : "-", totalDelivery < obligation, isTiny: isTiny), + NewBucketTd(delivery <= right ? $"{right - delivery:N0}" : "-", isTiny: isTiny), + NewBucketTd(obligation == 0 && right == 0 ? "-" : (delivery > right ? $"{delivery - right:N0}" : "-"), delivery > right && isGa, isTiny: isTiny), + ..(showPayment ? new List() { + NewBucketTd(isGa ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}", isTiny: isTiny) + } : []), + NewBucketTd($"{totalDelivery:N0}", isTiny: isTiny), + ]; } - private static string FormatRow(MemberBucket bucket, bool isGa = false, bool showPayment = false, bool showArea = false) { - return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.DeliveryTotal, bucket.Payment, bucket.Area, isGa, showPayment, showArea); + protected Cell[] FormatRow(MemberBucket bucket, bool isGa = false, bool showPayment = false, bool showArea = false, bool isTiny = false) { + return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.DeliveryTotal, bucket.Payment, bucket.Area, isGa, showPayment, showArea, isTiny); } - public string PrintBucketTable( + protected Table NewBucketTable( Season season, Dictionary buckets, bool includeDelivery = true, bool includePayment = false, bool isTiny = false, IEnumerable? filter = null ) { includePayment = includePayment && includeDelivery; - string tbl = GetColGroup(!includeDelivery ? [105, 20, 20, 20] : includePayment ? [45, 17, 17, 17, 19, 16, 17, 17] : [45, 20, 20, 20, 20, 20, 20]); - tbl += $""" - - - - {(includeDelivery ? "Lese " + season.Year : "Zusammengefasste Flächenbindungen")} - per {Date:dd.MM.yyyy} {(includeDelivery ? "[kg]" : "")} - - {(!includeDelivery ? "Fläche" : "")} - Lieferpflicht - Lieferrecht - {(includeDelivery ? "Unterliefert" : "")} - {(includeDelivery ? "Noch lieferbar" : "")} - {(includeDelivery ? "Überliefert" : "")} - {(includePayment ? "Zugeteilt" : "")} - {(includeDelivery ? "Geliefert" : "")} - - {(!includeDelivery ? "[m²][kg][kg]" : "")} - - """; + + var tbl = new Table(ColsMM(!includeDelivery ? [105, 20, 20, 20] : includePayment ? [45, 17, 17, 17, 19, 16, 17, 17] : [45, 20, 20, 20, 20, 20, 20])) + .SetBorder(Border.NO_BORDER).SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetFont(NF).SetFontSize(isTiny ? 8 : 10); + + if (includeDelivery) { + tbl.AddHeaderCell(NewBucketHdr(new KernedParagraph(8) + .Add(BoldItalic($"Lese {season.Year}")) + .Add(Italic($" per {Date:dd.MM.yyyy} [kg]")), left: true)) + .AddHeaderCell(NewBucketHdr("Lieferpflicht")) + .AddHeaderCell(NewBucketHdr("Lieferrecht")) + .AddHeaderCell(NewBucketHdr("Unterliefert")) + .AddHeaderCell(NewBucketHdr("Noch lieferbar")) + .AddHeaderCell(NewBucketHdr("Überliefert")); + if (includePayment) tbl.AddHeaderCell(NewBucketHdr("Zugeteilt")); + tbl.AddHeaderCell(NewBucketHdr("Geliefert")); + } else { + tbl.AddHeaderCell(NewBucketHdr(new KernedParagraph(8) + .Add(BoldItalic("Zusammengefasste Flächenbindungen")) + .Add(Italic($" per {Date:dd.MM.yyyy}")), 2, left: true)) + .AddHeaderCell(NewBucketHdr("Fläche")) + .AddHeaderCell(NewBucketHdr("Lieferpflicht")) + .AddHeaderCell(NewBucketHdr("Lieferrecht")) + .AddHeaderCell(NewBucketHdr("[m²]", unit: true)) + .AddHeaderCell(NewBucketHdr("[kg]", unit: true)) + .AddHeaderCell(NewBucketHdr("[kg]", unit: true)); + } var mBuckets = buckets .Where(b => ((!includeDelivery && b.Value.Area > 0) || @@ -155,30 +306,26 @@ namespace Elwig.Documents { .Where(b => !fbVars.Contains(b.Key)) .OrderBy(b => b.Value.Name); - tbl += "\n\n"; - tbl += $"Gesamtlieferung lt. gez. GA{FormatRow( - Member.BusinessShares * season.MinKgPerBusinessShare, + tbl.AddCell(NewBucketTh("Gesamtlieferung lt. gez. GA", isTiny: isTiny)); + tbl.AddCells(FormatRow(Member.BusinessShares * season.MinKgPerBusinessShare, Member.BusinessShares * season.MaxKgPerBusinessShare, season.Deliveries.Where(d => d.MgNr == Member.MgNr).Sum(d => d.Weight), - isGa: true, showPayment: includePayment, showArea: !includeDelivery - )}"; + isGa: true, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny)); + if (fbs.Any()) { - tbl += $"" + - $"Flächenbindungen{(vtr.Any() ? " (inkl. Verträge)" : "")}:"; + tbl.AddCell(NewBucketSubHdr("Flächenbindungen" + (vtr.Any() ? " (inkl. Verträge)" : "") + ":", includePayment ? 8 : 7, isTiny: isTiny)); foreach (var (id, b) in fbs) { - tbl += $"{b.Name}{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}"; + tbl.AddCell(NewBucketTh(b.Name, isTiny: isTiny)).AddCells(FormatRow(b, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny)); } } if (vtr.Any()) { - tbl += $"" + - "Verträge:"; + tbl.AddCell(NewBucketSubHdr("Verträge:", includePayment ? 8 : 7, isTiny: isTiny)); foreach (var (id, b) in vtr) { - tbl += $"{b.Name}{FormatRow(b, showPayment: includePayment, showArea: !includeDelivery)}"; + tbl.AddCell(NewBucketTh(b.Name, isTiny: isTiny)).AddCells(FormatRow(b, showPayment: includePayment, showArea: !includeDelivery, isTiny: isTiny)); } } - tbl += "\n\n"; - return $"\n" + tbl + "\n
"; + return tbl; } } } diff --git a/Elwig/Documents/BusinessDocument.cshtml b/Elwig/Documents/BusinessDocument.cshtml deleted file mode 100644 index 9f17087..0000000 --- a/Elwig/Documents/BusinessDocument.cshtml +++ /dev/null @@ -1,21 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.BusinessDocument -@{ Layout = "Document"; } - -
-
-
- @if (Model.IncludeSender) { -
@Elwig.App.Client.Sender1
-
@Elwig.App.Client.Sender2
- } -
-
@Model.Address
-
- - @if (Model.ShowDateAndLocation) { -
@Model.Location, am @($"{Model.Date:dd.MM.yyyy}")
- } -
-@RenderBody() diff --git a/Elwig/Documents/BusinessDocument.css b/Elwig/Documents/BusinessDocument.css deleted file mode 100644 index b235f92..0000000 --- a/Elwig/Documents/BusinessDocument.css +++ /dev/null @@ -1,150 +0,0 @@ - -.address-wrapper, aside { - overflow: hidden; -} - -.spacing { - height: 20mm; -} - -.info-wrapper { - width: 100%; - height: 45mm; - margin: 0 0 2mm 0; - position: relative; -} - -.info-wrapper .date { - text-align: right; - position: absolute; - right: 0; - bottom: -1.5em; -} - -.address-wrapper { - height: 45mm; - width: 85mm; - margin: 0; - padding: 5mm; - position: absolute; - left: -5mm; - top: 0; -} - -.address-wrapper .sender { - height: 4em; - font-size: 8pt; - padding: 1em 0; -} - -address { - height: 5em; - white-space: pre-line; - font-size: 12pt; - font-style: normal; -} - -aside { - height: 40mm; - width: 75mm; - margin: 0; - position: absolute; - left: 100mm; - top: 5mm; -} - -aside table { - border-collapse: collapse; - border: var(--border-thickness) solid #808080; - width: 65mm; - margin-right: 10mm; -} - -aside table thead:not(:first-child) tr { - border-top: var(--border-thickness) solid #808080; -} - -aside table thead tr { - background-color: #E0E0E0; - font-size: 10pt; -} - -aside table tbody th, -aside table tbody td { - text-align: left; - font-size: 10pt; -} - -aside table tbody th { - padding: 0.25mm 0.5mm 0.25mm 1mm; -} - -aside table tbody td { - padding: 0.25mm 0; -} - -aside table tbody th { - font-weight: normal; -} - -main { - margin: 2em 0 1em 0; -} - -main > *:first-child { - margin-top: 0; -} - -main h1, -main h2, -main h3, -.main-wrapper p { - font-size: 12pt; - margin: 1em 0; - text-align: justify; -} - -.main-wrapper p { - widows: 3; - orphans: 3; - hyphens: manual; -} - -main h1 { - margin-top: 0; - margin-bottom: 2em; -} - -.main-wrapper p.comment { - font-size: 10pt; -} - -.main-wrapper p.custom { - white-space: pre-wrap; - break-inside: avoid; -} - -.main-wrapper .hidden { - break-before: avoid; -} - -.main-wrapper .bottom { - bottom: 0; - position: absolute; - width: 165mm; -} - -.main-wrapper .signatures { - width: 100%; - display: flex; - justify-content: space-around; - margin: 20mm 0 2mm 0; -} - -.main-wrapper .signatures > * { - width: 50mm; - border-top: var(--border-thickness) solid black; - padding-top: 1mm; - text-align: center; - font-size: 10pt; -} diff --git a/Elwig/Documents/BusinessLetter.cs b/Elwig/Documents/BusinessLetter.cs deleted file mode 100644 index e1e1ba0..0000000 --- a/Elwig/Documents/BusinessLetter.cs +++ /dev/null @@ -1,7 +0,0 @@ -using Elwig.Models.Entities; - -namespace Elwig.Documents { - public class BusinessLetter : BusinessDocument { - public BusinessLetter(string title, Member m) : base(title, m) { } - } -} diff --git a/Elwig/Documents/BusinessLetter.cshtml b/Elwig/Documents/BusinessLetter.cshtml deleted file mode 100644 index e6c9c67..0000000 --- a/Elwig/Documents/BusinessLetter.cshtml +++ /dev/null @@ -1,9 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.BusinessLetter -@{ Layout = "BusinessDocument"; } -
-

Sehr geehrtes Mitglied,

-

nein.

-

Mit freundlichen Grüßen
Ihre Winzergenossenschaft

-
diff --git a/Elwig/Documents/CreditNote.cs b/Elwig/Documents/CreditNote.cs index 7893fcf..2e71831 100644 --- a/Elwig/Documents/CreditNote.cs +++ b/Elwig/Documents/CreditNote.cs @@ -1,6 +1,10 @@ using Elwig.Helpers; using Elwig.Models.Dtos; using Elwig.Models.Entities; +using iText.Kernel.Pdf; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; using System; using System.Collections.Generic; using System.Linq; @@ -17,7 +21,7 @@ namespace Elwig.Documents { public string CurrencySymbol; public int Precision; public string MemberModifier; - public IEnumerable<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries; + public List<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries; public decimal MemberTotalUnderDelivery; public int MemberAutoBusinessShares; public decimal MemberAutoBusinessSharesAmount; @@ -53,12 +57,6 @@ namespace Elwig.Documents { } else { MemberModifier = "Sonstige Zu-/Abschläge"; } - Aside = Aside.Replace("", "") + - $"Gutschrift" + - $"TG-Nr.:{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : "-")}" + - $"Datum:{p.Variant.Date:dd.MM.yyyy}" + - $"Überw. am:{p.Variant.TransferDate:dd.MM.yyyy}" + - $""; Text = App.Client.TextCreditNote; DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr); CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code; @@ -96,4 +94,222 @@ namespace Elwig.Documents { .ToList(); } } - }} + + protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderHeader(doc, pdf); + Aside?.AddCell(NewAsideCell("Gutschrift", 2)) + .AddCell(NewAsideCell("TG-Nr.:", isName: true)).AddCell(NewAsideCell(Payment?.Credit != null ? $"{Payment.Credit.Year}/{Payment.Credit.TgNr:000}" : "-")) + .AddCell(NewAsideCell("Datum:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.Date:dd.MM.yyyy}")) + .AddCell(NewAsideCell("Überw. am:", isName: true)).AddCell(NewAsideCell($"{Payment?.Variant.TransferDate:dd.MM.yyyy}")); + } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(NewCreditTable(Data)); + + var div = new Table(ColsMM(60, 105)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + var hint = new KernedParagraph(8) + .Add(Italic("Hinweis:\n" + + $"Die Summe der Lieferungen und die Summe der anfal\u00adlenden Pönalen werden mit " + + $"{Payment?.Variant.Season.Precision} Nach\u00adkomma-stellen berechnent, " + + $"erst das Ergebnis wird kauf-männisch auf 2 Nach\u00adkomma\u00adstellen gerundet.")) + .SetWidth(56 * PtInMM).SetMarginsMM(4, 2, 4, 2); + div.AddCell(new Cell(1, 2).SetPadding(0).SetBorder(Border.NO_BORDER).SetBorderTop(new SolidBorder(BorderThickness))); + div.AddCell(new Cell(3, 1).SetPadding(0).SetBorder(Border.NO_BORDER).Add(hint).SetKeepTogether(true)); + var tbl1 = new Table(ColsMM(70, 5, 5, 25)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetKeepTogether(true); + var tbl2 = new Table(ColsMM(70, 5, 5, 25)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetKeepTogether(true); + var tbl3 = new Table(ColsMM(70, 5, 5, 25)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetKeepTogether(true); + + var sum = Data.Rows.Sum(p => p.Amount); + if (Payment == null) { + tbl1.AddCells(FormatRow("Gesamt", sum, bold: true, noTopBorder: true)); + } else { + var noBorder = true; + if (Payment.NetAmount != Payment.Amount) { + tbl1.AddCells(FormatRow("Zwischensumme", Payment.NetAmount, noTopBorder: noBorder)); + noBorder = false; + tbl1.AddCells(FormatRow(MemberModifier, Payment.Amount - Payment.NetAmount, add: true)); + } + if (Credit == null) { + tbl1.AddCells(FormatRow("Gesamtbetrag", Payment.Amount, bold: true, noTopBorder: noBorder)); + // TODO Mock VAT + } else { + var hasPrev = Credit.PrevNetAmount != null; + tbl1.AddCells(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Credit.NetAmount, bold: true, noTopBorder: noBorder)); + if (hasPrev) { + tbl1.AddCells(FormatRow("Bisher berücksichtigt", -Credit.PrevNetAmount, add: true)); + tbl1.AddCells(FormatRow("Nettobetrag", Credit.NetAmount - (Credit.PrevNetAmount ?? 0))); + } + tbl1.AddCells(FormatRow($"Mehrwertsteuer ({Credit.Vat * 100} %)", Credit.VatAmount, add: true)); + tbl1.AddCells(FormatRow("Bruttobetrag", Credit.GrossAmount, bold: true)); + } + } + + decimal penalty = 0; + string? comment = null; + + if (MemberUnderDeliveries != null && MemberUnderDeliveries.Count > 0) { + tbl2.AddCell(NewTd("Anfallende Pönalen durch Unterlieferungen:", colspan: 2).SetPaddingTopMM(5)) + .AddCell(NewCell(colspan: 2)); + foreach (var u in MemberUnderDeliveries) { + tbl2.AddCells(FormatRow($"{u.Name} ({u.Kg:N0} kg)", u.Amount, add: true, subCat: true)); + penalty += u.Amount; + } + penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero); + } + if (MemberTotalUnderDelivery != 0) { + tbl2.AddCells(FormatRow("Unterlieferung (GA)", MemberTotalUnderDelivery, add: true)); + penalty += MemberTotalUnderDelivery; + } + if (MemberAutoBusinessSharesAmount != 0) { + tbl2.AddCells(FormatRow($"Autom. Nachz. von GA ({MemberAutoBusinessShares})", MemberAutoBusinessSharesAmount, add: true)); + penalty += MemberAutoBusinessSharesAmount; + } + if (CustomPayment?.Amount != null) { + comment = CustomPayment.Comment; + string text = (CustomPayment.Amount.Value < 0 ? "Weitere Abzüge" : "Weitere Zuschläge") + (comment != null ? "*" : ""); + if (comment != null && comment!.Length <= 30) { + text = comment; + comment = null; + } + tbl2.AddCells(FormatRow(text, CustomPayment.Amount.Value, add: true)); + penalty += CustomPayment.Amount.Value; + } + + if (Credit == null) { + tbl3.AddCells(FormatRow("Auszahlungsbetrag", (Payment?.Amount + penalty) ?? (sum + penalty), bold: true)); + } else { + var diff = Credit.Modifiers - penalty; + if (diff != 0) { + tbl3.AddCells(FormatRow(diff < 0 ? "Sonstige Abzüge" : "Sonstige Zuschläge", diff, add: true)); + } + if (Credit.PrevModifiers != null && Credit.PrevModifiers != 0) { + tbl3.AddCells(FormatRow("Bereits berücksichtigte Abzüge", -Credit.PrevModifiers, add: true)); + } + tbl3.AddCells(FormatRow("Auszahlungsbetrag", Credit.Amount, bold: true)); + } + + div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl1)); + div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl2)); + div.AddCell(new Cell().SetPadding(0).SetBorder(Border.NO_BORDER).Add(tbl3)); + doc.Add(div); + + if (comment != null) { + doc.Add(new KernedParagraph($"*{comment}", 12).SetMarginTopMM(10)); + } + doc.Add(new KernedParagraph($"Überweisung erfolgt auf Konto {Utils.FormatIban(Member.Iban ?? "-")}.", 12).SetPaddingTopMM(10)); + if (Text != null) { + doc.Add(new KernedParagraph(Text, 12).SetMarginTop(12).SetKeepTogether(true)); + } + } + + protected Cell[] FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) { + float textSize = subCat ? 8 : !add ? 12 : 10; + float numSize = subCat ? 8 : 12; + var border = !add && !noTopBorder; + var pad = !add && !noTopBorder ? 1 : 0.5f; + return [ + NewTd($"{name}:", textSize, bold: bold, borderTop: border).SetPaddingTopMM(pad), + NewTd(value < 0 ? "–" : (add ? "+" : ""), numSize, right: true, bold: bold, borderTop: border).SetPaddingTopMM(pad), + NewTd(CurrencySymbol, numSize, bold: bold, borderTop: border).SetPaddingTopMM(pad), + NewTd($"{Math.Abs(value ?? 0):N2}", numSize, right: true, bold: bold, borderTop: border).SetPaddingTopMM(pad), + ]; + } + + protected Table NewCreditTable(CreditNoteDeliveryData data) { + var tbl = new Table(ColsMM(25, 6, 36, 10, 10, 15, 12, 13, 5, 17, 16), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Pos.", rowspan: 2).SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh("Sorte/Attribut/Bewirtschaftg.\nZu-/Abschlag", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Gradation", colspan: 2)) + .AddHeaderCell(NewTh("Flächenbindung", colspan: 2)) + .AddHeaderCell(NewTh("Preis")) + .AddHeaderCell(NewTh("Rbl.").SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh("Zu-/Abschläge").SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh("Betrag")) + .AddHeaderCell(NewTh("[°Oe]")) + .AddHeaderCell(NewTh("[°KMW]").SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh("[kg]", colspan: 2)) + .AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]")) + .AddHeaderCell(NewTh("[%]").SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh($"[{CurrencySymbol}]")) + .AddHeaderCell(NewTh($"[{CurrencySymbol}]")); + + foreach (var p in data.Rows) { + var sub = new Table(ColsMM(25, 6, 36, 10, 10, 15, 12, 13, 5, 17, 16)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetKeepTogether(true); + var attr = p.Attribute != null || p.Cultivation != null || p.QualId == "WEI"; + var rows = Math.Max(p.Buckets.Length, 1 + (attr ? 1 : 0) + p.Modifiers.Length); + for (int i = 0; i < rows; i++) { + if (i == 0) { + sub.AddCell(NewTd(p.LsNr)) + .AddCell(NewTd($"{p.DPNr:N0}", center: true).SetPaddingLeft(0).SetPaddingRight(0)) + .AddCell(NewTd(p.Variety)) + .AddCell(NewTd($"{p.Gradation.Oe:N0}", center: true)) + .AddCell(NewTd($"{p.Gradation.Kmw:N1}", center: true)); + } else if (i == 1 && attr) { + var varibute = new KernedParagraph(8); + if (p.Attribute != null) varibute.Add(Normal(p.Attribute)); + if (p.Attribute != null && p.Cultivation != null) varibute.Add(Normal(" / ")); + if (p.Cultivation != null) varibute.Add(Normal(p.Cultivation)); + if ((p.Attribute != null || p.Cultivation != null) && p.QualId == "WEI") varibute.Add(Normal(" / ")); + if (p.QualId == "WEI") varibute.Add(Italic("abgew.")); + sub.AddCell(NewCell(colspan: 2)) + .AddCell(NewTd(varibute, colspan: 3).SetPaddingTop(0)); + } else if (i - (rows - p.Modifiers.Length) < p.Modifiers.Length) { + sub.AddCell(NewCell(colspan: 2)) + .AddCell(NewTd(p.Modifiers[i - (rows - p.Modifiers.Length)], 8, colspan: 3).SetPaddingTop(0).SetPaddingLeftMM(5)); + } else { + sub.AddCell(NewCell(colspan: 5)); + } + + if (i < p.Buckets.Length) { + var bucket = p.Buckets[i]; + var pad = i == 0 ? 0.5f : 0; + sub.AddCell(NewTd($"{bucket.Name}:", 8) + .SetPaddingTopMM(pad)) + .AddCell(NewTd($"{bucket.Value:N0}", right: true) + .SetPaddingTopMM(pad)) + .AddCell(NewTd($"{bucket.Price:N4}", right: true) + .SetPaddingTopMM(pad)); + } else { + sub.AddCell(NewCell(colspan: 3)); + } + + if (i == p.Buckets.Length - 1) { + var rebelMod = p.WeighingModifier * 100; + var totalMod = p.TotalModifiers ?? 0; + var pad = i == 0 ? 0.5f : 0; + sub.AddCell(NewTd(rebelMod == 0 ? "-" : (Utils.GetSign(rebelMod) + $"{Math.Abs(rebelMod):0.0##}"), 6, center: true) + .SetPaddingTopMM(pad)) + .AddCell(NewTd(totalMod == 0 ? "-" : Utils.GetSign(totalMod) + $"{Math.Abs(totalMod):N2}", center: totalMod == 0, right: true) + .SetPaddingTopMM(pad)) + .AddCell(NewTd($"{p.Amount:N2}", right: true) + .SetPaddingTopMM(pad)); + } else { + sub.AddCell(NewCell(colspan: 3)); + } + } + tbl.AddCell(new Cell(1, 12).SetPadding(0).SetBorder(Border.NO_BORDER).Add(sub)); + } + + return tbl; + } + } +} diff --git a/Elwig/Documents/CreditNote.cshtml b/Elwig/Documents/CreditNote.cshtml deleted file mode 100644 index 6d43827..0000000 --- a/Elwig/Documents/CreditNote.cshtml +++ /dev/null @@ -1,197 +0,0 @@ -@using Elwig.Helpers -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.CreditNote -@{ Layout = "BusinessDocument"; } - -
-

@Model.Title

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @foreach (var p in Model.Data.Rows) { - var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1); - @for (int i = 0; i < rows; i++) { - - @if (i == 0) { - - - - - - - } - @if (i > 0 && i <= p.Modifiers.Length) { - - } else if (i > 0) { - - } - @if (i < p.Buckets.Length) { - var bucket = p.Buckets[i]; - - - - } else { - - } - @if (i == p.Buckets.Length - 1) { - var rebelMod = p.WeighingModifier * 100; - var totalMod = p.TotalModifiers ?? 0; - - - - } else { - - } - - } - } - -
Lieferschein-Nr.Pos.SorteAttr./Bewirt.GradationFlächenbindungPreisRbl.Zu-/AbschlägeBetrag
[°Oe][°KMW][kg][@Model.CurrencySymbol/kg][%][@Model.CurrencySymbol][@Model.CurrencySymbol]
@p.LsNr@p.DPNr@p.Variety - @p.Attribute@(p.Attribute != null && p.Cultivation != null ? " / " : "")@p.Cultivation - @((p.Attribute != null || p.Cultivation != null) && p.QualId == "WEI" ? " / " : "")@Raw(p.QualId == "WEI" ? "abgew." : "") - @($"{p.Gradation.Oe:N0}")@($"{p.Gradation.Kmw:N1}")@p.Modifiers[i - 1]@bucket.Name:@($"{bucket.Value:N0}")@($"{bucket.Price:N4}")@(rebelMod == 0 ? "-" : (Utils.GetSign(rebelMod) + $"{Math.Abs(rebelMod):0.0##}"))@(totalMod == 0 ? "-" : Utils.GetSign(totalMod) + $"{Math.Abs(totalMod):N2}")@($"{p.Amount:N2}")
-
- Hinweis:
- Die Summe der Lieferungen und die Summe der anfal­lenden Pönalen werden mit - @Model.Payment?.Variant.Season.Precision Nach­komma­stellen berechnent, - erst das Ergebnis wird kauf­männisch auf 2 Nach­komma­stellen gerundet. -
- - - - - - - @{ - string FormatRow(string name, decimal? value, bool add = false, bool bold = false, bool subCat = false, bool noTopBorder = false) { - return $"" - + $"" - + $"" - + $"" - + $"\n"; - } - } - - @{ var sum = Model.Data.Rows.Sum(p => p.Amount); } - @if (Model.Payment == null) { - @Raw(FormatRow("Gesamt", sum, bold: true, noTopBorder: true)) - } else { - var noBorder = true; - if (Model.Payment.NetAmount != Model.Payment.Amount) { - @Raw(FormatRow("Zwischensumme", Model.Payment.NetAmount, noTopBorder: noBorder)) - noBorder = false; - @Raw(FormatRow(Model.MemberModifier, Model.Payment.Amount - Model.Payment.NetAmount, add: true)) - } - if (Model.Credit == null) { - @Raw(FormatRow("Gesamtbetrag", Model.Payment.Amount, bold: true, noTopBorder: noBorder)) - // TODO Mock VAT - } else { - var hasPrev = Model.Credit.PrevNetAmount != null; - @Raw(FormatRow(hasPrev ? "Gesamtbetrag" : "Nettobetrag", Model.Credit.NetAmount, bold: true, noTopBorder: noBorder)) - if (hasPrev) { - @Raw(FormatRow("Bisher berücksichtigt", -Model.Credit.PrevNetAmount, add: true)) - @Raw(FormatRow("Nettobetrag", Model.Credit.NetAmount - (Model.Credit.PrevNetAmount ?? 0))) - } - @Raw(FormatRow($"Mehrwertsteuer ({Model.Credit.Vat * 100} %)", Model.Credit.VatAmount, add: true)) - @Raw(FormatRow("Bruttobetrag", Model.Credit.GrossAmount, bold: true)) - } - } - - - @{ - decimal penalty = 0; - string? comment = null; - } - - @if (Model.MemberUnderDeliveries != null && Model.MemberUnderDeliveries.Count() > 0) { - - - - - foreach (var u in Model.MemberUnderDeliveries) { - @Raw(FormatRow($"{u.Name} ({u.Kg:N0} kg)", u.Amount, add: true, subCat: true)) - penalty += u.Amount; - } - penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero); - } - @if (Model.MemberTotalUnderDelivery != 0) { - @Raw(FormatRow("Unterlieferung (GA)", Model.MemberTotalUnderDelivery, add: true)); - penalty += Model.MemberTotalUnderDelivery; - } - @if (Model.MemberAutoBusinessSharesAmount != 0) { - @Raw(FormatRow($"Autom. Nachz. von GA ({Model.MemberAutoBusinessShares})", Model.MemberAutoBusinessSharesAmount, add: true)); - penalty += Model.MemberAutoBusinessSharesAmount; - } - @if (Model.CustomPayment?.Amount != null) { - comment = Model.CustomPayment.Comment; - string text = (Model.CustomPayment.Amount.Value < 0 ? "Weitere Abzüge" : "Weitere Zuschläge") + (comment != null ? "*" : ""); - if (comment != null && comment!.Length <= 30) { - text = comment; - comment = null; - } - @Raw(FormatRow(text, Model.CustomPayment.Amount.Value, add: true)); - penalty += Model.CustomPayment.Amount.Value; - } - - @if (Model.Credit == null) { - @Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true)) - } else { - var diff = Model.Credit.Modifiers - penalty; - if (diff != 0) { - @Raw(FormatRow(diff < 0 ? "Sonstige Abzüge" : "Sonstige Zuschläge", diff, add: true)) - } - if (Model.Credit.PrevModifiers != null && Model.Credit.PrevModifiers != 0) { - @Raw(FormatRow("Bereits berücksichtigte Abzüge", -Model.Credit.PrevModifiers, add: true)) - } - @Raw(FormatRow("Auszahlungsbetrag", Model.Credit.Amount, bold: true)) - } - -
{name}:{(value < 0 ? "–" : (add ? "+" : ""))}" - + $"{Model.CurrencySymbol}{Math.Abs(value ?? 0):N2}
Anfallende Pönalen durch Unterlieferungen:
- @if (comment != null) { -

* @comment

- } -

Überweisung erfolgt auf Konto @(Elwig.Helpers.Utils.FormatIban(Model.Member.Iban ?? "-")).

-
- @if (Model.Text != null) { -

@Model.Text

- } -
-
diff --git a/Elwig/Documents/CreditNote.css b/Elwig/Documents/CreditNote.css deleted file mode 100644 index fd9e79c..0000000 --- a/Elwig/Documents/CreditNote.css +++ /dev/null @@ -1,48 +0,0 @@ - -table.credit { - margin-bottom: 0; -} - -table.credit .mod { - padding-left: 5mm; -} - -table.credit tbody tr:not(.first) { - break-before: avoid; -} - -table.credit tbody tr:not(.last) { - break-after: avoid; -} - -table.credit tr:not(.first) td { - padding-top: 0; -} - -table.credit tr.last td { - padding-bottom: 0; -} - -table.credit-sum { - width: 60%; - margin-left: 40%; -} - -table.credit-sum tr.sum, -table.credit-sum tr .sum { - font-size: 12pt; -} - -table.credit-sum tr.sum td, -table.credit-sum td.sum { - padding-top: 1mm !important; -} - -.hint { - font-style: italic; - font-size: 8pt; - width: 56mm; - position: absolute; - left: 0; - margin: 2mm 4mm; -} diff --git a/Elwig/Documents/DeliveryAncmtList.cs b/Elwig/Documents/DeliveryAncmtList.cs index d99ed1f..32f8977 100644 --- a/Elwig/Documents/DeliveryAncmtList.cs +++ b/Elwig/Documents/DeliveryAncmtList.cs @@ -1,5 +1,9 @@ using Elwig.Models.Dtos; +using iText.Kernel.Pdf; +using iText.Layout.Element; +using iText.Layout.Properties; using System.Collections.Generic; +using System.Linq; namespace Elwig.Documents { public class DeliveryAncmtList : Document { @@ -7,15 +11,58 @@ namespace Elwig.Documents { public new static string Name => "Anmeldeliste"; public string Filter; - public IEnumerable Announcements; + public List Announcements; - public DeliveryAncmtList(string filter, IEnumerable announcements) : base($"{Name} {filter}") { + public DeliveryAncmtList(string filter, IEnumerable announcements) : + base($"{Name} {filter}") { Filter = filter; - Announcements = announcements; + Announcements = [.. announcements]; } public DeliveryAncmtList(string filter, DeliveryAncmtListData data) : this(filter, data.Rows) { } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(new KernedParagraph(Name, 24) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 2, 0)); + doc.Add(new KernedParagraph(Filter, 14) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 2, 0)); + doc.Add(NewAncmtTable(Announcements)); + } + + protected Table NewAncmtTable(List ancmts) { + var tbl = new Table(ColsMM(15, 12, 50, 25, 38, 11, 14), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + tbl.AddHeaderCell(NewTh("Datum", rowspan: 2)) + .AddHeaderCell(NewTh("MgNr.", rowspan: 2)) + .AddHeaderCell(NewTh("Mitglied", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Ort", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Sorte", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Anmldg.", rowspan: 2)) + .AddHeaderCell(NewTh("Menge")) + .AddHeaderCell(NewTh("[kg]")); + + foreach (var a in ancmts) { + tbl.AddCell(NewTd($"{a.Date:dd.MM.yyyy}", 8)) + .AddCell(NewTd($"{a.MgNr}", right: true)) + .AddCell(NewTd(a.AdministrativeName)) + .AddCell(NewTd(a.DefaultKg, 8)) + .AddCell(NewTd(a.Variety)) + .AddCell(NewTd(a.Status ?? "-", 8, center: true)) + .AddCell(NewTd($"{a.Weight:N0}", right: true)); + } + + tbl.AddCell(NewTd("Gesamt:", colspan: 2, bold: true, borderTop: true)) + .AddCell(NewTd($"Anmeldungen: {ancmts.Count:N0}", colspan: 3, bold: true, borderTop: true)) + .AddCell(NewTd($"{ancmts.Sum(a => a.Weight):N0}", colspan: 2, right: true, bold: true, borderTop: true)); + + return tbl; + } } } diff --git a/Elwig/Documents/DeliveryAncmtList.cshtml b/Elwig/Documents/DeliveryAncmtList.cshtml deleted file mode 100644 index b53272c..0000000 --- a/Elwig/Documents/DeliveryAncmtList.cshtml +++ /dev/null @@ -1,52 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.DeliveryAncmtList -@{ Layout = "Document"; } - -
-

Anmeldeliste

-

@Model.Filter

- - - - - - - - - - - - - - - - - - - - - - - - - - @foreach (var a in Model.Announcements) { - - - - - - - - - - } - - - - - - -
DatumMgNr.MitgliedOrtSorteAnmldg.Menge
[kg]
@($"{a.Date:dd.MM.yyyy}")@a.MgNr@a.AdministrativeName@a.DefaultKg@a.Variety@(a.Status ?? "-")@($"{a.Weight:N0}")
Gesamt:Anmeldungen: @($"{Model.Announcements.Count():N0}")@($"{Model.Announcements.Sum(a => a.Weight):N0}")
-
diff --git a/Elwig/Documents/DeliveryAncmtList.css b/Elwig/Documents/DeliveryAncmtList.css deleted file mode 100644 index aea8e98..0000000 --- a/Elwig/Documents/DeliveryAncmtList.css +++ /dev/null @@ -1,13 +0,0 @@ - -h1 { - text-align: center; - font-size: 24pt; - margin-top: 0; - margin-bottom: 2mm; -} - -h2 { - text-align: center; - font-size: 14pt; - margin-top: 2mm; -} diff --git a/Elwig/Documents/DeliveryConfirmation.cs b/Elwig/Documents/DeliveryConfirmation.cs index 131db31..81e5cae 100644 --- a/Elwig/Documents/DeliveryConfirmation.cs +++ b/Elwig/Documents/DeliveryConfirmation.cs @@ -1,8 +1,13 @@ using Elwig.Helpers; using Elwig.Models.Dtos; using Elwig.Models.Entities; +using iText.Kernel.Pdf; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; using System; using System.Collections.Generic; +using System.Linq; namespace Elwig.Documents { public class DeliveryConfirmation : BusinessDocument { @@ -25,5 +30,124 @@ namespace Elwig.Documents { MemberBuckets = ctx.GetMemberBuckets(Season.Year, m.MgNr).GetAwaiter().GetResult(); MemberStats = AppDbContext.GetMemberStats(Season.Year, m.MgNr).GetAwaiter().GetResult(); } + + protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderHeader(doc, pdf); + var firstDay = Data.Rows.MinBy(r => r.Date)?.Date; + var lastDay = Data.Rows.MaxBy(r => r.Date)?.Date; + Aside?.AddCell(NewAsideCell("Saison", 2)) + .AddCell(NewAsideCell("Lieferungen:", isName: true)).AddCell(NewAsideCell($"{Data.Rows.DistinctBy(r => r.LsNr).Count():N0} (Teil-Lfrg.: {Data.RowNum:N0})")) + .AddCell(NewAsideCell("Zeitraum:", isName: true)).AddCell(NewAsideCell(firstDay == null || lastDay == null ? "-" : firstDay == lastDay ? $"{firstDay:dd.MM.} (1 Tag)" : $"{firstDay:dd.MM.}\u2013{lastDay:dd.MM.} ({lastDay?.DayNumber - firstDay?.DayNumber + 1:N0} Tage)")) + .AddCell(NewAsideCell("Menge:", isName: true)).AddCell(NewAsideCell($"{MemberStats.Sum(s => s.Weight):N0} kg")); + } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + + doc.Add(NewDeliveryListTable(Data)); + doc.Add(NewWeightsTable(MemberStats) + .SetMarginTopMM(10).SetKeepTogether(true)); + doc.Add(NewBucketTable(Season, MemberBuckets, includePayment: true) + .SetMarginTopMM(10).SetKeepTogether(true)); + + if (Text != null) { + doc.Add(new KernedParagraph(Text, 10) + .SetMarginTop(20).SetKeepTogether(true)); + } + } + + protected Table NewDeliveryListTable(DeliveryConfirmationDeliveryData data) { + var tbl = new Table(ColsMM(25, 7, 39, 14, 11, 11, 15, 12, 14, 3, 14), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Pos.", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Sorte/Attribut/Bewirtschaftung\nZu-/Abschlag", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Qual.", rowspan: 2)) + .AddHeaderCell(NewTh("Gradation", colspan: 2)) + .AddHeaderCell(NewTh("Flächenbindung", colspan: 2)) + .AddHeaderCell(NewTh("Menge")) + .AddHeaderCell(NewTh("gerebelt", rowspan: 3, rotated: true)) + .AddHeaderCell(NewTh("Davon\nabzuwerten")) + .AddHeaderCell(NewTh("[°Oe]")) + .AddHeaderCell(NewTh("[°KMW]")) + .AddHeaderCell(NewTh("[kg]", colspan: 2)) + .AddHeaderCell(NewTh("[kg]")) + .AddHeaderCell(NewTh("[kg]")); + + var lastVariety = ""; + foreach (var p in data.Rows) { + var sub = new Table(ColsMM(25, 7, 39, 14, 11, 11, 15, 12, 14, 3, 14)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetKeepTogether(true); + if (lastVariety != "" && lastVariety != p.Variety) { + sub.SetBorderTop(new SolidBorder(BorderThickness)); + } + var attr = p.Attribute != null || p.Cultivation != null; + var rows = Math.Max(p.Buckets.Length, 1 + (attr ? 1 : 0) + p.Modifiers.Length); + for (int i = 0; i < rows; i++) { + if (i == 0) { + sub.AddCell(NewTd(p.LsNr)) + .AddCell(NewTd($"{p.DPNr:N0}", center: true)) + .AddCell(NewTd(p.Variety)) + .AddCell(NewTd(p.QualId, center: true)) + .AddCell(NewTd($"{p.Gradation.Oe:N0}", center: true)) + .AddCell(NewTd($"{p.Gradation.Kmw:N1}", center: true)); + } else if (i == 1 && attr) { + sub.AddCell(NewCell(colspan: 2)) + .AddCell(NewTd($"{p.Attribute}{(p.Attribute != null && p.Cultivation != null ? " / " : "")}{p.Cultivation}", 8, colspan: 2) + .SetPaddingsMM(0.125f, 1, 0.125f, 1)) + .AddCell(NewCell(colspan: 2)); + } else { + sub.AddCell(NewCell(colspan: 2)); + if (i - (rows - p.Modifiers.Length) < p.Modifiers.Length) { + sub.AddCell(NewTd(p.Modifiers[i - (rows - p.Modifiers.Length)], 8, colspan: 2) + .SetPaddingsMM(0.125f, 0, 0.125f, 5)); + } else { + sub.AddCell(NewCell(colspan: 2)); + } + sub.AddCell(NewCell(colspan: 2)); + } + + if (i < p.Buckets.Length) { + var bucket = p.Buckets[i]; + sub.AddCell(NewTd($"{bucket.Name}:", 8).SetHeight(10).SetPaddingsMM(0.125f, 0, 0.125f, 0)); + sub.AddCell(NewTd($"{bucket.Value:N0}", right: true)); + } else { + sub.AddCell(NewCell(colspan: 2)); + } + + if (i == p.Buckets.Length - 1) { + sub.AddCell(NewTd($"{p.Weight:N0}")); + sub.AddCell(NewTd(p.IsNetWeight ? "\u2611" : "\u2610", 7, center: true).SetFont(SF).SetPadding(0)); + } else { + sub.AddCell(NewCell(colspan: 2)); + } + + if (i == 0) { + sub.AddCell(NewCell(rowspan: rows)); + } + lastVariety = p.Variety; + } + tbl.AddCell(new Cell(1, 11).SetPadding(0).SetBorder(Border.NO_BORDER).Add(sub)); + } + + tbl.AddCell(NewTd("Gesamt:", 12, colspan: 7, bold: true, borderTop: true) + .SetPaddingsMM(1, 1, 0, 1)) + .AddCell(NewTd($"{data.Rows.Sum(p => p.Weight):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true) + .SetPaddingsMM(1, 1, 0, 1)) + .AddCell(NewTd(colspan: 2, borderTop: true) + .SetPaddingsMM(1, 1, 0, 1)) + .AddCell(NewTd("Davon abgewertet:", 10, colspan: 7) + .SetPaddingsMM(0, 1, 0.5f, 1)) + .AddCell(NewTd($"{data.Rows.Where(p => p.IsDepreciated).Sum(p => p.Weight):N0}", 10, colspan: 2, right: true) + .SetPaddingsMM(0, 1, 0.5f, 1)) + .AddCell(NewCell(colspan: 2) + .SetPaddingsMM(0, 1, 0.5f, 1)); + + return tbl; + } } } diff --git a/Elwig/Documents/DeliveryConfirmation.cshtml b/Elwig/Documents/DeliveryConfirmation.cshtml deleted file mode 100644 index f32a170..0000000 --- a/Elwig/Documents/DeliveryConfirmation.cshtml +++ /dev/null @@ -1,118 +0,0 @@ -@using Elwig.Documents -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.DeliveryConfirmation -@{ Layout = "BusinessDocument"; } - -
-

@Model.Title

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @{ - var lastVariety = ""; - } - @foreach (var p in Model.Data.Rows) { - var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1); - var first = true; - @for (int i = 0; i < rows; i++) { - i + 1 ? "last" : "")"> - @if (first) { - - - - - - - - } - @if (i > 0 && i <= p.Modifiers.Length) { - - } else if (i > 0) { - - } - @if (i < p.Buckets.Length) { - var bucket = p.Buckets[i]; - - - } else { - - } - @if (i == p.Buckets.Length - 1) { - - - } else { - - - } - @if (first) { - - first = false; - } - - lastVariety = p.Variety; - } - } - - - - - - - - - - - - - -
Lieferschein-Nr.Pos.SorteAttr./Bewirt.QualitätsstufeGradationFlächenbindungMenge - - - gerebelt - - - Davon
abzuwerten
[°Oe][°KMW][kg][kg][kg]
@p.LsNr@p.DPNr@p.Variety@p.Attribute@(p.Attribute != null && p.Cultivation != null ? " / " : "")@p.Cultivation@p.QualityLevel@($"{p.Gradation.Oe:N0}")@($"{p.Gradation.Kmw:N1}")@(p.Modifiers[i - 1])@bucket.Name:@($"{bucket.Value:N0}")@($"{p.Weight:N0}")@(p.IsNetWeight ? "\u2611" : "\u2610")
Gesamt:@($"{Model.Data.Rows.Sum(p => p.Weight):N0}")
Davon abgewertet:@($"{Model.Data.Rows.Where(p => p.IsDepreciated).Sum(p => p.Weight):N0}")
- @Raw(BusinessDocument.PrintSortenaufteilung(Model.MemberStats)) - @Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includePayment: true)) -
- @if (Model.Text != null) { -

@Model.Text

- } -
-
diff --git a/Elwig/Documents/DeliveryConfirmation.css b/Elwig/Documents/DeliveryConfirmation.css deleted file mode 100644 index 23d074c..0000000 --- a/Elwig/Documents/DeliveryConfirmation.css +++ /dev/null @@ -1,32 +0,0 @@ - -table.delivery-confirmation .mod { - padding-left: 5mm; -} - -table.delivery-confirmation tr:not(.first) { - break-before: avoid; -} - -table.delivery-confirmation tr:not(.first) td { - padding-top: 0; -} - -table.delivery-confirmation tr.last td { - padding-bottom: 0; -} - -table.delivery-confirmation tr.sum { - font-size: 12pt; -} - -table.delivery-confirmation tr.sum td { - padding-top: 1mm; -} - -table.sortenaufteilung { - break-after: avoid; -} - -table.buckets { - break-before: avoid; -} diff --git a/Elwig/Documents/DeliveryDepreciationList.cs b/Elwig/Documents/DeliveryDepreciationList.cs index 5fb92c4..cdc474a 100644 --- a/Elwig/Documents/DeliveryDepreciationList.cs +++ b/Elwig/Documents/DeliveryDepreciationList.cs @@ -1,5 +1,9 @@ using Elwig.Models.Dtos; +using iText.Kernel.Pdf; +using iText.Layout.Element; +using iText.Layout.Properties; using System.Collections.Generic; +using System.Linq; namespace Elwig.Documents { public class DeliveryDepreciationList : Document { @@ -7,16 +11,98 @@ namespace Elwig.Documents { public new static string Name => "Abwertungsliste"; public string Filter; - public IEnumerable Deliveries; + public List Deliveries; public DeliveryDepreciationList(string filter, IEnumerable deliveries) : base($"{Name} {filter}") { Filter = filter; - Deliveries = deliveries; + Deliveries = [.. deliveries]; } public DeliveryDepreciationList(string filter, DeliveryJournalData data) : this(filter, data.Rows) { } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(new KernedParagraph(Name, 24) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 2, 0)); + doc.Add(new KernedParagraph(Filter, 14) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 5, 0)); + doc.Add(NewJournalTable(Deliveries)); + } + + protected Table NewJournalTable(List deliveries) { + var tbl = new Table(ColsMM(25, 6, 20, 12, 38, 18, 12, 10, 10, 14), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Pos.", rowspan: 2).SetPaddingRight(0)) + .AddHeaderCell(NewTh("Datum", rowspan: 2)) + .AddHeaderCell(NewTh("Zeit", rowspan: 2)) + .AddHeaderCell(NewTh("Sorte", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Attr./Bewirt.", rowspan: 2, colspan: 2, left: true)) + .AddHeaderCell(NewTh("Gradation", colspan: 2)) + .AddHeaderCell(NewTh("Menge")) + .AddHeaderCell(NewTh("[°Oe]")) + .AddHeaderCell(NewTh("[°KMW]").SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh("[kg]")); + + int? lastMember = null; + foreach (var p in deliveries) { + if (lastMember != p.MgNr) { + var border = lastMember != null; + var memberDeliveries = deliveries.Where(d => d.MgNr == p.MgNr).ToList(); + var memberKmw = Helpers.Utils.AggregateDeliveryPartsKmw(memberDeliveries); + var memberOe = Helpers.Utils.KmwToOe(memberKmw); + tbl.AddCell(NewTd($"{p.MgNr}, {p.AdministrativeName}", colspan: 5, borderTop: border) + .SetFont(BI)) + .AddCell(NewTd("Teil-Lfrg.:", borderTop: border, bold: true)) + .AddCell(NewTd($"{memberDeliveries.Count:N0}", right: true, borderTop: border, bold: true)) + .AddCell(NewTd($"{memberOe:N0}", center: true, borderTop: border, bold: true)) + .AddCell(NewTd($"{memberKmw:N1}", center: true, borderTop: border, bold: true)) + .AddCell(NewTd($"{memberDeliveries.Sum(p => p.Weight):N0}", right: true, borderTop: border, bold: true)); + } + + tbl.AddCell(NewTd(p.LsNr)) + .AddCell(NewTd($"{p.Pos:N0}", center: true).SetPaddingLeft(0).SetPaddingRight(0)) + .AddCell(NewTd($"{p.Date:dd.MM.yyyy}", center: true)) + .AddCell(NewTd($"{p.Time:HH:mm}", center: true).SetPadding(0)) + .AddCell(NewTd(p.Variety)) + .AddCell(NewTd($"{p.Attribute}{(p.Attribute != null && p.Cultivation != null ? " / " : "")}{p.Cultivation}", colspan: 2)) + .AddCell(NewTd($"{p.Oe:N0}", center: true)) + .AddCell(NewTd($"{p.Kmw:N1}", center: true).SetPaddingLeft(0).SetPaddingRight(0)) + .AddCell(NewTd($"{p.Weight:N0}", right: true)); + lastMember = p.MgNr; + } + + var branches = deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray(); + if (branches.Length > 1) { + foreach (var b in branches) { + var border = branches[0] == b; + var branchDeliveries = deliveries.Where(d => d.DeliveryBranch == b).ToList(); + var branchKmw = Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries); + var branchOe = Helpers.Utils.KmwToOe(branchKmw); + tbl.AddCell(NewTd($"{b}:", colspan: 2, bold: true, borderTop: border)) + .AddCell(NewTd($"(Teil-)Lieferungen: {branchDeliveries.DistinctBy(p => p.LsNr).Count():N0} ({branchDeliveries.Count:N0})", colspan: 5, bold: true, borderTop: border)) + .AddCell(NewTd($"{branchOe:N0}", center: true, bold: true, borderTop: border)) + .AddCell(NewTd($"{branchOe:N1}", center: true, bold: true, borderTop: border)) + .AddCell(NewTd($"{branchDeliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: border)); + } + } + + var kmw = Helpers.Utils.AggregateDeliveryPartsKmw(deliveries); + var oe = Helpers.Utils.KmwToOe(kmw); + tbl.AddCell(NewTd("Gesamt:", colspan: 2, bold: true, borderTop: true)) + .AddCell(NewTd($"(Teil-)Lieferungen: {deliveries.DistinctBy(p => p.LsNr).Count():N0} ({deliveries.Count:N0})", colspan: 5, bold: true, borderTop: true)) + .AddCell(NewTd($"{oe:N0}", center: true, bold: true, borderTop: true)) + .AddCell(NewTd($"{kmw:N1}", center: true, bold: true, borderTop: true)) + .AddCell(NewTd($"{deliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: true)); + + return tbl; + } } } diff --git a/Elwig/Documents/DeliveryDepreciationList.cshtml b/Elwig/Documents/DeliveryDepreciationList.cshtml deleted file mode 100644 index 0b56548..0000000 --- a/Elwig/Documents/DeliveryDepreciationList.cshtml +++ /dev/null @@ -1,104 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.DeliveryDepreciationList -@{ Layout = "Document"; } - -
-

Abwertungsliste

-

@Model.Filter

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @{ - int? lastMember = null; - } - @foreach (var p in Model.Deliveries) { - if (lastMember != p.MgNr) { - - @{ - var memberDeliveries = Model.Deliveries.Where(d => d.MgNr == p.MgNr).ToList(); - var memberKmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(memberDeliveries); - var memberOe = Elwig.Helpers.Utils.KmwToOe(memberKmw); - } - - - - - - - } - - - - - - - - - - - - lastMember = p.MgNr; - } - @{ - var branches = Model.Deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray(); - if (branches.Length > 1) { - foreach (var b in branches) { - - @{ - var branchDeliveries = Model.Deliveries.Where(d => d.DeliveryBranch == b).ToList(); - var branchKmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries); - var branchOe = Elwig.Helpers.Utils.KmwToOe(branchKmw); - } - - - - - - - } - } - } - - @{ - var kmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(Model.Deliveries); - var oe = Elwig.Helpers.Utils.KmwToOe(kmw); - } - - - - - - - -
Lieferschein-Nr.Pos.DatumZeitSorteAttr./Bewirt.GradationMenge
[°Oe][°KMW][kg]
- @($"{p.MgNr}, {p.AdministrativeName}") - Teil-Lfrg.: @($"{memberDeliveries.Count():N0}")@($"{memberOe:N0}")@($"{memberKmw:N1}")@($"{memberDeliveries.Sum(p => p.Weight):N0}")
@p.LsNr@p.Pos@($"{p.Date:dd.MM.yyyy}")@($"{p.Time:HH:mm}")@p.Variety@p.Attribute@(p.Attribute != null && p.Cultivation != null ? " / " : "")@p.Cultivation@($"{p.Oe:N0}")@($"{p.Kmw:N1}")@($"{p.Weight:N0}")
@b:(Teil-)Lieferungen: @($"{branchDeliveries.DistinctBy(p => p.LsNr).Count():N0}") (@($"{branchDeliveries.Count():N0}"))@($"{branchOe:N0}")@($"{branchKmw:N1}")@($"{branchDeliveries.Sum(p => p.Weight):N0}")
Gesamt:(Teil-)Lieferungen: @($"{Model.Deliveries.DistinctBy(p => p.LsNr).Count():N0}") (@($"{Model.Deliveries.Count():N0}"))@($"{oe:N0}")@($"{kmw:N1}")@($"{Model.Deliveries.Sum(p => p.Weight):N0}")
-
diff --git a/Elwig/Documents/DeliveryDepreciationList.css b/Elwig/Documents/DeliveryDepreciationList.css deleted file mode 100644 index aea8e98..0000000 --- a/Elwig/Documents/DeliveryDepreciationList.css +++ /dev/null @@ -1,13 +0,0 @@ - -h1 { - text-align: center; - font-size: 24pt; - margin-top: 0; - margin-bottom: 2mm; -} - -h2 { - text-align: center; - font-size: 14pt; - margin-top: 2mm; -} diff --git a/Elwig/Documents/DeliveryJournal.cs b/Elwig/Documents/DeliveryJournal.cs index 2e531aa..58ebf67 100644 --- a/Elwig/Documents/DeliveryJournal.cs +++ b/Elwig/Documents/DeliveryJournal.cs @@ -1,5 +1,9 @@ using Elwig.Models.Dtos; +using iText.Kernel.Pdf; +using iText.Layout.Element; +using iText.Layout.Properties; using System.Collections.Generic; +using System.Linq; namespace Elwig.Documents { public class DeliveryJournal : Document { @@ -7,16 +11,84 @@ namespace Elwig.Documents { public new static string Name => "Lieferjournal"; public string Filter; - public IEnumerable Deliveries; + public List Deliveries; public DeliveryJournal(string filter, IEnumerable deliveries) : base($"{Name} {filter}") { Filter = filter; - Deliveries = deliveries; + Deliveries = [.. deliveries]; } public DeliveryJournal(string filter, DeliveryJournalData data) : this(filter, data.Rows) { } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(new KernedParagraph(Name, 24) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 2, 0)); + doc.Add(new KernedParagraph(Filter, 14) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 5, 0)); + doc.Add(NewJournalTable(Deliveries)); + } + + protected Table NewJournalTable(List deliveries) { + var tbl = new Table(ColsMM(25, 6, 15, 8, 11, 38, 28, 10, 10, 14), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + tbl.AddHeaderCell(NewTh("Lieferschein-Nr.", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Pos.", rowspan: 2).SetPaddingRight(0)) + .AddHeaderCell(NewTh("Datum", rowspan: 2)) + .AddHeaderCell(NewTh("Zeit", rowspan: 2)) + .AddHeaderCell(NewTh("MgNr.", rowspan: 2)) + .AddHeaderCell(NewTh("Mitglied", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Sorte", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Gradation", colspan: 2)) + .AddHeaderCell(NewTh("Menge")) + .AddHeaderCell(NewTh("[°Oe]")) + .AddHeaderCell(NewTh("[°KMW]").SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh("[kg]")); + + foreach (var p in deliveries) { + tbl.AddCell(NewTd(p.LsNr)) + .AddCell(NewTd($"{p.Pos:N0}", center: true).SetPaddingLeft(0).SetPaddingRight(0)) + .AddCell(NewTd($"{p.Date:dd.MM.yyyy}", 8, center: true)) + .AddCell(NewTd($"{p.Time:HH:mm}", 8, center: true).SetPadding(0)) + .AddCell(NewTd($"{p.MgNr}", right: true)) + .AddCell(NewTd(p.AdministrativeName, 8)) + .AddCell(NewTd(p.Variety, 8)) + .AddCell(NewTd($"{p.Oe:N0}", center: true)) + .AddCell(NewTd($"{p.Kmw:N1}", center: true).SetPaddingLeft(0).SetPaddingRight(0)) + .AddCell(NewTd($"{p.Weight:N0}", right: true)); + } + + var branches = deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray(); + if (branches.Length > 1) { + foreach (var b in branches) { + var border = branches[0] == b; + var branchDeliveries = deliveries.Where(d => d.DeliveryBranch == b).ToList(); + var branchKmw = Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries); + var branchOe = Helpers.Utils.KmwToOe(branchKmw); + tbl.AddCell(NewTd($"{b}:", colspan: 2, bold: true, borderTop: border)) + .AddCell(NewTd($"(Teil-)Lieferungen: {branchDeliveries.DistinctBy(p => p.LsNr).Count():N0} ({branchDeliveries.Count:N0})", colspan: 5, bold: true, borderTop: border)) + .AddCell(NewTd($"{branchOe:N0}", center: true, bold: true, borderTop: border)) + .AddCell(NewTd($"{branchOe:N1}", center: true, bold: true, borderTop: border)) + .AddCell(NewTd($"{branchDeliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: border)); + } + } + + var kmw = Helpers.Utils.AggregateDeliveryPartsKmw(deliveries); + var oe = Helpers.Utils.KmwToOe(kmw); + tbl.AddCell(NewTd("Gesamt:", colspan: 2, bold: true, borderTop: true)) + .AddCell(NewTd($"(Teil-)Lieferungen: {deliveries.DistinctBy(p => p.LsNr).Count():N0} ({deliveries.Count:N0})", colspan: 5, bold: true, borderTop: true)) + .AddCell(NewTd($"{oe:N0}", center: true, bold: true, borderTop: true)) + .AddCell(NewTd($"{kmw:N1}", center: true, bold: true, borderTop: true)) + .AddCell(NewTd($"{deliveries.Sum(p => p.Weight):N0}", right: true, bold: true, borderTop: true)); + + return tbl; + } } } diff --git a/Elwig/Documents/DeliveryJournal.cshtml b/Elwig/Documents/DeliveryJournal.cshtml deleted file mode 100644 index 0f979d9..0000000 --- a/Elwig/Documents/DeliveryJournal.cshtml +++ /dev/null @@ -1,87 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.DeliveryJournal -@{ Layout = "Document"; } - -
-

Lieferjournal

-

@Model.Filter

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @foreach (var p in Model.Deliveries) { - - - - - - - - - - - - - } - @{ - var branches = Model.Deliveries.Select(d => d.DeliveryBranch).Distinct().Order().ToArray(); - if (branches.Length > 1) { - foreach (var b in branches) { - - @{ - var branchDeliveries = Model.Deliveries.Where(d => d.DeliveryBranch == b).ToList(); - var branchKmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(branchDeliveries); - var branchOe = Elwig.Helpers.Utils.KmwToOe(branchKmw); - } - - - - - - - } - } - } - - @{ - var kmw = Elwig.Helpers.Utils.AggregateDeliveryPartsKmw(Model.Deliveries); - var oe = Elwig.Helpers.Utils.KmwToOe(kmw); - } - - - - - - - -
Lieferschein-Nr.Pos.DatumZeitMgNr.MitgliedSorteGradationMenge
[°Oe][°KMW][kg]
@p.LsNr@p.Pos@($"{p.Date:dd.MM.yyyy}")@($"{p.Time:HH:mm}")@p.MgNr@p.AdministrativeName@p.Variety@($"{p.Oe:N0}")@($"{p.Kmw:N1}")@($"{p.Weight:N0}")
@b:(Teil-)Lieferungen: @($"{branchDeliveries.DistinctBy(p => p.LsNr).Count():N0}") (@($"{branchDeliveries.Count():N0}"))@($"{branchOe:N0}")@($"{branchKmw:N1}")@($"{branchDeliveries.Sum(p => p.Weight):N0}")
Gesamt:(Teil-)Lieferungen: @($"{Model.Deliveries.DistinctBy(p => p.LsNr).Count():N0}") (@($"{Model.Deliveries.Count():N0}"))@($"{oe:N0}")@($"{kmw:N1}")@($"{Model.Deliveries.Sum(p => p.Weight):N0}")
-
diff --git a/Elwig/Documents/DeliveryJournal.css b/Elwig/Documents/DeliveryJournal.css deleted file mode 100644 index aea8e98..0000000 --- a/Elwig/Documents/DeliveryJournal.css +++ /dev/null @@ -1,13 +0,0 @@ - -h1 { - text-align: center; - font-size: 24pt; - margin-top: 0; - margin-bottom: 2mm; -} - -h2 { - text-align: center; - font-size: 14pt; - margin-top: 2mm; -} diff --git a/Elwig/Documents/DeliveryNote.cs b/Elwig/Documents/DeliveryNote.cs index 3df249b..7529f0b 100644 --- a/Elwig/Documents/DeliveryNote.cs +++ b/Elwig/Documents/DeliveryNote.cs @@ -1,6 +1,14 @@ using Elwig.Helpers; using Elwig.Models.Entities; +using iText.Kernel.Pdf; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Layout; +using iText.Layout.Properties; +using System; using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; namespace Elwig.Documents { public class DeliveryNote : BusinessDocument { @@ -17,19 +25,183 @@ namespace Elwig.Documents { // 3 - full public int DisplayStats = App.Client.ModeDeliveryNoteStats; - public DeliveryNote(Delivery d, AppDbContext? ctx = null) : base($"{Name} Nr. {d.LsNr}", d.Member) { + public DeliveryNote(Delivery d, AppDbContext? ctx = null) : + base($"{Name} Nr. {d.LsNr}", d.Member) { UseBillingAddress = true; ShowDateAndLocation = true; Delivery = d; - Aside = Aside.Replace("", "") + - $"Lieferung" + - $"LS-Nr.:{d.LsNr}" + - $"Datum/Zeit:{d.Date:dd.MM.yyyy} / {d.Time:HH:mm}" + - $"Zweigstelle:{d.Branch.Name}" + - $""; Text = App.Client.TextDeliveryNote; DocumentId = d.LsNr; + IsDoublePaged = true; MemberBuckets = ctx?.GetMemberBuckets(d.Year, d.Member.MgNr).GetAwaiter().GetResult() ?? []; } + + protected override void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderHeader(doc, pdf); + Aside?.AddCell(NewAsideCell("Lieferung", 2)) + .AddCell(NewAsideCell("LS-Nr.:", isName: true)).AddCell(NewAsideCell(Delivery.LsNr)) + .AddCell(NewAsideCell("Datum/Zeit:", isName: true)).AddCell(NewAsideCell($"{Delivery.Date:dd.MM.yyyy} / {Delivery.Time:HH:mm}")) + .AddCell(NewAsideCell("Zweigstelle:", isName: true)).AddCell(NewAsideCell(Delivery.Branch.Name)); + } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + + doc.Add(NewDeliveryTable()); + if (Delivery.Comment != null) { + doc.Add(new KernedParagraph($"Anmerkung zur Lieferung: {Delivery.Comment}", 10).SetMarginsMM(5, 0, 0, 0)); + } + if (DisplayStats > 0) { + doc.Add(NewBucketTable(Delivery.Season, MemberBuckets, isTiny: true, + filter: DisplayStats > 2 ? null : DisplayStats == 1 ? [] : Delivery.Parts.Select(p => p.SortId).Distinct().ToList()) + .SetKeepTogether(true) + .SetMarginsMM(5, 0, 0, 0)); + } + + var sig = new Div().SetWidth(165 * PtInMM); + if (Text != null) + sig.Add(new KernedParagraph(Regex.Replace(Text, @"\s+", " "), 10).SetTextAlignment(TextAlignment.JUSTIFIED).SetSpacingRatio(1)); + sig.Add(new Table(ColsMM(15, 50, 35, 50, 15)).SetMarginsMM(18, 0, 2, 0) + .AddCell(NewTd()) + .AddCell(NewTd("Genossenschaft", center: true, borderTop: true).SetPaddingTopMM(1)) + .AddCell(NewTd()) + .AddCell(NewTd("Mitglied", center: true, borderTop: true).SetPaddingTopMM(1)) + .AddCell(NewTd())); + + var renderer = sig.CreateRendererSubTree(); + renderer.SetParent(doc.GetRenderer()); + var layoutResult = renderer.Layout(new LayoutContext(new LayoutArea(1, pdf.GetDefaultPageSize()))); + float sigHeight = layoutResult.GetOccupiedArea().GetBBox().GetHeight(); + doc.Add(new Div().SetWidth(165 * PtInMM).SetHeight(sigHeight)); + + var size = pdf.GetDefaultPageSize(); + doc.Add(sig.SetFixedPosition( + size.GetLeft() + doc.GetLeftMargin(), + size.GetBottom() + doc.GetBottomMargin(), + size.GetWidth() - doc.GetLeftMargin() - doc.GetRightMargin())); ; + } + + protected Cell NewDeliveryMainTd(string? text, int rowspan = 1, int colspan = 1, bool center = false, bool right = false) { + return NewTd(text, 12, rowspan: rowspan, colspan: colspan, bold: true, center: center, right: right) + .SetPaddingTopMM(2); + } + + protected Table NewDeliveryTable() { + var tbl = new Table(ColsMM(10, 21, 25, 19.5, 19.5, 30, 12.5, 12.5, 15), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorder(Border.NO_BORDER).SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + tbl.AddHeaderCell(NewTh("Pos.", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Sorte", colspan: 2, rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Attribut", colspan: 2, rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Qualitätsstufe", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Gradation", colspan: 2)) + .AddHeaderCell(NewTh("Menge")) + .AddHeaderCell(NewTh("[°Oe]", 8)) + .AddHeaderCell(NewTh("[°KMW]", 8)) + .AddHeaderCell(NewTh("[kg]", 8)); + + foreach (var part in Delivery.Parts.OrderBy(p => p.DPNr)) { + var sub = new Table(ColsMM(10, 21, 25, 19.5, 19.5, 30, 12.5, 12.5, 15), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + int rowspan = 1 + (part.Cultivation != null ? 1 : 0) + 1 + part.Modifiers.Count() + 1 + (part.Comment != null ? 1 : 0) + (part.Temperature != null || part.Acid != null ? 1 : 0); + sub.AddCell(NewDeliveryMainTd($"{part.DPNr:N0}", center: true)) + .AddCell(NewDeliveryMainTd(part.Variety.Name, colspan: 2)) + .AddCell(NewDeliveryMainTd(part.Attribute?.Name, colspan: 2)) + .AddCell(NewDeliveryMainTd(part.Quality.Name)) + .AddCell(NewDeliveryMainTd($"{part.Oe:N0}", center: true)) + .AddCell(NewDeliveryMainTd($"{part.Kmw:N1}", center: true)) + .AddCell(NewDeliveryMainTd($"{part.Weight:N0}", center: true)); + + if (part.Cultivation != null) { + var cult = new KernedParagraph(8); + cult.Add(Italic("Bewirtschaftung:")).Add(Normal(" " + part.Cultivation.Name + (part.Cultivation.Description != null ? $" ({part.Cultivation.Description})" : ""))); + sub.AddCell(NewTd()) + .AddCell(NewTd(cult, colspan: 5)) + .AddCell(NewTd(colspan: 3)); + } + sub.AddCell(NewTd()) + .AddCell(NewTd(new KernedParagraph(8).Add(Italic("Herkunft:")).Add(Normal(" " + part.OriginString.Replace("\n ", "\n\u00a0"))), colspan: 5)) + .AddCell(NewTd(colspan: 3)); + if (part.Modifiers.Any()) { + sub.AddCell(NewTd()) + .AddCell(NewTd("Zu-/Abschläge:", 8, italic: true)); + int i = 0, last = part.Modifiers.Count() - 1; + foreach (var mod in part.Modifiers) { + if (i > 0) sub.AddCell(NewCell(colspan: 2)); + sub.AddCell(NewTd(mod.Name, 8, bold: true, colspan: 3).SetPaddingsMM(i == 0 ? 0.5f : 0, 1, i == last ? 0.5f : 0, 1)) + .AddCell(NewTd(mod.PublicValueStr, 8).SetPaddingsMM(i == 0 ? 0.5f : 0, 1, i == last ? 0.5f : 0, 1)) + .AddCell(NewTd(colspan: 3)); + i++; + } + } + var weighing = new KernedParagraph(8); + if (part.IsManualWeighing) { + weighing.Add(Italic("Handwiegung " + (part.IsNetWeight ? "(gerebelt gewogen)" : "(nicht gerebelt gewogen)"))); + if (part.WeighingReason != null) { + weighing.Add(Normal(", ")) + .Add(Italic("Begründung:")) + .Add(Normal(" " + part.WeighingReason)); + } + } else { + var info = part.WeighingInfo; + weighing.Add(Italic("Waage:")) + .Add(Normal(" " + (part.ScaleId ?? "?") + ", ")) + .Add(Italic("ID:")) + .Add(Normal(" " + (info.Id ?? "?"))); + if (info.Date != null || info.Time != null) + weighing.Add(Normal(" \u2013 ")); + if (info.Time != null) + weighing.Add(Normal($"{info.Time:HH:mm}")); + if (info.Date != null) + weighing.Add(Normal($", {info.Date:dd.MM.yyyy}")); + if (info.Gross != null && info.Tare != null && info.Net != null) { + weighing.Add("\n") + .Add(Italic("Brutto:")) + .Add(Normal($" {info.Gross:N0} kg \u2013 ")) + .Add(Italic("Tara:")) + .Add(Normal($" {info.Tare:N0} kg \u2013 ")) + .Add(Italic("Netto:")) + .Add(Normal($" {info.Net:N0} kg \u2013 ")) + .Add(Italic(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen")); + } else { + weighing.Add(" ") + .Add(Italic(part.IsNetWeight ? "(gerebelt gewogen)" : "(nicht gerebelt gewogen)")); + } + } + sub.AddCell(NewCell()) + .AddCell(NewCell(weighing, colspan: 5)) + .AddCell(NewCell(colspan: 3)); + if (part.Comment != null) { + sub.AddCell(NewCell()) + .AddCell(NewCell(new KernedParagraph(8).Add(Italic("Anmerkung")).Add(Normal(" " + part.Comment)), colspan: 5)) + .AddCell(NewCell(colspan: 3)); + } + if (part.Temperature != null || part.Acid != null) { + var p = new KernedParagraph(8); + if (part.Temperature != null) + p.Add(Italic("Temperatur:")).Add(Normal($" {part.Temperature:N1} °C")); + if (part.Temperature != null && part.Acid != null) + p.Add(Normal(", ")); + if (part.Acid != null) + p.Add(Italic("Säure")).Add(Normal($" {part.Acid:N1} g/l")); + sub.AddCell(NewCell()) + .AddCell(NewCell(p, colspan: 5)) + .AddCell(NewCell(colspan: 3)); + } + tbl.AddCell(new Cell(1, 9).SetPadding(0).SetBorder(Border.NO_BORDER).Add(sub)); + } + + if (Delivery.Parts.Count > 1) { + tbl.AddCell(NewTd("Gesamt:", 12, bold: true, borderTop: true, colspan: 6).SetPaddingsMM(1, 1, 1, 1)) + .AddCell(NewTd($"{Delivery.Oe:N0}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)) + .AddCell(NewTd($"{Delivery.Kmw:N1}", 12, bold: true, center: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)) + .AddCell(NewTd($"{Delivery.Weight:N0}", 12, bold: true, right: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); + } + + return tbl; + } } } diff --git a/Elwig/Documents/DeliveryNote.cshtml b/Elwig/Documents/DeliveryNote.cshtml deleted file mode 100644 index 30e929a..0000000 --- a/Elwig/Documents/DeliveryNote.cshtml +++ /dev/null @@ -1,115 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.DeliveryNote -@{ Layout = "BusinessDocument"; } - -
-

@Model.Title

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - @foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) { - - - - - - - - - - @if (part.Cultivation != null) { - - } - - @if (part.Modifiers.Count() > 0) { - var first = true; - foreach (var mod in part.Modifiers) { - - first = false; - } - } - - @if (part.Comment != null) { - - } - @if (part.Temperature != null || part.Acid != null) { - - } - } - @if (Model.Delivery.Parts.Count() > 1) { - - - - - - - } - -
Pos.SorteAttributQualitätsstufeGradationMenge
[°Oe][°KMW][kg]
@part.DPNr@part.Variety.Name@part.Attribute?.Name@part.Quality.Name@($"{part.Oe:N0}")@($"{part.Kmw:N1}")@($"{part.Weight:N0}")
Bewirtschaftung: - @part.Cultivation.Name - @if(part.Cultivation.Description != null) { - @("(")@part.Cultivation.Description@(")") - } -
Herkunft: @part.OriginString
@Raw(first ? "Zu-/Abschläge:" : "")@mod.Name@mod.PublicValueStr
- @if (part.IsManualWeighing) { - Handwiegung @(part.IsNetWeight ? " (gerebelt gewogen)" : " (nicht gerebelt gewogen)")@Raw(part.WeighingReason != null ? ", Begründung: " : "") @part.WeighingReason - } else { - var info = part.WeighingInfo; - Waage: @(part.ScaleId ?? "?")@(", ") ID: @(info.Id ?? "?") - @(info.Date != null || info.Time != null ? " – " : "")@(info.Time != null ? $"{info.Time:HH:mm}" : "")@(info.Date != null ? $", {info.Date:dd.MM.yyyy}" : "") - @if (info.Gross != null && info.Tare != null && info.Net != null) { -
Brutto: @($"{info.Gross:N0} kg")@(" – ") Tara: @($"{info.Tare:N0} kg")@(" – ") Netto: @($"{info.Net:N0} kg")@(" – ")@Raw(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen") - } else { - @Raw($" ({(part.IsNetWeight ? "gerebelt gewogen" : "nicht gerebelt gewogen")})") - } - } -
Anmerkung: @part.Comment
@Raw(part.Temperature != null ? $"Temperatur: {part.Temperature:N1} °C" : "")@(part.Temperature != null && part.Acid != null ? ", " : "")@Raw(part.Acid != null ? $"Säure: {part.Acid:N1} g/l" : "")
Gesamt:@($"{Model.Delivery.Oe:N0}")@($"{Model.Delivery.Kmw:N1}")@($"{Model.Delivery.Weight:N0}")
-@if (Model.Delivery.Comment != null) { -

Amerkung zur Lieferung: @Model.Delivery.Comment

-} -@if (Model.DisplayStats > 0) { - @Raw(Model.PrintBucketTable( - Model.Delivery.Season, Model.MemberBuckets, isTiny: true, - filter: Model.DisplayStats > 2 ? null : - Model.DisplayStats == 1 ? new List() : - Model.Delivery.Parts.Select(p => p.SortId).Distinct().ToList() - )) -} -
-@for (int i = 0; i < 2; i++) { -
- @if (Model.Text != null) { -

@Model.Text

- } -
-
Genossenschaft
-
Mitglied
-
-
-} diff --git a/Elwig/Documents/DeliveryNote.css b/Elwig/Documents/DeliveryNote.css deleted file mode 100644 index b60e7c2..0000000 --- a/Elwig/Documents/DeliveryNote.css +++ /dev/null @@ -1,46 +0,0 @@ - -main h1 { - margin-bottom: 1.5em !important; -} - -table.delivery { - margin-bottom: 5mm; -} - -table.delivery tr:not(.main) { - break-before: avoid; -} - -table.delivery th.main { - text-align: left; -} - -table.delivery tr.main td { - font-weight: bold; - padding-top: 2mm; -} - -table.delivery tbody tr:not(.main) td { - font-size: 8pt; -} - -table.delivery tr.tight:not(.first) td { - padding-top: 0; - padding-bottom: 0; -} - -table.delivery tr.tight.first td { - padding-bottom: 0; -} - -table.delivery tr.tight:has(+ tr:not(.tight)) td { - padding-bottom: 0.5mm !important; -} - -table.delivery tr.sum td { - padding-top: 1mm; -} - -table.delivery-note-stats { - break-after: avoid; -} diff --git a/Elwig/Documents/Document.Page.css b/Elwig/Documents/Document.Page.css deleted file mode 100644 index 758fb29..0000000 --- a/Elwig/Documents/Document.Page.css +++ /dev/null @@ -1,43 +0,0 @@ - -.page-break { - break-before: page; -} -hr.page-break { - display: none; -} - -@page { - size: A4; - margin: 25mm 20mm 25mm 25mm; -} - -@page :first { - margin: 25mm 20mm 35mm 25mm; -} - -@media screen { - body, header { - width: 210mm; - } - - header, .address-wrapper, aside, main { - border: 1px solid lightgray; - } - - header { - top: 0; - } - - .spacing { - height: 45mm; - } - - .main-wrapper { - margin: 0 20mm 40mm 25mm; - } -} - -a { - text-decoration: inherit; - color: inherit; -} diff --git a/Elwig/Documents/Document.Table.css b/Elwig/Documents/Document.Table.css deleted file mode 100644 index a8633f1..0000000 --- a/Elwig/Documents/Document.Table.css +++ /dev/null @@ -1,178 +0,0 @@ - -main table { - border-collapse: collapse; - margin-bottom: 0mm; - font-size: 10pt; -} -main table.large {font-size: 12pt;} -main table.tiny { - font-size: 8pt; - margin-bottom: 0mm; -} - -main table.border { - border: var(--border-thickness) solid black; -} - -main table tr { - break-inside: avoid; -} - -main table th, -main table td { - overflow: hidden; - white-space: nowrap; - vertical-align: middle; - padding: 0.5mm 1mm; - font-weight: normal; -} -main table.small th, -main table.small td, -main table.tiny th, -main table.tiny td { - padding: 0mm 0.125mm; -} - -main table td[rowspan] { - vertical-align: top; -} - -main table thead { - font-size: 8pt; -} -main table.large thead { - font-size: 10pt; -} - -main table th { - font-style: italic; -} - -main table tbody { -} - -main table .small { - font-size: 8pt; -} -main table .large { - font-size: 12pt; -} -main table .tiny { - font-size: 6pt; -} - -main table.number td, -main table.number th { - padding-left: 0; - padding-right: 0; -} - -main table.number thead th, -main table.number td, -main table tbody td.number { - text-align: right; -} - -main table.center tbody td, -main table tbody td.center { - text-align: center; -} - -main table tbody th { - text-align: left; -} - -main table.cohere { - break-inside: avoid; -} - -main table tr.subheading th, -main table tr.subheading td { - font-weight: bold; -} - -main table tr.subheading th { - text-align: left; - font-size: 10pt; -} - -main table.small tr.subheading th, -main table.small tr.subheading td, -main table.tiny tr.subheading th, -main table.tiny tr.subheading td { - font-size: 8pt; -} - -main table tr.sectionheading { - background-color: #E0E0E0; -} - -main table tr.sectionheading th { - padding-top: 0.5mm; - padding-bottom: 0.5mm; - font-weight: bold; - text-align: center; - font-size: 10pt; - border-top: var(--border-thickness) solid black; -} - -main table tr.header { - border: var(--border-thickness) solid black; - background-color: #E0E0E0; -} - -main table tr.header th { - font-weight: bold; - font-style: normal; - font-size: 16pt; - padding: 1mm 2mm; -} - -main table tr.spacing td, -main table tr.spacing th { - height: 5mm; -} - -main table tr.spacing ~ tr.header { - break-before: avoid; -} - -main table.number thead tr:first-child th:first-child { - text-align: left; -} - -main table tr.bold td { - font-weight: bold; -} - -main table tr.sum, -main table td.sum, -main table tr.new, -main table tr.border { - border-top: var(--border-thickness) solid black; -} - -main table th.unit { - font-size: 8pt; -} - -main table.number th.unit { - padding-right: 2mm; -} - -main table th.narrow { - padding-left: 0; - padding-right: 0; -} - -main table .tborder {border-top: var(--border-thickness) solid black;} -main table .lborder {border-left: var(--border-thickness) solid black;} -main table .rborder {border-right: var(--border-thickness) solid black;} - -main table .fleft { - float: left; -} - -main tbody.sum tr:last-child { - border-bottom: var(--border-thickness) solid black; -} diff --git a/Elwig/Documents/Document.cs b/Elwig/Documents/Document.cs index a15511a..bf586c0 100644 --- a/Elwig/Documents/Document.cs +++ b/Elwig/Documents/Document.cs @@ -1,19 +1,42 @@ -using System; -using System.Threading.Tasks; -using System.IO; using Elwig.Helpers; -using System.Collections.Generic; -using System.Linq; using Elwig.Helpers.Printing; +using iText.IO.Font; +using iText.Kernel.Font; +using iText.Kernel.Pdf; +using iText.Kernel.Pdf.Canvas; +using iText.Kernel.Pdf.Event; +using iText.Kernel.Pdf.Xobject; +using iText.Kernel.Utils; +using iText.Layout; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; using MimeKit; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Threading; +using System.Threading.Tasks; namespace Elwig.Documents { - public abstract partial class Document : IDisposable { + public class Document : IDisposable { public static string Name => "Dokument"; - protected static readonly double GenerationProportion = 0.125; + public const float PtInMM = 2.8346456693f; + public const float BorderThickness = 0.5f; + + protected PdfFont NF = null!; + protected PdfFont BF = null!; + protected PdfFont IF = null!; + protected PdfFont BI = null!; + protected PdfFont SF = null!; + + //protected readonly PdfFont NF = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN); + //protected readonly PdfFont BF = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLD); + //protected readonly PdfFont IF = PdfFontFactory.CreateFont(StandardFonts.TIMES_ITALIC); + //protected readonly PdfFont BI = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLDITALIC); protected TempFile? _pdfFile = null; protected string? _pdfPath; @@ -24,22 +47,18 @@ namespace Elwig.Documents { public bool ShowFoldMarks = App.Config.Debug; public bool IsDoublePaged = false; public bool IsPreview = false; + private iText.Layout.Document? _doc; - public string DocumentsPath; public int CurrentNextSeason; public string? DocumentId; public string Title; public string Author; - public string Header; public DateOnly Date; public Document(string title) { - var c = App.Client; - DocumentsPath = App.DocumentsPath; CurrentNextSeason = Utils.CurrentNextSeason; Title = title; - Author = c.NameFull; - Header = ""; + Author = App.Client.NameFull; Date = DateOnly.FromDateTime(Utils.Today); } @@ -58,99 +77,173 @@ namespace Elwig.Documents { } public static Document FromPdf(string path) { - return new PdfDocument(path); + return new RawPdfDocument(path); } - private async Task Render() { - string name; - if (this is BusinessLetter) { - name = "BusinessLetter"; - } else if (this is DeliveryNote) { - name = "DeliveryNote"; - } else if (this is CreditNote) { - name = "CreditNote"; - } else if (this is DeliveryJournal) { - name = "DeliveryJournal"; - } else if (this is DeliveryDepreciationList) { - name = "DeliveryDepreciationList"; - } else if (this is Letterhead) { - name = "Letterhead"; - } else if (this is DeliveryConfirmation) { - name = "DeliveryConfirmation"; - } else if (this is MemberDataSheet) { - name = "MemberDataSheet"; - } else if (this is MemberList) { - name = "MemberList"; - } else if (this is WineQualityStatistics) { - name = "WineQualityStatistics"; - } else if (this is PaymentVariantSummary) { - name = "PaymentVariantSummary"; - } else if (this is DeliveryAncmtList) { - name = "DeliveryAncmtList"; - } else { - throw new InvalidOperationException("Invalid document object"); + private class RawPdfDocument : Document { + public RawPdfDocument(string pdfPath) : + base(Path.GetFileNameWithoutExtension(pdfPath)) { + _pdfPath = pdfPath; } - return await Render(name); } - private async Task Render(string name) { - return await Html.CompileRenderAsync(name, this); ; + public int Render(string path) { + using var writer = new PdfWriter(path); + return Render(writer); + } + + private int Render(PdfWriter writer) { + NF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\times.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED); + BF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesbd.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED); + IF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesi.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED); + BI = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\timesbi.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED); + SF = PdfFontFactory.CreateFont(@"C:\Windows\Fonts\seguisym.ttf", PdfEncodings.IDENTITY_H, PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED); + NF.SetSubset(true); + BF.SetSubset(true); + IF.SetSubset(true); + BI.SetSubset(true); + SF.SetSubset(true); + writer.SetCompressionLevel(CompressionConstants.BEST_COMPRESSION); + writer.SetSmartMode(true); + using var pdf = new PdfDocument(writer); + pdf.GetDocumentInfo() + .SetTitle(Title) + .SetAuthor(Author) + .SetCreator($"Elwig {App.Version}"); + var handler = new EventHandler(this); + pdf.AddEventHandler(PdfDocumentEvent.START_PAGE, handler); + pdf.AddEventHandler(PdfDocumentEvent.END_PAGE, handler); + pdf.AddEventHandler(PdfDocumentEvent.START_DOCUMENT_CLOSING, handler); + _doc = new iText.Layout.Document(pdf, iText.Kernel.Geom.PageSize.A4); + try { + _doc.SetFont(NF).SetFontSize(12); + RenderHeader(_doc, pdf); + RenderBody(_doc, pdf); + var pageNum = pdf.GetNumberOfPages(); + return pageNum; + } finally { + _doc.Close(); + } + } + + protected virtual void RenderHeader(iText.Layout.Document doc, PdfDocument pdf) { } + + protected virtual void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { } + + protected virtual Paragraph GetFooter() { + return new KernedParagraph(App.Client.NameFull, 10); } public async Task Generate(CancellationToken? cancelToken = null, IProgress? progress = null) { if (_pdfFile != null) return; progress?.Report(0.0); - if (this is PdfDocument) { + if (this is RawPdfDocument) { // nothing to do } else if (this is MergedDocument m) { + using var tmpPdf = new TempFile("pdf"); var pdf = new TempFile("pdf"); - var tmpHtmls = new List(); - var tmpFiles = new List(); try { - var n = m.Documents.Count(); - int i = 0; - foreach (var doc in m.Documents) { - if (doc is PdfDocument) { - tmpFiles.Add(doc.PdfPath!); - continue; - } + var pageNums = new List(); + + using var writer = new PdfWriter(pdf.FilePath); + using var mergedPdf = new PdfDocument(writer); + var merger = new PdfMerger(mergedPdf); + + PdfPage? letterheadPage = null; + int letterheadInsertIndex = 0; + int letterheadDocIndex = 0; + + for (int i = 0; i < m.Documents.Count; i++) { if (cancelToken?.IsCancellationRequested ?? false) throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!"); - var tmpHtml = new TempFile("html"); - await File.WriteAllTextAsync(tmpHtml.FilePath, await doc.Render(), Utils.UTF8); - tmpHtmls.Add(tmpHtml); - tmpFiles.Add((doc is Letterhead ? "#" : "") + tmpHtml.FilePath); - i++; - progress?.Report(GenerationProportion * 100 * i / n); + var doc = m.Documents[i]; + int p0 = mergedPdf.GetNumberOfPages(); + + if (letterheadPage != null && doc is Letterhead) { + if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) { + mergedPdf.AddPage(letterheadPage); + mergedPdf.AddNewPage(); + } else { + mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage); + mergedPdf.AddNewPage(letterheadInsertIndex + 2); + } + + pageNums[letterheadDocIndex] = 1; + letterheadPage = null; + } + + if (doc is RawPdfDocument) { + if (IsDoublePaged && doc is Letterhead) { + using var reader = new PdfReader(doc.PdfPath); + using var src = new PdfDocument(reader); + letterheadPage = src.GetPage(1).CopyTo(mergedPdf); + letterheadInsertIndex = p0; + letterheadDocIndex = i; + } else { + using var reader = new PdfReader(doc.PdfPath); + using var src = new PdfDocument(reader); + merger.Merge(src, 1, src.GetNumberOfPages()); + } + } else { + int pageNum = doc.Render(tmpPdf.FilePath); + if (IsDoublePaged && doc is Letterhead) { + using var reader = new PdfReader(tmpPdf.FilePath); + using var src = new PdfDocument(reader); + letterheadPage = src.GetPage(1).CopyTo(mergedPdf); + letterheadInsertIndex = p0; + letterheadDocIndex = i; + } else { + using var reader = new PdfReader(tmpPdf.FilePath); + using var src = new PdfDocument(reader); + merger.Merge(src, 1, pageNum); + } + } + + int p1 = mergedPdf.GetNumberOfPages(); + pageNums.Add(p1 - p0); + + if (IsDoublePaged && doc is not Letterhead && mergedPdf.GetNumberOfPages() % 2 != 0) { + if (letterheadPage != null) { + mergedPdf.AddPage(letterheadPage); + letterheadPage = null; + } else { + mergedPdf.AddNewPage(); + } + } + + progress?.Report(100.0 * (i + 1) / (m.Documents.Count + 1)); } - progress?.Report(GenerationProportion * 100); - var pages = Pdf.Convert(tmpFiles, pdf.FilePath, IsDoublePaged, m.Documents, cancelToken, new Progress(v => progress?.Report(GenerationProportion * 100 + v * (1 - GenerationProportion)))); - TotalPages = pages.Pages; - _pdfFile = pdf; + + if (letterheadPage != null) { + if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) { + mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf)); + mergedPdf.AddNewPage(); + } else { + mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage.CopyTo(mergedPdf)); + mergedPdf.AddNewPage(letterheadInsertIndex + 2); + } + + pageNums[letterheadDocIndex] = 1; + } + + TotalPages = pageNums.Sum(); } catch { pdf.Dispose(); throw; - } finally { - foreach (var tmp in tmpHtmls) { - tmp.Dispose(); - } } + _pdfFile = pdf; } else { if (cancelToken?.IsCancellationRequested ?? false) throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!"); var pdf = new TempFile("pdf"); try { - using var tmpHtml = new TempFile("html"); - await File.WriteAllTextAsync(tmpHtml.FilePath, await Render(), Utils.UTF8); - progress?.Report(50.0); - var pages = Pdf.Convert(tmpHtml.FilePath, pdf.FilePath, this); - TotalPages = pages.Pages; - _pdfFile = pdf; + TotalPages = Render(pdf.FilePath); } catch { pdf.Dispose(); throw; } + _pdfFile = pdf; } progress?.Report(100.0); } @@ -180,14 +273,239 @@ namespace Elwig.Documents { }; } - private class MergedDocument(IEnumerable docs) : Document("Mehrere Dokumente") { - public IEnumerable Documents = docs; + private class MergedDocument : Document { + public List Documents; + + public MergedDocument(IEnumerable docs) : + base("Mehrere Dokumente") { + Documents = [.. docs]; + IsDoublePaged = docs.Any(x => x.IsDoublePaged); + } } - private class PdfDocument : Document { - public PdfDocument(string pdfPath) : - base(Path.GetFileNameWithoutExtension(pdfPath)) { - _pdfPath = pdfPath; + protected static Cell NewCell(Paragraph? p = null, int rowspan = 1, int colspan = 1, bool overflow = false) { + var cell = new Cell(rowspan, colspan) + .SetBorder(Border.NO_BORDER) + .SetPaddingsMM(0.5f, 1, 0.5f, 1); + if (p != null) { + p.SetProperty(Property.NO_SOFT_WRAP_INLINE, true); + if (!overflow) p.SetProperty(Property.OVERFLOW_X, OverflowPropertyValue.HIDDEN); + cell.Add(p); + } + return cell; + } + + protected Cell NewTh(string? text, float fontSize = 8, int rowspan = 1, int colspan = 1, bool left = false, bool rotated = false) { + var p = new KernedParagraph(text ?? "", fontSize); + if (rotated) p.SetRotationAngle(rotated ? 0.5 * Math.PI : 0); + var cell = NewCell(p, rowspan: rowspan, colspan: colspan) + .SetTextAlignment(left ? TextAlignment.LEFT : TextAlignment.CENTER) + .SetVerticalAlignment(VerticalAlignment.MIDDLE) + .SetFont(IF); + if (rotated || !left) cell.SetPadding(0); + return cell; + } + + protected Cell NewTd(string? text = null, float fontSize = 10, int rowspan = 1, int colspan = 1, bool center = false, bool right = false, bool bold = false, bool italic = false, bool borderTop = false, bool overflow = false) { + return NewTd(new KernedParagraph(text ?? "", fontSize), rowspan: rowspan, colspan: colspan, center: center, right: right, bold: bold, italic: italic, borderTop: borderTop, overflow: overflow); + } + + protected Cell NewTd(KernedParagraph p, int rowspan = 1, int colspan = 1, bool center = false, bool right = false, bool bold = false, bool italic = false, bool borderTop = false, bool overflow = false) { + return NewCell(p, rowspan: rowspan, colspan: colspan, overflow: overflow) + .SetTextAlignment(center ? TextAlignment.CENTER : right ? TextAlignment.RIGHT : TextAlignment.LEFT) + .SetVerticalAlignment(VerticalAlignment.MIDDLE) + .SetBorderTop(borderTop ? new SolidBorder(BorderThickness) : Border.NO_BORDER) + .SetFont(bold ? (italic ? BI : BF) : (italic ? IF : NF)); + } + + public static float[] ColsMM(params double[] widths) { + return [.. widths.Select(w => (float)w * PtInMM)]; + } + + public Text Normal(string? text, float? fontSize = null) { + var t = new Text(text ?? "").SetFont(NF); + if (fontSize != null) t.SetFontSize(fontSize.Value); + return t; + } + + public Text Bold(string? text, float? fontSize = null) { + var t = new Text(text ?? "").SetFont(BF); + if (fontSize != null) t.SetFontSize(fontSize.Value); + return t; + } + + public Text Italic(string? text, float? fontSize = null) { + var t = new Text(text ?? "").SetFont(IF); + if (fontSize != null) t.SetFontSize(fontSize.Value); + return t; + } + + public Text BoldItalic(string? text, float? fontSize = null) { + var t = new Text(text ?? "").SetFont(BI); + if (fontSize != null) t.SetFontSize(fontSize.Value); + return t; + } + + private class EventHandler : AbstractPdfDocumentEventHandler { + + private const float _fontSize = 10; + private const float _placeholderWidth = 50 * PtInMM; + + private readonly Document _doc; + private readonly List _pageNumPlaceholders; + + + public int NumberOfPages { get; private set; } + + public EventHandler(Document doc) { + _doc = doc; + _pageNumPlaceholders = []; + } + + protected override void OnAcceptedEvent(AbstractPdfDocumentEvent evt) { + if (evt.GetType() == PdfDocumentEvent.START_PAGE) { + OnPageStart((PdfDocumentEvent)evt); + } else if (evt.GetType() == PdfDocumentEvent.END_PAGE) { + OnPageEnd((PdfDocumentEvent)evt); + } else if (evt.GetType() == PdfDocumentEvent.START_DOCUMENT_CLOSING) { + OnDocumentClose((PdfDocumentEvent)evt); + } + } + + private void OnPageStart(PdfDocumentEvent evt) { + var pdf = evt.GetDocument(); + var page = evt.GetPage(); + var pageNum = pdf.GetPageNumber(page); + + if (pageNum == 1) { + // first page + if (_doc is BusinessDocument) { + _doc._doc?.SetMarginsMM(90, 20, 35, 25); + } else { + _doc._doc?.SetMarginsMM(20, 20, 35, 25); + } + } else if (_doc.IsDoublePaged && (pageNum % 2) == 0) { + // left page (= swapped) + _doc._doc?.SetMarginsMM(20, 25, 25, 20); + } else { + // right page + _doc._doc?.SetMarginsMM(20, 20, 25, 25); + } + } + + private void OnPageEnd(PdfDocumentEvent evt) { + var pdf = evt.GetDocument(); + var page = evt.GetPage(); + var pageNum = pdf.GetPageNumber(page); + + var pageSize = page.GetPageSize(); + float leftX1 = pageSize.GetLeft() + 25 * PtInMM; + float leftX2 = pageSize.GetLeft() + 20 * PtInMM; + float rightX1 = pageSize.GetRight() - 20 * PtInMM; + float rightX2 = pageSize.GetRight() - 25 * PtInMM; + float footerY = pageSize.GetBottom() + 25 * PtInMM; + float y1 = footerY + _fontSize; + float y2 = footerY - _fontSize; + + var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdf); + using var canvas = new Canvas(pdfCanvas, pageSize); + + var placeholder = new PdfFormXObject(new iText.Kernel.Geom.Rectangle(0, 0, _placeholderWidth, _fontSize)); + _pageNumPlaceholders.Add(placeholder); + + var c = App.Client; + var dateP = new KernedParagraph($"{_doc.Date:dddd, d. MMMM yyyy}", _fontSize).SetFont(_doc.NF); + var centerP = new KernedParagraph(_doc.DocumentId ?? "", _fontSize).SetFont(_doc.IF); + var pageNumP = new KernedParagraph(_fontSize).Add(new Image(placeholder)).SetFont(_doc.NF); + + if (pageNum == 1) { + // first page + canvas.ShowTextAligned(dateP, leftX1, y1, TextAlignment.LEFT, VerticalAlignment.BOTTOM); + canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, y1, TextAlignment.CENTER, VerticalAlignment.BOTTOM); + canvas.ShowTextAligned(pageNumP, rightX1, y1, TextAlignment.RIGHT, VerticalAlignment.BOTTOM); + + var footer = _doc.GetFooter(); + using var footerCanvas = new Canvas(page, pageSize); + footerCanvas.Add(new Table(1).AddCell(new Cell().Add(footer).SetBorder(Border.NO_BORDER).SetPaddingsMM(1, 0, 0, 0)) + .SetFixedPositionMM(25, 0, 165).SetHeightMM(25) + .SetFont(_doc.NF).SetFontSize(10) + .SetTextAlignment(TextAlignment.CENTER) + .SetBorder(Border.NO_BORDER) + .SetBorderTop(new SolidBorder(BorderThickness))); + } else if (_doc.IsDoublePaged && (pageNum % 2 == 0)) { + // left page (= swapped) + canvas.ShowTextAligned(pageNumP, leftX2, y2, TextAlignment.LEFT, VerticalAlignment.TOP); + canvas.ShowTextAligned(centerP, (leftX2 + rightX2) / 2, y2, TextAlignment.CENTER, VerticalAlignment.TOP); + canvas.ShowTextAligned(dateP, rightX2, y2, TextAlignment.RIGHT, VerticalAlignment.TOP); + } else { + // right page + canvas.ShowTextAligned(dateP, leftX1, y2, TextAlignment.LEFT, VerticalAlignment.TOP); + canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, y2, TextAlignment.CENTER, VerticalAlignment.TOP); + canvas.ShowTextAligned(pageNumP, rightX1, y2, TextAlignment.RIGHT, VerticalAlignment.TOP); + } + + if (_doc.ShowFoldMarks) { + var m1 = pageSize.GetTop() - 105 * PtInMM; + var m2 = pageSize.GetTop() - 148.5 * PtInMM; + var m3 = pageSize.GetTop() - 210 * PtInMM; + pdfCanvas.SetLineWidth(BorderThickness); + + pdfCanvas.MoveTo(0, m1); + pdfCanvas.LineTo(10 * PtInMM, m1); + pdfCanvas.MoveTo(pageSize.GetRight(), m1); + pdfCanvas.LineTo(pageSize.GetRight() - 10 * PtInMM, m1); + + pdfCanvas.MoveTo(0, m2); + pdfCanvas.LineTo(7 * PtInMM, m2); + pdfCanvas.MoveTo(pageSize.GetRight(), m2); + pdfCanvas.LineTo(pageSize.GetRight() - 7 * PtInMM, m2); + + pdfCanvas.MoveTo(0, m3); + pdfCanvas.LineTo(10 * PtInMM, m3); + pdfCanvas.MoveTo(pageSize.GetRight(), m3); + pdfCanvas.LineTo(pageSize.GetRight() - 10 * PtInMM, m3); + + pdfCanvas.ClosePathStroke(); + } + + if (NumberOfPages > 0) { + // FillPlaceholders() was already called + FillPlaceholder(pdf, pageNum); + } + } + + private void OnDocumentClose(PdfDocumentEvent evt) { + var pdf = evt.GetDocument(); + var page = evt.GetPage(); + var pageNum = pdf.GetPageNumber(page); + + // ... + + FillPlaceholders(pdf); + } + + private void FillPlaceholders(PdfDocument pdf) { + NumberOfPages = pdf.GetNumberOfPages(); + for (int i = 0; i < _pageNumPlaceholders.Count; i++) + FillPlaceholder(pdf, i + 1); + } + + private void FillPlaceholder(PdfDocument pdf, int pageNum) { + var placeholder = _pageNumPlaceholders[pageNum - 1]; + using var canvas = new Canvas(placeholder, pdf); + if (_doc.IsDoublePaged && (pageNum % 2 == 0)) { + // left page (= swapped) + var p = new KernedParagraph(_fontSize).SetFont(_doc.NF); + if (_doc.IsPreview) p.Add(_doc.Bold("(vorläufig) ")); + p.Add(_doc.Normal($"Seite {pageNum:N0} von {NumberOfPages:N0} ")); + canvas.ShowTextAligned(p, 0, 0, TextAlignment.LEFT); + } else { + // right page + var p = new KernedParagraph(_fontSize).SetFont(_doc.NF) + .Add(_doc.Normal($"Seite {pageNum:N0} von {NumberOfPages:N0}")); + if (_doc.IsPreview) p.Add(_doc.Bold(" (vorläufig)")); + canvas.ShowTextAligned(p, _placeholderWidth, 0, TextAlignment.RIGHT); + } } } } diff --git a/Elwig/Documents/Document.cshtml b/Elwig/Documents/Document.cshtml deleted file mode 100644 index 814b4ee..0000000 --- a/Elwig/Documents/Document.cshtml +++ /dev/null @@ -1,28 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.Document - - - - @Model.Title - - - - - - @if (Model.IsDoublePaged) { - - } - - -
@Raw(Model.Header)
-
-
- @RenderBody() -
- - diff --git a/Elwig/Documents/Document.css b/Elwig/Documents/Document.css deleted file mode 100644 index 55933fa..0000000 --- a/Elwig/Documents/Document.css +++ /dev/null @@ -1,66 +0,0 @@ - -:root { - font-family: "Times New Roman", serif; - line-height: 1; - --border-thickness: 0.5pt; -} - -* { - box-sizing: border-box; -} - -body { - margin: 0; -} - -table { - width: 100%; - border-collapse: collapse; - table-layout: fixed; -} - -table td, -table th { - padding: 0.5mm 1mm; -} - -table th { - text-align: center; -} - -header { - height: 45mm; - padding: 10mm 0 0 0; - position: absolute; - top: -25mm; - left: 0; - right: 0; - text-align: center; - overflow: hidden; -} - -header .name { - font-size: 18pt; - margin-top: 8mm; - font-weight: bold; -} - -header .suffix { - font-size: 14pt; - font-weight: bold; -} - -header .type { - font-size: 12pt; - font-weight: normal; -} - -.hidden { - visibility: hidden; -} - -hr { - border: none; - border-top: var(--border-thickness) solid black; - margin: 5mm 0; -} diff --git a/Elwig/Documents/Extensions.cs b/Elwig/Documents/Extensions.cs new file mode 100644 index 0000000..9b9b9f5 --- /dev/null +++ b/Elwig/Documents/Extensions.cs @@ -0,0 +1,93 @@ +using iText.Kernel.Geom; +using iText.Layout; +using iText.Layout.Element; +using iText.Layout.Properties; + +namespace Elwig.Documents { + public static class Extensions { + + public static T SetFixedPositionMM(this BlockElement element, float left, float top, float width, Rectangle pageSize) where T : IElement { + element.SetVerticalAlignment(VerticalAlignment.TOP); + return element.SetFixedPosition(left * Document.PtInMM, pageSize.GetTop() - top * Document.PtInMM, width * Document.PtInMM); + } + + public static T SetFixedPositionMM(this BlockElement element, float left, float top, float width, float height, Rectangle pageSize) where T : IElement { + element.SetHeight(height * Document.PtInMM); + return element.SetFixedPosition(left * Document.PtInMM, pageSize.GetTop() - (top + height) * Document.PtInMM, width * Document.PtInMM); + } + + public static T SetFixedPositionMM(this ElementPropertyContainer element, float left, float bottom, float width) where T : IPropertyContainer { + return element.SetFixedPosition(left * Document.PtInMM, bottom * Document.PtInMM, width * Document.PtInMM); + } + + public static T SetHeightMM(this BlockElement element, float height) where T : IElement { + return element.SetHeight(height * Document.PtInMM); + } + + public static T SetPaddingTopMM(this BlockElement element, float top) where T : IElement { + return element.SetPaddingTop(top * Document.PtInMM); + } + + public static T SetPaddingRightMM(this BlockElement element, float right) where T : IElement { + return element.SetPaddingRight(right * Document.PtInMM); + } + + public static T SetPaddingBottomMM(this BlockElement element, float bottom) where T : IElement { + return element.SetPaddingBottom(bottom * Document.PtInMM); + } + + public static T SetPaddingLeftMM(this BlockElement element, float left) where T : IElement { + return element.SetPaddingLeft(left * Document.PtInMM); + } + + public static T SetPaddingsMM(this BlockElement element, float top, float right, float bottom, float left) where T : IElement { + return element.SetPaddings(top * Document.PtInMM, right * Document.PtInMM, bottom * Document.PtInMM, left * Document.PtInMM); + } + + public static T SetMarginTopMM(this BlockElement element, float top) where T : IElement { + return element.SetMarginTop(top * Document.PtInMM); + } + + public static T SetMarginRightMM(this BlockElement element, float right) where T : IElement { + return element.SetMarginRight(right * Document.PtInMM); + } + + public static T SetMarginBottomMM(this BlockElement element, float bottom) where T : IElement { + return element.SetMarginBottom(bottom * Document.PtInMM); + } + + public static T SetMarginLeftMM(this BlockElement element, float left) where T : IElement { + return element.SetMarginLeft(left * Document.PtInMM); + } + + public static T SetMarginsMM(this BlockElement element, float top, float right, float bottom, float left) where T : IElement { + return element.SetMargins(top * Document.PtInMM, right * Document.PtInMM, bottom * Document.PtInMM, left * Document.PtInMM); + } + + public static void SetTopMarginMM(this iText.Layout.Document element, float top) { + element.SetTopMargin(top * Document.PtInMM); + } + + public static void SetRightMarginMM(this iText.Layout.Document element, float right) { + element.SetRightMargin(right * Document.PtInMM); + } + + public static void SetBottomMarginMM(this iText.Layout.Document element, float bottom) { + element.SetBottomMargin(bottom * Document.PtInMM); + } + + public static void SetLeftMarginMM(this iText.Layout.Document element, float left) { + element.SetLeftMargin(left * Document.PtInMM); + } + + public static void SetMarginsMM(this iText.Layout.Document element, float top, float right, float bottom, float left) { + element.SetMargins(top * Document.PtInMM, right * Document.PtInMM, bottom * Document.PtInMM, left * Document.PtInMM); + } + + public static Table AddCells(this Table table, params Cell[] cells) { + foreach (var cell in cells) + table.AddCell(cell); + return table; + } + } +} diff --git a/Elwig/Documents/KernedParagraph.cs b/Elwig/Documents/KernedParagraph.cs new file mode 100644 index 0000000..3d4fffd --- /dev/null +++ b/Elwig/Documents/KernedParagraph.cs @@ -0,0 +1,82 @@ +using iText.IO.Font; +using iText.IO.Font.Otf; +using iText.Kernel.Font; +using iText.Layout.Element; +using iText.Layout.Properties; +using iText.Layout.Renderer; +using System; + +namespace Elwig.Documents { + public class KernedParagraph : Paragraph { + + public KernedParagraph(float fontSize) : + base() { + SetFontKerning(FontKerning.YES); + SetFixedLeading(fontSize); + SetFontSize(fontSize); + } + + public KernedParagraph(string text, float fontSize) : + base(text) { + SetFontKerning(FontKerning.YES); + SetFixedLeading(fontSize); + SetFontSize(fontSize); + } + + public KernedParagraph(Text text, float fontSize) : + base(text) { + SetFontKerning(FontKerning.YES); + SetFixedLeading(fontSize); + SetFontSize(fontSize); + } + + public override KernedParagraph Add(ILeafElement element) { + if (element is Text t) { + t.SetFontKerning(FontKerning.YES); + t.SetNextRenderer(new KerningTextRenderer(t)); + } + base.Add(element); + return this; + } + + public override Paragraph Add(IBlockElement element) { + base.Add(element); + return this; + } + + public class KerningTextRenderer(Text textElement) : TextRenderer(textElement) { + + public override IRenderer GetNextRenderer() { + return new KerningTextRenderer((Text)modelElement); + } + + public override void ApplyOtf() { + PdfFont font; + try { + font = GetPropertyAsFont(Property.FONT); + } catch (InvalidCastException) { + return; + } + if (strToBeConverted != null) { + SetProcessedGlyphLineAndFont(TextPreprocessingUtil.ReplaceSpecialWhitespaceGlyphs(font.CreateGlyphLine(strToBeConverted), font), font); + } + if (otfFeaturesApplied || text.GetStart() >= text.GetEnd()) { + return; + } + if (GetProperty(Property.FONT_KERNING, (FontKerning?)FontKerning.NO) == FontKerning.YES) { + ApplyKerning(font.GetFontProgram(), text); + } + otfFeaturesApplied = true; + } + + private static void ApplyKerning(FontProgram font, GlyphLine text) { + for (int i = 1; i < text.Size(); i++) { + var kerning = font.GetKerning(text.Get(i - 1), text.Get(i)); + if (kerning != 0) { + text.Set(i - 1, new Glyph(text.Get(i - 1), 0, 0, kerning, 0, 0)); + } + } + } + } + } +} diff --git a/Elwig/Documents/Letterhead.cs b/Elwig/Documents/Letterhead.cs index e839a83..db3cf7c 100644 --- a/Elwig/Documents/Letterhead.cs +++ b/Elwig/Documents/Letterhead.cs @@ -2,8 +2,9 @@ using Elwig.Models.Entities; namespace Elwig.Documents { public class Letterhead : BusinessDocument { - public Letterhead(Member m) : base($"Briefkopf {m.FullName}", m, true) { - Aside = ""; + public Letterhead(Member m) : + base($"Briefkopf {m.FullName}", m, true) { + Aside = null; } } } diff --git a/Elwig/Documents/Letterhead.cshtml b/Elwig/Documents/Letterhead.cshtml deleted file mode 100644 index ee938be..0000000 --- a/Elwig/Documents/Letterhead.cshtml +++ /dev/null @@ -1,9 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.Letterhead -@{ Layout = "BusinessDocument"; } - diff --git a/Elwig/Documents/MemberDataSheet.cs b/Elwig/Documents/MemberDataSheet.cs index c52c7a2..1793287 100644 --- a/Elwig/Documents/MemberDataSheet.cs +++ b/Elwig/Documents/MemberDataSheet.cs @@ -1,8 +1,14 @@ using Elwig.Helpers; using Elwig.Models.Entities; +using iText.Kernel.Colors; +using iText.Kernel.Pdf; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace Elwig.Documents { public class MemberDataSheet : BusinessDocument { @@ -11,13 +17,168 @@ namespace Elwig.Documents { public Season Season; public Dictionary MemberBuckets; - public IEnumerable ActiveAreaCommitments; + public List ActiveAreaCommitments; public MemberDataSheet(Member m, AppDbContext ctx) : base($"{Name} {m.AdministrativeName}", m) { DocumentId = $"{Name} {m.MgNr}"; Season = ctx.Seasons.ToList().MaxBy(s => s.Year) ?? throw new ArgumentException("invalid season"); MemberBuckets = ctx.GetMemberBuckets(Utils.CurrentYear, m.MgNr).GetAwaiter().GetResult(); - ActiveAreaCommitments = m.ActiveAreaCommitments(ctx); + ActiveAreaCommitments = [.. m.ActiveAreaCommitments(ctx)]; + } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(NewMemberData().SetMarginBottomMM(5)); + doc.Add(NewBucketTable(Season, MemberBuckets, includeDelivery: false)); + if (ActiveAreaCommitments.Count != 0) { + bool firstOnPage = false; + if (pdf.GetNumberOfPages() == 1) { + doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + firstOnPage = true; + } + doc.Add(new KernedParagraph(12).Add(Bold($"Flächenbindungen per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 12, 0)); + doc.Add(NewAreaComTable()); + } + } + + protected Cell NewDataHdr(string title, int colspan) { + return NewTd(title, 10, colspan: colspan, bold: true, italic: true, center: true, borderTop: true) + .SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)); + } + + protected Cell NewDataTh(string text, float fontSize = 10, int colspan = 1) { + return NewTd(text, fontSize, colspan: colspan, italic: true) + .SetPaddingRightMM(0); + } + + protected Table NewMemberData() { + var tbl = new Table(ColsMM(30.0, 51.5, 20.0, 12.0, 18.0, 31.5)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetBorder(new SolidBorder(BorderThickness)); + + tbl.AddCell(NewDataHdr("Persönliche Daten", 6)); + if (Member.IsJuridicalPerson) { + tbl + .AddCell(NewDataTh("Name", 8, colspan: 3)) + .AddCell(NewDataTh("Zu Handen", 8, colspan: 3)) + .AddCell(NewTd(Member.Name, 12, colspan: 3)) + .AddCell(NewTd(Member.ForTheAttentionOf, 12, colspan: 3)); + } else { + tbl + .AddCell(NewDataTh("Titel (vorangestellt)", 8)) + .AddCell(NewDataTh("Vorname", 8)) + .AddCell(NewDataTh("Nachname", 8, colspan: 3)) + .AddCell(NewDataTh("Titel (nachgestellt)", 8)) + .AddCell(NewTd(Member.Prefix, 12)) + .AddCell(NewTd($"{Member.GivenName} {Member.MiddleName}", 12)) + .AddCell(NewTd(Member.Name, 12, colspan: 3)) + .AddCell(NewTd(Member.Suffix, 12)); + } + + tbl + .AddCell(NewDataTh("Mitglieds-Nr.:")).AddCell(NewTd($"{Member.MgNr}")) + .AddCell(NewDataTh(Member.IsJuridicalPerson ? "Gründungsjahr/-tag:" : "Geburtsjahr/-tag:", colspan: 2)) + .AddCell(NewTd(string.Join('.', Member.Birthday?.Split('-')?.Reverse() ?? []), colspan: 2)) + .AddCell(NewDataTh("Adresse:")).AddCell(NewTd(Member.Address, colspan: 5)) + .AddCell(NewDataTh("PLZ/Ort:")) + .AddCell(NewTd($"{Member.PostalDest.AtPlz?.Plz} {Member.PostalDest.AtPlz?.Dest} ({Member.PostalDest.AtPlz?.Ort.Name})", colspan: 5)) + .AddCell(NewDataHdr("Rechnungsadresse (optional)", colspan: 6)) + .AddCell(NewDataTh("Name:")).AddCell(NewTd(Member.BillingAddress?.FullName, colspan: 5)) + .AddCell(NewDataTh("Adresse:")).AddCell(NewTd(Member.BillingAddress?.Address, colspan: 5)) + .AddCell(NewDataTh("PLZ/Ort:")) + .AddCell(NewTd(Member.BillingAddress != null ? $"{Member.BillingAddress.PostalDest.AtPlz?.Plz} {Member.BillingAddress.PostalDest.AtPlz?.Dest} ({Member.BillingAddress.PostalDest.AtPlz?.Ort.Name})" : "", colspan: 5)); + + tbl.AddCell(NewDataHdr("Kontaktdaten", colspan: 3)) + .AddCell(NewDataHdr("Bankverbindung", colspan: 3).SetBorderLeft(new SolidBorder(BorderThickness))); + List subTbl1 = [ + .. Member.EmailAddresses.Select(a => new[] { "E-Mail-Adresse", a.Address }), + .. Member.TelephoneNumbers.Select(n => new[] { Utils.PhoneNrTypeToString(n.Type), n.Number, n.Comment }), + ["Tel.-Nr./E-Mail-Adr.", null], + ]; + List subTbl2 = [ + ["IBAN", Member.Iban != null ? Utils.FormatIban(Member.Iban) : null], + ["BIC", Member.Bic], + ]; + for (int i = 0; i < Math.Max(subTbl1.Count, subTbl2.Count); i++) { + tbl.AddCell(NewDataTh(i < subTbl1.Count ? subTbl1[i][0] + ":" : "")); + if (i < subTbl1.Count && subTbl1[i].Length >= 3 && subTbl1[i][2] != null) { + tbl.AddCell(NewTd(subTbl1[i][1])).AddCell(NewTd($"({subTbl1[i][2]})")); + } else { + tbl.AddCell(NewTd(i < subTbl1.Count ? subTbl1[i][1] : "", colspan: 2)); + } + tbl.AddCell(NewDataTh(i < subTbl2.Count ? subTbl2[i][0] + ":" : "").SetBorderLeft(new SolidBorder(BorderThickness))) + .AddCell(NewTd(i < subTbl2.Count ? subTbl2[i][1] : "", colspan: 2)); + } + + tbl.AddCell(NewDataHdr("Betrieb", colspan: 6)) + .AddCell(NewDataTh("Betriebs-Nr.:")).AddCell(NewTd(Member.LfbisNr)) + .AddCell(NewDataTh("UID:", colspan: 2)).AddCell(NewTd(Member.UstIdNr, colspan: 2)) + .AddCell(NewDataTh("Stammgemeinde:")).AddCell(NewTd(Member.DefaultKg?.Name)) + .AddCell(NewDataTh("Buchführend:", colspan: 2)).AddCell(NewTd(new KernedParagraph(Member.IsBuchführend ? "Ja " : "Nein ", 10) + .Add(Normal($"({(Member.IsBuchführend ? Season.VatNormal : Season.VatFlatrate) * 100:N0}% USt.)", 8)), colspan: 2)) + .AddCell(NewDataTh("(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)", 8, colspan: 2)) + .AddCell(NewDataTh("Bio:", colspan: 2)).AddCell(NewTd(Member.IsOrganic ? "Ja" : "Nein", colspan: 2)) + .AddCell(NewDataHdr("Genossenschaft", colspan: 6)) + .AddCell(NewDataTh("Status:")).AddCell(NewTd(new KernedParagraph(Member.IsActive ? "Aktiv " : "Nicht aktiv ", 10) + .Add(Normal("(" + (Member.ExitDate != null ? $"{Member.EntryDate:dd.MM.yyyy}\u2013{Member.ExitDate:dd.MM.yyyy}" : $"seit {Member.EntryDate:dd.MM.yyyy}") + ")", 8)))) + .AddCell(NewDataTh("Geschäftsanteile:", colspan: 2)).AddCell(NewTd($"{Member.BusinessShares:N0}", colspan: 2)) + .AddCell(NewDataTh("Stamm-Zweigstelle:")).AddCell(NewTd(Member.Branch?.Name)) + .AddCell(NewDataTh("Volllieferant:", colspan: 2)).AddCell(NewTd(Member.IsVollLieferant ? "Ja" : "Nein", colspan: 2)) + .AddCell(NewDataTh("Zusendungen per\u2026")).AddCell(NewTd(new KernedParagraph(10) + .Add(Italic("Post:")).Add(Normal(Member.ContactViaPost ? " Ja \u2013 " : " Nein \u2013 ")) + .Add(Italic("E-Mail:")).Add(Normal(Member.ContactViaEmail ? " Ja" : " Nein")))) + .AddCell(NewDataTh("Funktionär:", colspan: 2)).AddCell(NewTd(Member.IsFunktionär ? "Ja" : "Nein", colspan: 2)); + + return tbl; + } + + protected Table NewAreaComTable() { + var areaComs = ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new { + Type = group.Key, + AreaComs = group.OrderBy(c => c.Kg.AtKg.Name).ToList(), + Size = group.Sum(c => c.Area) + }).OrderByDescending(a => a.Size).ToList(); + + var tbl = new Table(ColsMM(40, 30, 35, 15, 25, 20), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetBorder(Border.NO_BORDER) + .SetFontSize(10); + + tbl.AddHeaderCell(NewTh("Katastralgemeinde", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Ried", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Parzelle(n)", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Fläche")) + .AddHeaderCell(NewTh("Bewirt.", rowspan: 2)) + .AddHeaderCell(NewTh("Laufzeit", rowspan: 2)) + .AddHeaderCell(NewTh("[m²]")); + + var lastContract = ""; + foreach (var contractType in areaComs) { + tbl.AddCell(NewCell(new KernedParagraph(10).Add(BoldItalic($"{contractType.Type.WineVar.Name} {(contractType.Type.WineAttr != null ? "(" + contractType.Type.WineAttr + ")" : "")}")), colspan: 3) + .SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER)) + .AddCell(NewCell(new KernedParagraph(10).Add(Bold($"{contractType.Size:N0}")).SetTextAlignment(TextAlignment.RIGHT)) + .SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER)) + .AddCell(NewCell(colspan: 2) + .SetBorderTop(contractType.Type.DisplayName != lastContract && lastContract != "" ? new SolidBorder(BorderThickness) : Border.NO_BORDER)); + + foreach (var areaCom in contractType.AreaComs) { + tbl.AddCell(NewTd(new KernedParagraph(10).Add(Normal($"{areaCom.Kg.AtKg.Name} ")).Add(Normal($"({areaCom.Kg.AtKg.KgNr:00000})", 8)))) + .AddCell(NewTd(areaCom.Rd?.Name)) + .AddCell(NewTd(Regex.Replace(areaCom.GstNr.Replace(",", ", ").Replace("-", "\u2013"), @"\s+", " "), 10)) + .AddCell(NewTd($"{areaCom.Area:N0}", right: true)) + .AddCell(NewTd(areaCom.WineCult?.Name, center: true)) + .AddCell(NewTd(areaCom.YearTo == null ? (areaCom.YearFrom == null ? "unbefristet" : $"ab {areaCom.YearFrom}") : (areaCom.YearFrom == null ? $"bis {areaCom.YearTo}" : $"{areaCom.YearFrom}–{areaCom.YearTo}"), center: true)); + lastContract = contractType.Type.DisplayName; + } + } + + tbl.AddCell(NewTd("Gesamt:", 12, colspan: 2, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); + tbl.AddCell(NewTd($"{ActiveAreaCommitments.Sum(a => a.Area):N0}", 12, colspan: 2, right: true, bold: true, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); + tbl.AddCell(NewTd(colspan: 2, borderTop: true).SetPaddingsMM(1, 1, 1, 1)); + + return tbl; } } } diff --git a/Elwig/Documents/MemberDataSheet.cshtml b/Elwig/Documents/MemberDataSheet.cshtml deleted file mode 100644 index 3adcb63..0000000 --- a/Elwig/Documents/MemberDataSheet.cshtml +++ /dev/null @@ -1,221 +0,0 @@ -@using RazorLight -@using Elwig.Helpers -@inherits TemplatePage -@model Elwig.Documents.MemberDataSheet -@{ Layout = "BusinessDocument"; } - -
-

@Model.Title

- - - - - - - - - - - - - @if (Model.Member.IsJuridicalPerson) { - - - } else { - - - - - } - - - @if (Model.Member.IsJuridicalPerson) { - - - } else { - - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @{ - List subTbl1 = new(); - subTbl1.AddRange(Model.Member.EmailAddresses.Select(a => new[] { "E-Mail-Adresse", a.Address })); - subTbl1.AddRange(Model.Member.TelephoneNumbers.Select(n => new[] { Utils.PhoneNrTypeToString(n.Type), n.Number, n.Comment })); - subTbl1.Add(new[] { "Tel.-Nr./E-Mail-Adr.", null }); - - List subTbl2 = new(); - subTbl2.Add(new[] { "IBAN", Model.Member.Iban != null ? Elwig.Helpers.Utils.FormatIban(Model.Member.Iban) : null }); - subTbl2.Add(new[] { "BIC", Model.Member.Bic }); - } - @for (int i = 0; i < Math.Max(subTbl1.Count, subTbl2.Count); i++) { - - - @if (i < subTbl1.Count && subTbl1[i].Length >= 3 && subTbl1[i][2] != null) { - - - } else { - - } - - - - - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Persönliche Daten
NameZu HandenTitel (vorangestellt)VornameNachnameTitel (nachgestellt)
@Model.Member.Name@Model.Member.ForTheAttentionOf@Model.Member.Prefix@Model.Member.GivenName @Model.Member.MiddleName@Model.Member.Name@Model.Member.Suffix
Mitglieds-Nr.:@Model.Member.MgNr@(Model.Member.IsJuridicalPerson ? "Gründungsjahr/-tag" : "Geburtsjahr/-tag"):@(string.Join('.', Model.Member.Birthday?.Split('-')?.Reverse() ?? Array.Empty()))
Adresse:@Model.Member.Address
PLZ/Ort: - @Model.Member.PostalDest.AtPlz?.Plz - @Model.Member.PostalDest.AtPlz?.Dest - (@Model.Member.PostalDest.AtPlz?.Ort.Name) -
Rechnungsadresse (optional)
Name:@Model.Member.BillingAddress?.FullName
Adresse:@Model.Member.BillingAddress?.Address
PLZ/Ort: - @if (Model.Member.BillingAddress != null) { - @Model.Member.BillingAddress.PostalDest.AtPlz?.Plz - @(" ")@Model.Member.BillingAddress.PostalDest.AtPlz?.Dest - @(" (")@Model.Member.BillingAddress.PostalDest.AtPlz?.Ort.Name@(")") - } -
KontaktdatenBankverbindung
@(i < subTbl1.Count ? subTbl1[i][0] + ":" : "")@subTbl1[i][1](@subTbl1[i][2])@(i < subTbl1.Count ? subTbl1[i][1] : "")@(i < subTbl2.Count ? subTbl2[i][0] + ":" : "")@(i < subTbl2.Count ? subTbl2[i][1] : "")
Betrieb
Betriebs-Nr.:@Model.Member.LfbisNrUID:@Model.Member.UstIdNr
Stammgemeinde:@Model.Member.DefaultKg?.NameBuchführend:@(Model.Member.IsBuchführend ? "Ja" : "Nein") (@((Model.Member.IsBuchführend ? Model.Season.VatNormal : Model.Season.VatFlatrate) * 100)% USt.)
(Katastralgemeinde mit dem größten Anteil an Weinbauflächen)Bio:@(Model.Member.IsOrganic ? "Ja" : "Nein")
Genossenschaft
Status: - @(Model.Member.IsActive ? "Aktiv" : "Nicht aktiv") - - (@(Model.Member.ExitDate != null ? - $"{Model.Member.EntryDate:dd.MM.yyyy}–{Model.Member.ExitDate:dd.MM.yyyy}" : - $"seit {Model.Member.EntryDate:dd.MM.yyyy}")) - - Geschäftsanteile:@Model.Member.BusinessShares
Stamm-Zweigstelle:@Model.Member.Branch?.NameVolllierferant:@(Model.Member.IsVollLieferant ? "Ja" : "Nein")
Zusendungen via... - Post: @(Model.Member.ContactViaPost ? "Ja" : "Nein") – - E-Mail: @(Model.Member.ContactViaEmail ? "Ja" : "Nein") - Funktionär:@(Model.Member.IsFunktionär ? "Ja" : "Nein")
- - @Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includeDelivery: false)) - - @{ - var areaComs = Model.ActiveAreaCommitments.GroupBy(a => a.AreaComType).Select(group => new { - AreaComType = group.Key, - AreaComs = group.OrderBy(c => c.Kg.AtKg.Name), - Size = group.Sum(c => c.Area) - }).OrderByDescending(a => a.Size).ToList(); - var lastContract = ""; - } - - @if (areaComs.Count != 0) { -

Flächenbindungen per @($"{Model.Date:dd.MM.yyyy}")

- - - - - - - - - - - - - - - - - - - - - - - - @foreach (var contractType in areaComs) { - - - - - - @foreach (var areaCom in contractType.AreaComs) { - - - - - - - - - lastContract = contractType.AreaComType.DisplayName; - } - } - - - - - - -
KatastralgemeindeRiedParzelle(n)FlächeBewirt.Laufzeit
[m²]
- @($"{contractType.AreaComType.WineVar.Name} {(contractType.AreaComType.WineAttr != null ? "(" + contractType.AreaComType.WineAttr + ")" : "")}") - @($"{contractType.Size:N0}")
@areaCom.Kg.AtKg.Name (@($"{areaCom.Kg.AtKg.KgNr:00000}"))@areaCom.Rd?.Name@areaCom.GstNr.Replace(",", ", ").Replace("-", "–")@($"{areaCom.Area:N0}")@areaCom.WineCult?.Name@(areaCom.YearTo == null ? (areaCom.YearFrom == null ? "unbefristet" : $"ab {areaCom.YearFrom}") : (areaCom.YearFrom == null ? $"bis {areaCom.YearTo}" : $"{areaCom.YearFrom}–{areaCom.YearTo}"))
Gesamt:@($"{Model.ActiveAreaCommitments.Sum(a => a.Area):N0}")
- } -
\ No newline at end of file diff --git a/Elwig/Documents/MemberDataSheet.css b/Elwig/Documents/MemberDataSheet.css deleted file mode 100644 index 59f21b5..0000000 --- a/Elwig/Documents/MemberDataSheet.css +++ /dev/null @@ -1,25 +0,0 @@ - -h2 { - margin-top: 0; - margin-bottom: 0.5em !important; -} - -table.member { - margin-bottom: 5mm; -} - -table.area-commitements { - margin-top: 0; -} - -table.area-commitements td { - vertical-align: top; -} - -table.area-commitements td.text { - white-space: normal; -} - -table.area-commitements tr.sum { - font-size: 12pt; -} diff --git a/Elwig/Documents/MemberList.cs b/Elwig/Documents/MemberList.cs index 942d077..b490364 100644 --- a/Elwig/Documents/MemberList.cs +++ b/Elwig/Documents/MemberList.cs @@ -1,4 +1,10 @@ using Elwig.Models.Dtos; +using iText.Kernel.Colors; +using iText.Kernel.Pdf; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; +using System; using System.Collections.Generic; using System.Linq; @@ -8,14 +14,15 @@ namespace Elwig.Documents { public new static string Name => "Mitgliederliste"; public string Filter; - public IEnumerable Members; + public List Members; public string[] AreaComFilters; public bool FilterAreaComs => AreaComFilters.Length > 0; - public MemberList(string filter, IEnumerable members) : base(Name) { + public MemberList(string filter, IEnumerable members) : + base(Name) { Filter = filter; - Members = members; + Members = [.. members]; AreaComFilters = [..members .SelectMany(m => m.AreaCommitmentsFiltered) .Select(c => c.VtrgId) @@ -26,5 +33,79 @@ namespace Elwig.Documents { public MemberList(string filter, MemberListData data) : this(filter, data.Rows) { } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(new KernedParagraph(Name, 24) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 2, 0)); + doc.Add(new KernedParagraph(Filter, 14) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 2, 0)); + doc.Add(NewMemberTable(Members)); + } + + protected Table NewMemberTable(List members) { + var tbl = new Table(AreaComFilters.Length > 1 ? ColsMM(8, 38, 36, 8, 18, 12, 5, 16, 12, 12) : ColsMM(8, 42, 40, 8, 20, 12, 5, 18, 12), true) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + var headerSpan = FilterAreaComs ? 3 : 2; + tbl.AddHeaderCell(NewTh("Nr.", rowspan: headerSpan)) + .AddHeaderCell(NewTh("Name", rowspan: headerSpan, left: true)) + .AddHeaderCell(NewTh("Adresse", rowspan: headerSpan, left: true)) + .AddHeaderCell(NewTh("PLZ", rowspan: headerSpan)) + .AddHeaderCell(NewTh("Ort", rowspan: headerSpan, left: true)) + .AddHeaderCell(NewTh("Betr.-Nr.", rowspan: headerSpan)) + .AddHeaderCell(NewTh("GA", rowspan: headerSpan).SetPaddingLeft(0).SetPaddingRight(0)) + .AddHeaderCell(NewTh("Stamm-KG", rowspan: headerSpan, left: true)) + .AddHeaderCell(NewTh("Geb. Fl.", colspan: FilterAreaComs ? AreaComFilters.Length : 1)); + if (FilterAreaComs) { + foreach (var vtrgId in AreaComFilters) { + tbl.AddHeaderCell(NewTh(vtrgId)); + } + } + for (int i = 0; i < (FilterAreaComs ? AreaComFilters.Length : 1); i++) { + tbl.AddHeaderCell(NewTh("[m²]")); + } + + string? lastBranch = members.Select(m => m.Branch).Distinct().Count() == 1 ? null : ""; + foreach (var m in members) { + if (lastBranch != null && m.Branch != lastBranch) { + tbl.AddCell(NewCell(colspan: 8 + Math.Max(AreaComFilters.Length, 1)).SetHeightMM(5).SetKeepWithNext(true)); + tbl.AddCell(NewCell(new KernedParagraph(m.Branch ?? "", 16).SetFont(BF), colspan: 8 + Math.Max(AreaComFilters.Length, 1)) + .SetPaddingsMM(1, 2, 1, 2) + .SetBorder(new SolidBorder(BorderThickness)) + .SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0))); + lastBranch = m.Branch; + } + + tbl.AddCell(NewTd($"{m.MgNr}", 8, rowspan: m.BillingName != null ? 2 : 1, right: true).SetVerticalAlignment(VerticalAlignment.TOP)) + .AddCell(NewTd($"{m.AdminName1} {m.Name2}", 8)) + .AddCell(NewTd(m.Address, 8)) + .AddCell(NewTd($"{m.Plz}", 8)) + .AddCell(NewTd(m.Locality, 6)) + .AddCell(NewTd(m.LfbisNr ?? "", 8)) + .AddCell(NewTd($"{m.BusinessShares:N0}", 8, right: true).SetPaddingLeft(0).SetPaddingRight(0)) + .AddCell(NewTd(m.DefaultKg ?? "", 6)); + if (AreaComFilters.Length > 0) { + foreach (var v in AreaComFilters) { + tbl.AddCell(NewTd($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}", 8, right: true)); + } + } else { + tbl.AddCell(NewTd($"{m.AreaCommitment:N0}", 8, right: true)); + } + + if (m.BillingName != null) { + tbl.AddCell(NewTd(m.BillingName, 8)) + .AddCell(NewTd(m.BillingAddress ?? "", 8)) + .AddCell(NewTd($"{m.BillingPlz}", 8)) + .AddCell(NewTd(m.BillingLocality ?? "", 6)) + .AddCell(NewTd(colspan: 3 + Math.Max(AreaComFilters.Length, 1))); + } + } + + return tbl; + } } } diff --git a/Elwig/Documents/MemberList.cshtml b/Elwig/Documents/MemberList.cshtml deleted file mode 100644 index 2405c3b..0000000 --- a/Elwig/Documents/MemberList.cshtml +++ /dev/null @@ -1,109 +0,0 @@ -@using RazorLight -@inherits TemplatePage -@model Elwig.Documents.MemberList -@{ Layout = "Document"; } - -
-

Mitgliederliste

-

@Model.Filter

- - - - @if (Model.AreaComFilters.Length > 1) { - - } else { - - } - @if (Model.AreaComFilters.Length > 1) { - - } else { - - } - - @if (Model.AreaComFilters.Length > 1) { - - } else { - - } - - - @if (Model.AreaComFilters.Length > 1) { - - } else { - - } - - @if (Model.AreaComFilters.Length > 1) { - - } - - - - @{ - var headerSpan = Model.FilterAreaComs ? 3 : 2; - } - - - - - - - - - - - @if (Model.FilterAreaComs) { - - @foreach (var vtrgId in Model.AreaComFilters) { - - } - - } - - @for (int i = 0; i < Math.Max(Model.AreaComFilters.Length, 1); i++) { - - } - - - - @{ - string? lastBranch = Model.Members.Select(m => m.Branch).Distinct().Count() == 1 ? null : ""; - } - @foreach (var m in Model.Members) { - if (lastBranch != null && m.Branch != lastBranch) { - - - - - lastBranch = m.Branch; - } - - - - - - - - - - @if (Model.AreaComFilters.Length > 0) { - foreach (var v in Model.AreaComFilters) { - - } - } else { - - } - - if (m.BillingName != null) { - - - - - - - - } - } - -
Nr.NameAdressePLZOrtBetr.-Nr.GAStamm-KGGeb. Fl.
@vtrgId
[m²]
@m.Branch
@m.MgNr@m.AdminName1 @m.Name2@m.Address@m.Plz@m.Locality@m.LfbisNr@m.BusinessShares@m.DefaultKg@($"{m.AreaCommitmentsFiltered.FirstOrDefault(c => c.VtrgId == v).Area:N0}")@($"{m.AreaCommitment:N0}")
@m.BillingName@m.BillingAddress@m.BillingPlz@m.BillingLocality
-
diff --git a/Elwig/Documents/MemberList.css b/Elwig/Documents/MemberList.css deleted file mode 100644 index aea8e98..0000000 --- a/Elwig/Documents/MemberList.css +++ /dev/null @@ -1,13 +0,0 @@ - -h1 { - text-align: center; - font-size: 24pt; - margin-top: 0; - margin-bottom: 2mm; -} - -h2 { - text-align: center; - font-size: 14pt; - margin-top: 2mm; -} diff --git a/Elwig/Documents/PaymentVariantSummary.cs b/Elwig/Documents/PaymentVariantSummary.cs index 738fd79..a994097 100644 --- a/Elwig/Documents/PaymentVariantSummary.cs +++ b/Elwig/Documents/PaymentVariantSummary.cs @@ -2,6 +2,12 @@ using Elwig.Helpers; using Elwig.Helpers.Billing; using Elwig.Models.Dtos; using Elwig.Models.Entities; +using iText.Kernel.Colors; +using iText.Kernel.Pdf; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; +using System; using System.Collections.Generic; using System.Linq; @@ -33,5 +39,258 @@ namespace Elwig.Documents { ModifierStat = AppDbContext.GetModifierStats(v.Year, v.AvNr).GetAwaiter().GetResult(); Modifiers = v.Season.Modifiers.ToDictionary(m => m.ModId); } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(new KernedParagraph($"{Name} Lese {Variant.Year}", 24) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 2, 0)); + doc.Add(new KernedParagraph(Variant.Name, 14) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginsMM(0, 0, 10, 0)); + doc.Add(NewVariantStatTable().SetMarginBottomMM(10)); + doc.Add(NewModifierStatTable()); + doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); + doc.Add(NewPriceTable()); + } + + protected Cell NewSectionHdr(string text, int colspan = 1, bool borderLeft = false) { + return NewTd(text, 10, colspan: colspan, bold: true, italic: true, center: true, borderTop: true) + .SetBackgroundColor(new DeviceRgb(0xe0, 0xe0, 0xe0)) + .SetPaddingsMM(0.5f, 1, 0.5f, 1) + .SetBorderLeft(borderLeft ? new SolidBorder(BorderThickness) : Border.NO_BORDER); + } + + protected Cell NewSectionTh(string? text = null, float fontSize = 10, int colspan = 1, bool borderTop = false, bool borderLeft = false, bool overflow = false) { + return NewTd(text, fontSize, colspan: colspan, italic: true, borderTop: borderTop, overflow: overflow) + .SetPaddingRightMM(0) + .SetBorderLeft(borderLeft ? new SolidBorder(BorderThickness) : Border.NO_BORDER); + } + + protected Table NewVariantStatTable() { + var tbl = new Table(ColsMM(20, 30, 4.5, 4.5, 23.5, 47.5, 15, 20)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetBorder(new SolidBorder(BorderThickness)); + + //var sum1 = Variant.DeliveryPartPayments.Sum(p => p.NetAmount); + //var sum2 = Variant.Credits.Sum(p => p.); //Variant.MemberPayments.Sum(p => p.Amount); + var deliveryModifiers = Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount); + var memberModifiers = Variant.Credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount); + var sum2 = Variant.Credits.Sum(p => p.NetAmount); + var sum1 = sum2 - deliveryModifiers - memberModifiers; + var payed = -Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m); + var netSum = Variant.Credits.Sum(p => p.NetAmount) - Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m); + var vat = Variant.Credits.Sum(p => p.VatAmount); + var grossSum = Variant.Credits.Sum(p => p.GrossAmount); + var totalMods = Variant.Credits.Sum(p => p.Modifiers ?? 0m); + var considered = -Variant.Credits.Sum(p => p.PrevModifiers ?? 0m); + var totalSum = Variant.Credits.Sum(p => p.Amount); + + var weiRows = Data.Rows.Where(r => r.QualityLevel == "Wein"); + var minWei = weiRows.Min(r => r.Ungeb.MinPrice); + var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice); + var quwRows = Data.Rows.Where(r => r.QualityLevel != "Wein"); + var minPrice = quwRows.Min(r => r.Ungeb.MinPrice); + var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice); + var gebRows = Data.Rows + .Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null) + .Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice); + var minGeb = gebRows.Min(); + var maxGeb = gebRows.Max(); + + tbl.AddCell(NewSectionHdr("Allgemein", colspan: 5)) + .AddCell(NewSectionHdr("Berücksichtigt", colspan: 3, borderLeft: true)) + + .AddCell(NewSectionTh("Name:")) + .AddCell(NewTd(Variant.Name, colspan: 4)) + .AddCell(NewSectionTh("Zu-/Abschläge bei Lieferungen:", colspan: 2, borderLeft: true)) + .AddCell(NewTd(BillingData.ConsiderDelieryModifiers ? "Ja" : "Nein", center: true)) + + .AddCell(NewSectionTh("Beschr.:")) + .AddCell(NewTd(Variant.Comment, colspan: 4)) + .AddCell(NewSectionTh("Pönalen bei Unterlieferungen (FB):", colspan: 2, borderLeft: true)) + .AddCell(NewTd(BillingData.ConsiderContractPenalties ? "Ja" : "Nein", center: true)) + + .AddCell(NewSectionTh("Rebel-Zuschl.:", overflow: true)) + .AddCell(NewTd($"{Utils.GetSign(BillingData.NetWeightModifier)}{Math.Abs(BillingData.NetWeightModifier) * 100:N2} % / {Utils.GetSign(BillingData.GrossWeightModifier)}{Math.Abs(BillingData.GrossWeightModifier) * 100:N2} %", colspan: 4, center: true)) + .AddCell(NewSectionTh("Strafen bei Unterlieferungen (GA):", colspan: 2, borderLeft: true)) + .AddCell(NewTd(BillingData.ConsiderTotalPenalty ? "Ja" : "Nein", center: true)) + + .AddCell(NewSectionTh("Datum/Überw.:", overflow: true)) + .AddCell(NewTd($"{Variant.Date:dd.MM.yyyy} / {Variant.TransferDate:dd.MM.yyyy}", colspan: 4, center: true)) + .AddCell(NewSectionTh("Automatische Nachzeichnung der GA:", colspan: 2, borderLeft: true)) + .AddCell(NewTd(BillingData.ConsiderAutoBusinessShares ? "Ja" : "Nein", center: true)) + + .AddCell(NewSectionTh("Berechnung:")) + .AddCell(NewTd($"{Variant.CalcTime:dd.MM.yyyy, HH:mm:ss}", colspan: 4, center: true)) + .AddCell(NewSectionTh("Benutzerdef. Zu-/Abschläge pro Mitglied:", colspan: 2, borderLeft: true)) + .AddCell(NewTd(BillingData.ConsiderCustomModifiers ? "Ja" : "Nein", center: true)) + + .AddCell(NewSectionHdr("Beträge", colspan: 5)) + .AddCell(NewSectionHdr("Statistik", colspan: 3, borderLeft: true)) + + .AddCell(NewSectionTh("Zwischensumme:", colspan: 2)) + .AddCell(NewTd()) + .AddCell(NewTd(CurrencySymbol)) + .AddCell(NewTd($"{sum1:N2}", right: true)) + .AddCell(NewSectionTh("Lieferanten:", borderLeft: true)) + .AddCell(NewTd($"{MemberNum:N0}", colspan: 2, right: true)) + + .AddCell(NewSectionTh("Zu-/Abschläge (Mitglieder):", colspan: 2)) + .AddCell(NewTd(Utils.GetSign(memberModifiers), right: true)) + .AddCell(NewTd(CurrencySymbol)) + .AddCell(NewTd($"{Math.Abs(memberModifiers):N2}", right: true)) + .AddCell(NewSectionTh("Lieferungen:", borderLeft: true)) + .AddCell(NewTd($"{DeliveryNum:N0}", colspan: 2, right: true)) + + .AddCell(NewSectionTh("Zu-/Abschläge (Lieferungen):", colspan: 2)) + .AddCell(NewTd(Utils.GetSign(deliveryModifiers), right: true)) + .AddCell(NewTd(CurrencySymbol)) + .AddCell(NewTd($"{Math.Abs(deliveryModifiers):N2}", right: true)) + .AddCell(NewSectionTh("Teillieferungen:", borderLeft: true)) + .AddCell(NewTd($"{DeliveryPartNum:N0}", colspan: 2, right: true)) + + .AddCell(NewSectionTh("Gesamtsumme:", colspan: 2)) + .AddCell(NewTd(borderTop: true)) + .AddCell(NewTd(CurrencySymbol, borderTop: true)) + .AddCell(NewTd($"{sum2:N2}", right: true, borderTop: true)) + .AddCell(NewSectionTh(borderLeft: true)) + .AddCell(NewTd(colspan: 2)) + + .AddCell(NewSectionTh("Bisher ausgezahlt:", colspan: 2)) + .AddCell(NewTd(Utils.GetSign(payed), right: true)) + .AddCell(NewTd(CurrencySymbol)) + .AddCell(NewTd($"{Math.Abs(payed):N2}", right: true)) + .AddCell(NewSectionTh("Preis (abgewertet):", borderTop: true, borderLeft: true)) + .AddCell(NewTd((minWei != maxWei ? $"{minWei:N4}\u2013{maxWei:N4}" : $"{minWei:N4}") + $" {CurrencySymbol}/kg", colspan: 2, center: true, borderTop: true)) + + .AddCell(NewSectionTh("Nettosumme:", colspan: 2)) + .AddCell(NewTd(borderTop: true)) + .AddCell(NewTd(CurrencySymbol, borderTop: true)) + .AddCell(NewTd($"{netSum:N2}", right: true, borderTop: true)) + .AddCell(NewSectionTh("Preis (ungeb., nicht abgew.):", borderLeft: true)) + .AddCell(NewTd((minPrice != maxPrice ? $"{minPrice:N4}–{maxPrice:N4}" : $"{minPrice:N4}") + $" {CurrencySymbol}/kg", colspan: 2, center: true)) + + .AddCell(NewSectionTh("Mehrwertsteuer:", colspan: 2)) + .AddCell(NewTd(Utils.GetSign(vat), right: true)) + .AddCell(NewTd(CurrencySymbol)) + .AddCell(NewTd($"{Math.Abs(vat):N2}", right: true)) + .AddCell(NewSectionTh("Gebunden-Zuschlag:", borderLeft: true)) + .AddCell(NewTd(minGeb != maxGeb ? $"{minGeb:N4}\u2013{maxGeb:N4} {CurrencySymbol}/kg" : minGeb == 0 ? "-" : $"{minGeb:N4} {CurrencySymbol}/kg", colspan: 2, center: true)) + + .AddCell(NewSectionTh("Bruttosumme:", colspan: 2)) + .AddCell(NewTd(borderTop: true)) + .AddCell(NewTd(CurrencySymbol, borderTop: true)) + .AddCell(NewTd($"{grossSum:N2}", right: true, borderTop: true)) + .AddCell(NewSectionTh(borderLeft: true)) + .AddCell(NewTd(colspan: 2)) + + .AddCell(NewSectionTh("Abzüge (Strafen/Pönalen, GA, \u2026):", colspan: 2)) + .AddCell(NewTd(Utils.GetSign(totalMods))) + .AddCell(NewTd(CurrencySymbol)) + .AddCell(NewTd($"{Math.Abs(totalMods):N2}", right: true)) + .AddCell(NewSectionTh("Menge (ungebunden):", borderLeft: true, borderTop: true)) + .AddCell(NewTd($"{Data.Rows.Sum(r => r.Ungeb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)) + + .AddCell(NewSectionTh("Bereits berücksichtigte Abzüge:", colspan: 2)) + .AddCell(NewTd(Utils.GetSign(considered))) + .AddCell(NewTd(CurrencySymbol)) + .AddCell(NewTd($"{Math.Abs(considered):N2}", right: true)) + .AddCell(NewSectionTh("Menge (gebunden):", borderLeft: true)) + .AddCell(NewTd($"{Data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0} kg", colspan: 2, right: true)) + + .AddCell(NewSectionTh("Auszahlungsbetrag:", colspan: 2)) + .AddCell(NewTd(borderTop: true)) + .AddCell(NewTd(CurrencySymbol, borderTop: true)) + .AddCell(NewTd($"{totalSum:N2}", right: true, borderTop: true)) + .AddCell(NewSectionTh("Gesamtmenge:", borderLeft: true)) + .AddCell(NewTd($"{Data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0} kg", colspan: 2, right: true, borderTop: true)); + + return tbl; + } + + protected Table NewModifierStatTable() { + var tbl = new Table(ColsMM(35, 30, 25, 25, 25, 25)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetBorder(new SolidBorder(BorderThickness)); + + tbl.AddCell(NewSectionHdr("Statistik Zu-/Abschläge", colspan: 6)) + .AddCell(NewTh("Name", rowspan: 2)) + .AddCell(NewTh("Zu-/Abschlag", rowspan: 2)) + .AddCell(NewTh("Lieferungen")) + .AddCell(NewTh("Minimum")) + .AddCell(NewTh("Maximum")) + .AddCell(NewTh("Betrag")) + .AddCell(NewTh("[#]")) + .AddCell(NewTh($"[{CurrencySymbol}]")) + .AddCell(NewTh($"[{CurrencySymbol}]")) + .AddCell(NewTh($"[{CurrencySymbol}]")); + + foreach (var m in ModifierStat) { + var mod = Modifiers[m.ModId]; + tbl.AddCell(NewTd(mod.Name, italic: true)) + .AddCell(NewTd(mod.ValueStr, right: true)) + .AddCell(NewTd($"{m.Count:N0}", right: true)) + .AddCell(NewTd($"{m.Min:N2}", right: true)) + .AddCell(NewTd($"{m.Max:N2}", right: true)) + .AddCell(NewTd($"{m.Sum:N2}", right: true)); + } + + return tbl; + } + + protected Table NewPriceTable() { + var tbl = new Table(ColsMM(25, 19, 18, 15, 18, 15, 18, 15, 22)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE); + + tbl.AddHeaderCell(NewTh("Sorte/Attr./Bewirt.\nQualitätsstufe", rowspan: 2, left: true)) + .AddHeaderCell(NewTh("Gradation")) + .AddHeaderCell(NewTh("ungebunden", colspan: 2)) + .AddHeaderCell(NewTh("attributlos gebunden", colspan: 2)) + .AddHeaderCell(NewTh("gebunden", colspan: 2)) + .AddHeaderCell(NewTh("Gesamt")) + .AddHeaderCell(NewTh(true ? "[°Oe]" : "[°KMW]")) + .AddHeaderCell(NewTh("[kg]")) + .AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]")) + .AddHeaderCell(NewTh("[kg]")) + .AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]")) + .AddHeaderCell(NewTh("[kg]")) + .AddHeaderCell(NewTh($"[{CurrencySymbol}/kg]")) + .AddHeaderCell(NewTh($"[{CurrencySymbol}]")); + + string? lastHdr = null; + foreach (var row in Data.Rows) { + var hdr = $"{row.Variety}{(row.Attribute != null ? " / " : "")}{row.Attribute}{(row.Cultivation != null ? " / " : "")}{row.Cultivation}"; + if (lastHdr != hdr) { + var rows = Data.Rows + .Where(r => r.Variety == row.Variety && r.Attribute == row.Attribute && r.Cultivation == row.Cultivation) + .ToList(); + var border = lastHdr != null; + tbl.AddCell(NewTd(hdr, colspan: 2, bold: true, italic: true, borderTop: border)) + .AddCell(NewTd($"{rows.Sum(r => r.Ungeb.Weight):N0}", right: true, bold: true, borderTop: border)) + .AddCell(NewTd(borderTop: border)) + .AddCell(NewTd($"{rows.Sum(r => r.LowGeb.Weight):N0}", right: true, bold: true, borderTop: border)) + .AddCell(NewTd(borderTop: border)) + .AddCell(NewTd($"{rows.Sum(r => r.Geb.Weight):N0}", right: true, bold: true, borderTop: border)) + .AddCell(NewTd(borderTop: border)) + .AddCell(NewTd($"{rows.Sum(r => r.Amount):N2}", right: true, bold: true, borderTop: border)); + } + tbl.AddCell(NewTd(row.QualityLevel)) + .AddCell(NewTd($"{row.Oe:N0}", center: true)) + .AddCell(NewTd(row.Ungeb.Weight != 0 ? $"{row.Ungeb.Weight:N0}" : "-", right: true)) + .AddCell(NewTd(row.Ungeb.MaxPrice != null ? $"{row.Ungeb.MaxPrice:N4}" : "-", right: true)) + .AddCell(NewTd(row.LowGeb.Weight != 0 ? $"{row.LowGeb.Weight:N0}" : "-", right: true)) + .AddCell(NewTd(row.LowGeb.MaxPrice != null ? $"{row.LowGeb.MaxPrice:N4}" : "-", right: true)) + .AddCell(NewTd(row.Geb.Weight != 0 ? $"{row.Geb.Weight:N0}" : "-", right: true)) + .AddCell(NewTd(row.Geb.MaxPrice != null ? $"{row.Geb.MaxPrice:N4}" : "-", right: true)) + .AddCell(NewTd($"{row.Amount:N2}", right: true)); + lastHdr = hdr; + } + + return tbl; + } } } diff --git a/Elwig/Documents/PaymentVariantSummary.cshtml b/Elwig/Documents/PaymentVariantSummary.cshtml deleted file mode 100644 index eddb229..0000000 --- a/Elwig/Documents/PaymentVariantSummary.cshtml +++ /dev/null @@ -1,288 +0,0 @@ -@using RazorLight -@using Elwig.Helpers -@inherits TemplatePage -@model Elwig.Documents.PaymentVariantSummary -@{ Layout = "Document"; } - -
-

Auszahlungsvariante Lese @Model.Variant.Year

-

@Model.Variant.Name

- - - - - - - - - - - @{ - //var sum1 = Model.Variant.DeliveryPartPayments.Sum(p => p.NetAmount); - //var sum2 = Model.Variant.Credits.Sum(p => p.); //Model.Variant.MemberPayments.Sum(p => p.Amount); - var deliveryModifiers = Model.Variant.DeliveryPartPayments.Sum(p => p.Amount - p.NetAmount); - var memberModifiers = Model.Variant.Credits.Sum(c => c.Payment.Amount - c.Payment.NetAmount); - var sum2 = Model.Variant.Credits.Sum(p => p.NetAmount); - var sum1 = sum2 - deliveryModifiers - memberModifiers; - var payed = -Model.Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m); - var netSum = Model.Variant.Credits.Sum(p => p.NetAmount) - Model.Variant.Credits.Sum(p => p.PrevNetAmount ?? 0m); - var vat = Model.Variant.Credits.Sum(p => p.VatAmount); - var grossSum = Model.Variant.Credits.Sum(p => p.GrossAmount); - var totalMods = Model.Variant.Credits.Sum(p => p.Modifiers ?? 0m); - var considered = -Model.Variant.Credits.Sum(p => p.PrevModifiers ?? 0m); - var totalSum = Model.Variant.Credits.Sum(p => p.Amount); - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @{ - var weiRows = Model.Data.Rows.Where(r => r.QualityLevel == "Wein"); - var minWei = weiRows.Min(r => r.Ungeb.MinPrice); - var maxWei = weiRows.Max(r => r.Ungeb.MaxPrice); - } - - - - - - - - @{ - var quwRows = Model.Data.Rows.Where(r => r.QualityLevel != "Wein"); - var minPrice = quwRows.Min(r => r.Ungeb.MinPrice); - var maxPrice = quwRows.Max(r => r.Ungeb.MaxPrice); - } - - - - - - - - @{ - var gebRows = Model.Data.Rows - .Where(r => r.Geb.MaxPrice != null && r.Ungeb.MinPrice != null) - .Select(r => r.Geb.MaxPrice - r.Ungeb.MinPrice); - var minGeb = gebRows.Min(); - var maxGeb = gebRows.Max(); - } - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AllgemeinBerücksichtigt
Name:@Model.Variant.NameZu-/Abschläge bei Lieferungen:@(Model.BillingData.ConsiderDelieryModifiers ? "Ja" : "Nein")
Beschr.:@Model.Variant.CommentPönalen bei Unterlieferungen (FB):@(Model.BillingData.ConsiderContractPenalties ? "Ja" : "Nein")
Rebel-Zuschl.: - @($"{Utils.GetSign(Model.BillingData.NetWeightModifier)}{Math.Abs(Model.BillingData.NetWeightModifier) * 100:N2}") % / - @($"{Utils.GetSign(Model.BillingData.GrossWeightModifier)}{Math.Abs(Model.BillingData.GrossWeightModifier) * 100:N2}") % - Strafen bei Unterlieferungen (GA):@(Model.BillingData.ConsiderTotalPenalty ? "Ja" : "Nein")
Datum/Überw.: - @($"{Model.Variant.Date:dd.MM.yyyy}") / - @($"{Model.Variant.TransferDate:dd.MM.yyyy}") - Automatische Nachzeichnung der GA:@(Model.BillingData.ConsiderAutoBusinessShares ? "Ja" : "Nein")
Berechnung:@($"{Model.Variant.CalcTime:dd.MM.yyyy, HH:mm:ss}")Benutzerdef. Zu-/Abschläge pro Mitglied:@(Model.BillingData.ConsiderCustomModifiers ? "Ja" : "Nein")
BeträgeStatistik
Zwischensumme:@Model.CurrencySymbol@($"{sum1:N2}")Lieferanten:@($"{Model.MemberNum:N0}")
Zu-/Abschläge (Mitglieder):@Utils.GetSign(memberModifiers)@Model.CurrencySymbol@($"{Math.Abs(memberModifiers):N2}")Lieferungen:@($"{Model.DeliveryNum:N0}")
Zu-/Abschläge (Lieferungen):@Utils.GetSign(deliveryModifiers)@Model.CurrencySymbol@($"{Math.Abs(deliveryModifiers):N2}")Teillieferungen:@($"{Model.DeliveryPartNum:N0}")
Gesamtsumme:@Model.CurrencySymbol@($"{sum2:N2}")
Bisher ausgezahlt:@Utils.GetSign(payed)@Model.CurrencySymbol@($"{Math.Abs(payed):N2}")Preis (abgewertet):@(minWei != maxWei ? $"{minWei:N4}–{maxWei:N4}" : $"{minWei:N4}") @Model.CurrencySymbol/kg
Nettosumme:@Model.CurrencySymbol@($"{netSum:N2}")Preis (ungeb., nicht abgew.):@(minPrice != maxPrice ? $"{minPrice:N4}–{maxPrice:N4}" : $"{minPrice:N4}") @Model.CurrencySymbol/kg
Mehrwertsteuer:@Utils.GetSign(vat)@Model.CurrencySymbol@($"{Math.Abs(vat):N2}")Gebunden-Zuschlag: - @(minGeb != maxGeb ? $"{minGeb:N4}–{maxGeb:N4} {Model.CurrencySymbol}/kg" : minGeb == 0 ? "-" : $"{minGeb:N4} {Model.CurrencySymbol}/kg") -
Bruttosumme:@Model.CurrencySymbol@($"{grossSum:N2}")
Abzüge (Strafen/Pönalen, GA, ...):@Utils.GetSign(totalMods)@Model.CurrencySymbol@($"{Math.Abs(totalMods):N2}")Menge (ungebunden):@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight):N0}") kg
Bereits berücksichtigte Abzüge:@Utils.GetSign(considered)@Model.CurrencySymbol@($"{Math.Abs(considered):N2}")Menge (gebunden):@($"{Model.Data.Rows.Sum(r => r.Geb.Weight + r.LowGeb.Weight):N0}") kg
Auszahlungsbetrag:@Model.CurrencySymbol@($"{totalSum:N2}")Gesamtmenge:@($"{Model.Data.Rows.Sum(r => r.Ungeb.Weight + r.LowGeb.Weight + r.Geb.Weight):N0}") kg
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @foreach (var m in Model.ModifierStat) { - var mod = Model.Modifiers[m.ModId]; - - - - - - - - - } - -
Statistik Zu-/Abschläge
NameZu-/AbschlagLieferungenMinimumMaximumBetrag
[#][@Model.CurrencySymbol][@Model.CurrencySymbol][@Model.CurrencySymbol]
@mod.Name@mod.ValueStr@($"{m.Count:N0}")@($"{m.Min:N2}")@($"{m.Max:N2}")@($"{m.Sum:N2}")
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @{ - string? lastHdr = null; - } - @foreach (var row in Model.Data.Rows) { - var hdr = $"{row.Variety}{(row.Attribute != null ? " / " : "")}{row.Attribute}{(row.Cultivation != null ? " / " : "")}{row.Cultivation}"; - if (lastHdr != hdr) { - var rows = Model.Data.Rows - .Where(r => r.Variety == row.Variety && r.Attribute == row.Attribute && r.Cultivation == row.Cultivation) - .ToList(); - - - - - - - - - - - } - - - - - - - - - - - - lastHdr = hdr; - } - -
QualitätsstufeGradationungebundenattributlos gebundengebundenGesamt
[@(true ? "°Oe" : "°KMW")][kg][@(Model.CurrencySymbol)/kg][kg][@(Model.CurrencySymbol)/kg][kg][@(Model.CurrencySymbol)/kg][@(Model.CurrencySymbol)]
@hdr@($"{rows.Sum(r => r.Ungeb.Weight):N0}")@($"{rows.Sum(r => r.LowGeb.Weight):N0}")@($"{rows.Sum(r => r.Geb.Weight):N0}")@($"{rows.Sum(r => r.Amount):N2}")
@(row.QualityLevel)@($"{row.Oe:N0}")@(row.Ungeb.Weight != 0 ? $"{row.Ungeb.Weight:N0}" : "-")@(row.Ungeb.MaxPrice != null ? $"{row.Ungeb.MaxPrice:N4}" : "-")@(row.LowGeb.Weight != 0 ? $"{row.LowGeb.Weight:N0}" : "-")@(row.LowGeb.MaxPrice != null ? $"{row.LowGeb.MaxPrice:N4}" : "-")@(row.Geb.Weight != 0 ? $"{row.Geb.Weight:N0}" : "-")@(row.Geb.MaxPrice != null ? $"{row.Geb.MaxPrice:N4}" : "-")@($"{row.Amount:N2}")
-
diff --git a/Elwig/Documents/PaymentVariantSummary.css b/Elwig/Documents/PaymentVariantSummary.css deleted file mode 100644 index 750ede6..0000000 --- a/Elwig/Documents/PaymentVariantSummary.css +++ /dev/null @@ -1,21 +0,0 @@ - -h1 { - text-align: center; - font-size: 24pt; - margin-top: 0; - margin-bottom: 2mm; -} - -h2 { - text-align: center; - font-size: 14pt; - margin-top: 2mm; -} - -table.payment-variant { - margin-top: 10mm; -} - -table.payment-variant-data { - break-before: page; -} diff --git a/Elwig/Documents/WineQualityStatistics.cs b/Elwig/Documents/WineQualityStatistics.cs index 240d5f4..b49ce9a 100644 --- a/Elwig/Documents/WineQualityStatistics.cs +++ b/Elwig/Documents/WineQualityStatistics.cs @@ -1,5 +1,13 @@ +using Elwig.Helpers; using Elwig.Models.Dtos; +using iText.Kernel.Colors; +using iText.Kernel.Pdf; +using iText.Layout.Borders; +using iText.Layout.Element; +using iText.Layout.Properties; +using System; using System.Collections.Generic; +using System.Linq; namespace Elwig.Documents { public class WineQualityStatistics : Document { @@ -19,9 +27,99 @@ namespace Elwig.Documents { public WineQualityStatisticsData Data; public bool UseOe => Data.UseOe; - public WineQualityStatistics(string filter, WineQualityStatisticsData data) : base($"{Name} {filter}") { + public WineQualityStatistics(string filter, WineQualityStatisticsData data) : + base($"{Name} {filter}") { Filter = filter; Data = data; } + + protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { + base.RenderBody(doc, pdf); + doc.Add(new KernedParagraph(Name, 24) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginBottomMM(2)); + doc.Add(new KernedParagraph(Filter, 14) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF) + .SetMarginBottomMM(10)); + foreach (var sec in Data.Sections) { + doc.Add(NewQualitySectionTable(sec).SetMarginBottomMM(5)); + } + } + + protected Table NewQualityColumnTable(string[] qualIds, WineQualityStatisticsData.QualitySection sec) { + var tbl = new Table(ColsMM(9.5, 10, 19.5)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetMarginsMM(1, 0, 1, 0) + .AddCell(NewCell(new KernedParagraph(UseOe ? "[°Oe]" : "[°KMW]", 8) + .SetTextAlignment(TextAlignment.CENTER).SetFont(IF)).SetPaddingsMM(1, 1, 1, 2)) + .AddCell(NewCell(new KernedParagraph("[#]", 8) + .SetTextAlignment(TextAlignment.CENTER).SetFont(IF)).SetPaddingsMM(1, 1, 1, 1)) + .AddCell(NewCell(new KernedParagraph("[kg]", 8) + .SetTextAlignment(TextAlignment.CENTER).SetFont(IF)).SetPaddingsMM(1, 2, 1, 1)); + + foreach (var qualId in qualIds) { + tbl.AddCell(NewCell(new KernedParagraph(QualityLevels.GetValueOrDefault(qualId, qualId), 10) + .SetFont(BI).SetTextAlignment(TextAlignment.CENTER), colspan: 3) + .SetPaddingsMM(2, 0, 2, 0)); + foreach (var (grad, avgKmw, num, weight) in sec.Data.GetValueOrDefault(qualId, Array.Empty<(double, double, int, int)>())) { + tbl.AddCell(NewCell(new KernedParagraph(UseOe ? $"{grad:N0}" : $"{grad:N1}", 10) + .SetTextAlignment(TextAlignment.CENTER)).SetPaddingsMM(0, 0, 0, 2)) + .AddCell(NewCell(new KernedParagraph($"{num:N0}", 10) + .SetTextAlignment(TextAlignment.RIGHT)).SetPaddingsMM(0, 0, 0, 0)) + .AddCell(NewCell(new KernedParagraph($"{weight:N0}", 10) + .SetTextAlignment(TextAlignment.RIGHT)).SetPaddingsMM(0, 2, 0, 0)); + } + } + return tbl; + } + + protected Table NewQualitySumTable(double kmw, int num, int weight) { + return new Table(ColsMM(9.5, 10, 19.5)) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetMarginsMM(1, 0, 1, 0) + .AddCell(NewCell(new KernedParagraph(weight == 0 ? "-" : UseOe ? $"{Utils.KmwToOe(kmw):N0}" : $"{kmw:N1}", 10) + .SetTextAlignment(TextAlignment.CENTER).SetFont(BF)).SetPaddingsMM(0, 0, 0, 2)) + .AddCell(NewCell(new KernedParagraph($"{num:N0}", 10) + .SetTextAlignment(TextAlignment.RIGHT).SetFont(BF)).SetPaddingsMM(0, 0, 0, 0)) + .AddCell(NewCell(new KernedParagraph($"{weight:N0}", 10) + .SetTextAlignment(TextAlignment.RIGHT).SetFont(BF)).SetPaddingsMM(0, 2, 0, 0)); + } + + protected Table NewQualitySectionTable(WineQualityStatisticsData.QualitySection sec) { + var tbl = new Table(4) + .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() + .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) + .SetBorder(new SolidBorder(BorderThickness)) + .SetKeepTogether(true); + + var bgColor = sec.Type == "R" ? new DeviceRgb(0xff, 0xc0, 0xc0) : sec.Type == "W" ? new DeviceRgb(0xc0, 0xff, 0xc0) : new DeviceRgb(0xe0, 0xe0, 0xe0); + tbl.AddCell(NewCell(new KernedParagraph(sec.Name, 14).SetFont(BF), colspan: 4) + .SetBackgroundColor(bgColor).SetPaddingsMM(1, 2, 1, 2)); + + foreach (var qualIds in QualIds) { + tbl.AddCell(NewCell().SetPadding(0).Add(NewQualityColumnTable(qualIds, sec)) + .SetBorder(new SolidBorder(BorderThickness))); + } + + foreach (var qualIds in QualIds) { + var quals = qualIds.Select(q => sec.Data.GetValueOrDefault(q, Array.Empty<(double Grad, double AvgKmw, int Num, int Weight)>())); + var weight = quals.Sum(q => q.Sum(kv => kv.Weight)); + var num = quals.Sum(q => q.Sum(kv => kv.Num)); + var kmw = quals.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / weight; + tbl.AddCell(NewCell().SetPaddingsMM(0.5f, 0, 0.5f, 0).Add(NewQualitySumTable(kmw, num, weight)) + .SetBorder(new SolidBorder(BorderThickness))); + } + + var totalWeight = sec.Data.Values.Sum(q => q.Sum(kv => kv.Weight)); + var totalNum = sec.Data.Values.Sum(q => q.Sum(kv => kv.Num)); + var totalKmw = sec.Data.Values.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / totalWeight; + tbl.AddCell(NewCell(colspan: 3).SetBackgroundColor(bgColor)) + .AddCell(NewCell().SetPadding(0).Add(NewQualitySumTable(totalKmw, totalNum, totalWeight)) + .SetBackgroundColor(bgColor)); + + return tbl; + } } } diff --git a/Elwig/Documents/WineQualityStatistics.cshtml b/Elwig/Documents/WineQualityStatistics.cshtml deleted file mode 100644 index 76279f4..0000000 --- a/Elwig/Documents/WineQualityStatistics.cshtml +++ /dev/null @@ -1,81 +0,0 @@ -@using RazorLight -@using Elwig.Helpers -@inherits TemplatePage -@model Elwig.Documents.WineQualityStatistics -@{ Layout = "Document"; } - -
-

Qualitätsstatistik

-

@Model.Filter

- @foreach (var sec in Model.Data.Sections) { - - - - - - - - - - - - - - - @foreach (var qualIds in Model.QualIds) { - - } - - - @foreach (var qualIds in Model.QualIds) { - var quals = qualIds.Select(q => sec.Data.GetValueOrDefault(q, Array.Empty<(double Grad, double AvgKmw, int Num, int Weight)>())); - var weight = quals.Sum(q => q.Sum(kv => kv.Weight)); - var num = quals.Sum(q => q.Sum(kv => kv.Num)); - var kmw = quals.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / weight; - - } - - - - - @{ - var totalWeight = sec.Data.Values.Sum(q => q.Sum(kv => kv.Weight)); - var totalNum = sec.Data.Values.Sum(q => q.Sum(kv => kv.Num)); - var totalKmw = sec.Data.Values.Sum(q => q.Sum(kv => kv.AvgKmw * kv.Weight)) / totalWeight; - } - - - -
-

@sec.Name

-
-
- [@(Model.UseOe ? "°Oe" : "°KMW")] - [#] - [kg] -
- @foreach (var qualId in qualIds) { -

@(Model.QualityLevels.GetValueOrDefault(qualId, qualId))

- @foreach (var (grad, avgKmw, num, weight) in sec.Data.GetValueOrDefault(qualId, Array.Empty<(double, double, int, int)>())) { -
- @(Model.UseOe ? $"{grad:N0}" : $"{grad:N1}") - @($"{num:N0}") - @($"{weight:N0}") -
- } - } -
-
- @(weight == 0 ? "-" : Model.UseOe ? $"{Utils.KmwToOe(kmw):N0}" : $"{kmw:N1}") - @($"{num:N0}") - @($"{weight:N0}") -
-
- } -
diff --git a/Elwig/Documents/WineQualityStatistics.css b/Elwig/Documents/WineQualityStatistics.css deleted file mode 100644 index 842bd5e..0000000 --- a/Elwig/Documents/WineQualityStatistics.css +++ /dev/null @@ -1,97 +0,0 @@ - -h1 { - text-align: center; - font-size: 24pt; - margin-top: 0; - margin-bottom: 2mm; -} - -h2 { - text-align: center; - font-size: 14pt; - margin-top: 2mm; -} - -h3 { - font-weight: bold; - font-style: normal; - font-size: 14pt; - margin: 0; - text-align: left; -} - -h4 { - font-weight: bold; - font-style: italic; - font-size: 10pt; - margin: 0; - text-align: center; - margin: 2mm 0 2mm 0; -} - -.row:first-child { margin-top: 0.5mm; } -.row:last-child { margin-bottom: 0.5mm; } - -.bold { - font-weight: bold; -} - -table { - margin-top: 10mm; - break-inside: avoid; -} - -table th, -table td { - border: var(--border-thickness) solid black; - vertical-align: top !important; -} - -table .header { - padding: 1mm 2mm; -} - -table .header, -table .footer { - background-color: #E0E0E0; -} - -table .header.red, -table .footer.red { - background-color: #FFC0C0; -} - -table .header.green, -table .footer.green { - background-color: #C0FFC0; -} - -.row { - display: flex; - width: 100%; - font-size: 10pt; -} - -.row span { - flex: 10mm 1 1; - display: block; - padding: 0 1mm; -} - -.row .units { - text-align: center; - font-size: 8pt; - font-style: italic; - padding: 1mm; -} - -.gradation { - text-align: center; -} - -.number { - text-align: right; -} - -.row span:first-child { flex-basis: 7.5mm; } -.row span:last-child { flex-basis: 17.5mm; } diff --git a/Elwig/Elwig.csproj b/Elwig/Elwig.csproj index b1f3f40..73d1a3b 100644 --- a/Elwig/Elwig.csproj +++ b/Elwig/Elwig.csproj @@ -27,16 +27,13 @@ - - - diff --git a/Elwig/Helpers/Printing/FooterEventHandler.cs b/Elwig/Helpers/Printing/FooterEventHandler.cs deleted file mode 100644 index 5514684..0000000 --- a/Elwig/Helpers/Printing/FooterEventHandler.cs +++ /dev/null @@ -1,176 +0,0 @@ -using Elwig.Documents; -using iText.IO.Font.Constants; -using iText.Kernel.Font; -using iText.Kernel.Geom; -using iText.Kernel.Pdf; -using iText.Kernel.Pdf.Action; -using iText.Kernel.Pdf.Canvas; -using iText.Kernel.Pdf.Event; -using iText.Kernel.Pdf.Xobject; -using iText.Layout; -using iText.Layout.Borders; -using iText.Layout.Element; -using iText.Layout.Properties; -using System; -using System.Collections.Generic; - -namespace Elwig.Helpers.Printing { - public class FooterEventHandler : AbstractPdfDocumentEventHandler { - - private const float _fontSize = 10; - private const float _ptInMm = 2.8346456693f; - private const float _placeholderWidth = 50 * _ptInMm; - - private readonly string _date; - private readonly string? _center; - private readonly bool _doublePaged; - private readonly bool _isPreview; - private readonly bool _isBusiness; - private readonly bool _showFoldMarks; - private readonly PdfFont _font; - private readonly PdfFont _fontBold; - private readonly PdfFont _fontItalic; - - private readonly List _pageNumPlaceholders; - - public int NumberOfPages { get; private set; } - - public FooterEventHandler(Documents.Document? doc = null) { - _date = $"{doc?.Date ?? DateOnly.FromDateTime(Utils.Today):dddd, d. MMMM yyyy}"; - _center = doc?.DocumentId; - _doublePaged = doc?.IsDoublePaged ?? false; - _isPreview = doc?.IsPreview ?? false; - _isBusiness = doc is BusinessDocument; - _showFoldMarks = doc?.ShowFoldMarks ?? false; - _font = PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN); - _fontBold = PdfFontFactory.CreateFont(StandardFonts.TIMES_BOLD); - _fontItalic = PdfFontFactory.CreateFont(StandardFonts.TIMES_ITALIC); - _pageNumPlaceholders = []; - } - - protected override void OnAcceptedEvent(AbstractPdfDocumentEvent evt) { - if (evt.GetType() == PdfDocumentEvent.END_PAGE) { - OnPageEnd((PdfDocumentEvent)evt); - } else if (evt.GetType() == PdfDocumentEvent.START_DOCUMENT_CLOSING) { - OnDocumentClose((PdfDocumentEvent)evt); - } - } - - private void OnPageEnd(PdfDocumentEvent evt) { - var pdf = evt.GetDocument(); - var page = evt.GetPage(); - var pageNum = pdf.GetPageNumber(page); - - var pageSize = page.GetPageSize(); - float leftX1 = pageSize.GetLeft() + 25 * _ptInMm; - float leftX2 = pageSize.GetLeft() + 20 * _ptInMm; - float rightX1 = pageSize.GetRight() - 20 * _ptInMm; - float rightX2 = pageSize.GetRight() - 25 * _ptInMm; - float footerY = pageSize.GetBottom() + 25 * _ptInMm; - float y = footerY + _fontSize; - float footerWidth = 165 * _ptInMm; - float footerHeight = 25 * _ptInMm; - - var pdfCanvas = new PdfCanvas(page.NewContentStreamAfter(), page.GetResources(), pdf); - using var canvas = new Canvas(pdfCanvas, pageSize); - - var placeholder = new PdfFormXObject(new Rectangle(0, 0, _placeholderWidth, _fontSize)); - _pageNumPlaceholders.Add(placeholder); - - var c = App.Client; - var dateP = new Paragraph(_date).SetFont(_font).SetFontSize(_fontSize); - var centerP = new Paragraph(_center ?? "").SetFont(_fontItalic).SetFontSize(_fontSize); - var pageNumP = new Paragraph().Add(new Image(placeholder)).SetFont(_font).SetFontSize(_fontSize); - - if (pageNum == 1) { - // First page - canvas.ShowTextAligned(dateP, leftX1, y, TextAlignment.LEFT); - canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, y, TextAlignment.CENTER); - canvas.ShowTextAligned(pageNumP, rightX1, y, TextAlignment.RIGHT); - - var footerP = new Paragraph().SetFont(_font).SetFontSize(_fontSize) - .SetTextAlignment(TextAlignment.CENTER).SetVerticalAlignment(VerticalAlignment.TOP) - .SetMargin(0).SetMultipliedLeading(1) - .SetWidth(footerWidth).SetHeight(footerHeight).SetPaddingTop(1 * _ptInMm).SetBorderTop(new SolidBorder(0.5f)) - .Add(c.NameFull); - if (_isBusiness) { - footerP.Add("\n"); - footerP.AddAll(Utils.GenerateFooter("\n", " \u00b7 ") - .Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine() - .Item(c.EmailAddress != null ? new Link(c.EmailAddress, PdfAction.CreateURI($"mailto:{Uri.EscapeDataString(c.Name)}%20<{c.EmailAddress}>")) : null) - .Item(c.Website != null ? new Link(c.Website, PdfAction.CreateURI($"http://{c.Website}/")) : null) - .Item("Betriebs-Nr.", c.LfbisNr).Item("Bio-KSt.", c.OrganicAuthority).NextLine() - .Item("UID", c.UstIdNr).Item("BIC", c.Bic).Item("IBAN", c.Iban) - .ToLeafElements()); - } - // FIXME links are drawn on next page - move footer into "normal" document creation - canvas.ShowTextAligned(footerP, (leftX1 + rightX1) / 2, footerY, TextAlignment.CENTER, VerticalAlignment.TOP); - } else if (_doublePaged && (pageNum % 2 == 0)) { - // Swap side - canvas.ShowTextAligned(pageNumP, leftX2, footerY, TextAlignment.LEFT, VerticalAlignment.TOP); - canvas.ShowTextAligned(centerP, (leftX2 + rightX2) / 2, footerY, TextAlignment.CENTER, VerticalAlignment.TOP); - canvas.ShowTextAligned(dateP, rightX2, footerY, TextAlignment.RIGHT, VerticalAlignment.TOP); - } else { - canvas.ShowTextAligned(dateP, leftX1, footerY, TextAlignment.LEFT, VerticalAlignment.TOP); - canvas.ShowTextAligned(centerP, (leftX1 + rightX1) / 2, footerY, TextAlignment.CENTER, VerticalAlignment.TOP); - canvas.ShowTextAligned(pageNumP, rightX1, footerY, TextAlignment.RIGHT, VerticalAlignment.TOP); - } - - if (_showFoldMarks) { - var m1 = pageSize.GetTop() - 105 * _ptInMm; - var m2 = pageSize.GetTop() - 148.5 * _ptInMm; - var m3 = pageSize.GetTop() - 210 * _ptInMm; - pdfCanvas.SetLineWidth(0.5f); - - pdfCanvas.MoveTo(0, m1); - pdfCanvas.LineTo(10 * _ptInMm, m1); - pdfCanvas.MoveTo(pageSize.GetRight(), m1); - pdfCanvas.LineTo(pageSize.GetRight() - 10 * _ptInMm, m1); - - pdfCanvas.MoveTo(0, m2); - pdfCanvas.LineTo(7 * _ptInMm, m2); - pdfCanvas.MoveTo(pageSize.GetRight(), m2); - pdfCanvas.LineTo(pageSize.GetRight() - 7 * _ptInMm, m2); - - pdfCanvas.MoveTo(0, m3); - pdfCanvas.LineTo(10 * _ptInMm, m3); - pdfCanvas.MoveTo(pageSize.GetRight(), m3); - pdfCanvas.LineTo(pageSize.GetRight() - 10 * _ptInMm, m3); - - pdfCanvas.ClosePathStroke(); - } - - if (NumberOfPages > 0) { - // FillPlaceholders() was already called - FillPlaceholder(pdf, pageNum); - } - } - - private void OnDocumentClose(PdfDocumentEvent evt) { - FillPlaceholders(evt.GetDocument()); - } - - private void FillPlaceholders(PdfDocument pdf) { - NumberOfPages = pdf.GetNumberOfPages(); - for (int i = 0; i < _pageNumPlaceholders.Count; i++) - FillPlaceholder(pdf, i + 1); - } - - private void FillPlaceholder(PdfDocument pdf, int pageNum) { - var placeholder = _pageNumPlaceholders[pageNum - 1]; - using var canvas = new Canvas(placeholder, pdf); - if (_doublePaged && (pageNum % 2 == 0)) { - // swap - var p = new Paragraph().SetFont(_font).SetFontSize(_fontSize); - if (_isPreview) p.Add(new Text("(vorläufig) ").SetFont(_fontBold)); - p.Add($"Seite {pageNum:N0} von {NumberOfPages:N0} "); - canvas.ShowTextAligned(p, 0, 0, TextAlignment.LEFT); - } else { - var p = new Paragraph().SetFont(_font).SetFontSize(_fontSize) - .Add($"Seite {pageNum:N0} von {NumberOfPages:N0}"); - if (_isPreview) p.Add(new Text(" (vorläufig)").SetFont(_fontBold)); - canvas.ShowTextAligned(p, _placeholderWidth, 0, TextAlignment.RIGHT); - } - } - } -} diff --git a/Elwig/Helpers/Printing/Html.cs b/Elwig/Helpers/Printing/Html.cs deleted file mode 100644 index 9f76f69..0000000 --- a/Elwig/Helpers/Printing/Html.cs +++ /dev/null @@ -1,44 +0,0 @@ -using RazorLight; -using System; -using System.Threading.Tasks; - -namespace Elwig.Helpers.Printing { - public static class Html { - - private static RazorLightEngine? Engine = null; - public static bool IsReady => Engine != null; - - public static async Task Init(Action? evtHandler = null) { - var e = new RazorLightEngineBuilder() - .UseFileSystemProject(App.DocumentsPath) - .UseMemoryCachingProvider() - .Build(); - - await Task.Delay(500); - - await e.CompileTemplateAsync("Document"); - await e.CompileTemplateAsync("BusinessDocument"); - await e.CompileTemplateAsync("BusinessLetter"); - - await e.CompileTemplateAsync("CreditNote"); - await e.CompileTemplateAsync("DeliveryAncmtList"); - await e.CompileTemplateAsync("DeliveryConfirmation"); - await e.CompileTemplateAsync("DeliveryDepreciationList"); - await e.CompileTemplateAsync("DeliveryJournal"); - await e.CompileTemplateAsync("DeliveryNote"); - await e.CompileTemplateAsync("Letterhead"); - await e.CompileTemplateAsync("MemberDataSheet"); - await e.CompileTemplateAsync("MemberList"); - await e.CompileTemplateAsync("PaymentVariantSummary"); - await e.CompileTemplateAsync("WineQualityStatistics"); - - Engine = e; - evtHandler?.Invoke(); - } - - public static async Task CompileRenderAsync(string key, object model) { - if (Engine == null) throw new InvalidOperationException("The razor engine has not been initialized yet"); - return await Engine.CompileRenderAsync(key, model); - } - } -} diff --git a/Elwig/Helpers/Printing/Pdf.cs b/Elwig/Helpers/Printing/Pdf.cs index 28a1d60..203ecaa 100644 --- a/Elwig/Helpers/Printing/Pdf.cs +++ b/Elwig/Helpers/Printing/Pdf.cs @@ -1,19 +1,12 @@ using Elwig.Windows; -using iText.Html2pdf; -using iText.Kernel.Pdf; -using iText.Kernel.Pdf.Event; -using iText.Kernel.Utils; using System; -using System.Collections.Generic; using System.Drawing.Printing; -using System.IO; -using System.Linq; -using System.Threading; using System.Threading.Tasks; using System.Windows; namespace Elwig.Helpers.Printing { public static class Pdf { + public static Task Init(Action? evtHandler = null) { PdfiumNative.FPDF_InitLibrary(); evtHandler?.Invoke(); @@ -25,126 +18,6 @@ namespace Elwig.Helpers.Printing { return Task.CompletedTask; } - public static (int Pages, IEnumerable PerDoc) Convert(string htmlPath, string pdfPath, Documents.Document? doc = null) { - int nPages; - using var html = File.Open(htmlPath, FileMode.Open); - using var writer = new PdfWriter(pdfPath); - using var pdf = new PdfDocument(writer); - var footerHandler = new FooterEventHandler(doc); - pdf.AddEventHandler(PdfDocumentEvent.END_PAGE, footerHandler); - pdf.AddEventHandler(PdfDocumentEvent.START_DOCUMENT_CLOSING, footerHandler); - // TODO embed fonts? - //pdf.AddFont(PdfFontFactory.CreateFont(StandardFonts.TIMES_ROMAN)); - HtmlConverter.ConvertToPdf(html, pdf); - nPages = footerHandler.NumberOfPages; - return (nPages, [nPages]); - } - - public static (int Pages, IEnumerable PerDoc) Convert(IEnumerable inputFiles, string pdfPath, bool doublePaged = false, IEnumerable? docs = null, CancellationToken? cancelToken = null, IProgress? progress = null) { - var tmpFileNames = new List(); - var pageNums = new List(); - var tmpPageNums = new List(); - - var htmlFiles = inputFiles - .Where(f => !f.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)) - .Select(f => f.TrimStart('!', '#')) - .ToList(); - - try { - for (int i = 0; i < htmlFiles.Count; i++) { - string tmpFile = $"{pdfPath}.{i:0000}.part"; - tmpFileNames.Add(tmpFile); - var (pages, _) = Convert(htmlFiles[i], tmpFile, docs?.ElementAt(i)); - tmpPageNums.Add(pages); - } - - using var writer = new PdfWriter(pdfPath); - using var mergedPdf = new PdfDocument(writer); - var merger = new PdfMerger(mergedPdf); - - PdfPage? letterheadPage = null; - int letterheadInsertIndex = 0; - int letterheadDocIndex = 0; - - for (int i = 0; i < inputFiles.Count(); i++) { - var fileName = inputFiles.ElementAt(i); - int p0 = mergedPdf.GetNumberOfPages(); - - if (letterheadPage != null && fileName.StartsWith('#')) { - if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) { - mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf)); - mergedPdf.AddNewPage(); - } else { - mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage.CopyTo(mergedPdf)); - mergedPdf.AddNewPage(letterheadInsertIndex + 2); - } - - pageNums[letterheadDocIndex] = 1; - letterheadPage = null; - } - - if (fileName.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase)) { - var cleanName = fileName.TrimStart('!', '#'); - - if (doublePaged && fileName.StartsWith('#')) { - using var reader = new PdfReader(cleanName); - using var src = new PdfDocument(reader); - letterheadPage = src.GetPage(1).CopyTo(mergedPdf); - letterheadInsertIndex = p0; - letterheadDocIndex = i; - } else { - using var reader = new PdfReader(cleanName); - using var src = new PdfDocument(reader); - merger.Merge(src, 1, src.GetNumberOfPages()); - } - } else { - string tmpFile = tmpFileNames[i]; - if (doublePaged && fileName.StartsWith('#')) { - using var reader = new PdfReader(tmpFile); - using var src = new PdfDocument(reader); - letterheadPage = src.GetPage(1).CopyTo(mergedPdf); - letterheadInsertIndex = p0; - letterheadDocIndex = i; - } else { - using var reader = new PdfReader(tmpFile); - using var src = new PdfDocument(reader); - merger.Merge(src, 1, tmpPageNums[i]); - } - } - - int p1 = mergedPdf.GetNumberOfPages(); - pageNums.Add(p1 - p0); - - if (doublePaged && fileName[0] != '!' && fileName[0] != '#' && mergedPdf.GetNumberOfPages() % 2 != 0) { - if (letterheadPage != null) { - mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf)); - letterheadPage = null; - } else { - mergedPdf.AddNewPage(); - } - } - } - - if (letterheadPage != null) { - if (mergedPdf.GetNumberOfPages() <= letterheadInsertIndex) { - mergedPdf.AddPage(letterheadPage.CopyTo(mergedPdf)); - mergedPdf.AddNewPage(); - } else { - mergedPdf.AddPage(letterheadInsertIndex + 1, letterheadPage.CopyTo(mergedPdf)); - mergedPdf.AddNewPage(letterheadInsertIndex + 2); - } - - pageNums[letterheadDocIndex] = 1; - } - } finally { - foreach (var tmp in tmpFileNames) { - if (File.Exists(tmp)) File.Delete(tmp); - } - } - - return (pageNums.Sum(), pageNums); - } - public static void Show(TempFile file, string title) { App.MainDispatcher.BeginInvoke(() => { var w = new DocumentViewerWindow(title, file); diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index eb187ad..f48a2ea 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -300,8 +300,8 @@ namespace Elwig.Helpers { return d.ShowDialog() == true ? d.Price : null; } - public static Footer GenerateFooter(string lineBreak, string seperator) { - return new Footer(lineBreak, seperator); + public static Footer GenerateFooter(string lineBreak, string separator) { + return new Footer(lineBreak, separator); } public class Footer { @@ -341,7 +341,7 @@ namespace Elwig.Helpers { var first2 = true; foreach (var item in line) { if (!first2) l.Add(new Text(Separator)); - l.Add(item as ILeafElement ?? new Text(item.ToString())); + l.Add(item as ILeafElement ?? new Text(item.ToString() ?? "")); first2 = false; } first1 = false; diff --git a/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs b/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs index 20b9705..4dac291 100644 --- a/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs +++ b/Elwig/Models/Dtos/DeliveryConfirmationDeliveryData.cs @@ -1,5 +1,6 @@ using Elwig.Models.Entities; using Microsoft.EntityFrameworkCore; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -69,12 +70,14 @@ namespace Elwig.Models.Dtos { } public class DeliveryConfirmationDeliveryRow { + public DateOnly Date; public string LsNr; public int DPNr; public string Variety; public string? Attribute; public string? Cultivation; public string QualityLevel; + public string QualId; public bool IsDepreciated; public (double Oe, double Kmw) Gradation; public string[] Modifiers; @@ -84,12 +87,14 @@ namespace Elwig.Models.Dtos { public DeliveryConfirmationDeliveryRow(DeliveryPart p) { var d = p.Delivery; + Date = d.Date; LsNr = d.LsNr; DPNr = p.DPNr; Variety = p.Variety.Name; Attribute = p.Attribute?.Name; Cultivation = p.Cultivation?.Name; QualityLevel = p.Quality.Name; + QualId = p.Quality.QualId; IsDepreciated = p.QualId == "WEI"; Gradation = (p.Oe, p.Kmw); Modifiers = [.. p.Modifiers.Select(m => m.Name)]; diff --git a/Elwig/Windows/MailWindow.xaml.cs b/Elwig/Windows/MailWindow.xaml.cs index 47b36c8..979a6cb 100644 --- a/Elwig/Windows/MailWindow.xaml.cs +++ b/Elwig/Windows/MailWindow.xaml.cs @@ -654,92 +654,100 @@ namespace Elwig.Windows { CancelGeneration?.Dispose(); CancelGeneration = new(); - using var ctx = new AppDbContext(); + try { + var doublePaged = DoublePagedInput.IsChecked == true; + var location = PostalLocation.Text.Trim(); + var docs = SelectedDocs.OrderByDescending(d => d.Type).ToList(); - var doublePaged = DoublePagedInput.IsChecked == true; - var location = PostalLocation.Text.Trim(); - var docs = SelectedDocs.OrderByDescending(d => d.Type).ToList(); - - IEnumerable recipients = Recipients; - if (OrderMgNrInput.IsChecked == true) { - recipients = recipients - .OrderBy(m => m.MgNr) - .ToList(); - } else if (OrderNameInput.IsChecked == true) { - recipients = recipients - .OrderBy(m => m.Name) - .ThenBy(m => m.GivenName) - .ThenBy(m => m.MgNr) - .ToList(); - } else if (OrderPlzInput.IsChecked == true) { - if (docs.Any(d => d.Type == DocType.DeliveryConfirmation || d.Type == DocType.CreditNote)) { + IEnumerable recipients = Recipients; + if (OrderMgNrInput.IsChecked == true) { recipients = recipients - .OrderBy(m => m.BillingAddress?.PostalDest.AtPlz?.Plz ?? m.PostalDest.AtPlz?.Plz) - .ThenBy(m => m.BillingAddress?.PostalDest.AtPlz?.Ort.Name ?? m.PostalDest.AtPlz?.Ort.Name) - .ThenBy(m => m.Name) - .ThenBy(m => m.GivenName) - .ThenBy(m => m.MgNr) - .ToList(); - } else { - recipients = recipients - .OrderBy(m => m.PostalDest.AtPlz?.Plz) - .ThenBy(m => m.PostalDest.AtPlz?.Ort.Name) - .ThenBy(m => m.Name) + .OrderBy(m => m.MgNr) + .ToList(); + } else if (OrderNameInput.IsChecked == true) { + recipients = recipients + .OrderBy(m => m.Name) .ThenBy(m => m.GivenName) .ThenBy(m => m.MgNr) .ToList(); + } else if (OrderPlzInput.IsChecked == true) { + if (docs.Any(d => d.Type == DocType.DeliveryConfirmation || d.Type == DocType.CreditNote)) { + recipients = recipients + .OrderBy(m => m.BillingAddress?.PostalDest.AtPlz?.Plz ?? m.PostalDest.AtPlz?.Plz) + .ThenBy(m => m.BillingAddress?.PostalDest.AtPlz?.Ort.Name ?? m.PostalDest.AtPlz?.Ort.Name) + .ThenBy(m => m.Name) + .ThenBy(m => m.GivenName) + .ThenBy(m => m.MgNr) + .ToList(); + } else { + recipients = recipients + .OrderBy(m => m.PostalDest.AtPlz?.Plz) + .ThenBy(m => m.PostalDest.AtPlz?.Ort.Name) + .ThenBy(m => m.Name) + .ThenBy(m => m.GivenName) + .ThenBy(m => m.MgNr) + .ToList(); + } } + + var postalDate = DateOnly.ParseExact(PostalDate.Text, "dd.MM.yyyy"); + var printMode = PostalAllInput.IsChecked == true ? 3 : PostalWishInput.IsChecked == true ? 2 : PostalNoEmailInput.IsChecked == true ? 1 : 0; + var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0; + + var hasPreviewDocs = await Task.Run(async () => await GenerateDocuments(doublePaged, location, docs, recipients, postalDate, printMode, emailMode)); + PreviewButton.IsEnabled = true; + PrintButton.IsEnabled = PrintDocument != null && !hasPreviewDocs; + EmailButton.IsEnabled = EmailDocuments != null && !hasPreviewDocs && App.Config.Smtp != null; + } catch (Exception exc) { + MessageBox.Show(exc.ToString(), "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); + } finally { + UnlockInputs(); + GenerateButton.IsEnabled = true; + GenerateButton.Visibility = Visibility.Visible; + AbortButton.IsEnabled = false; + AbortButton.Visibility = Visibility.Hidden; + Mouse.OverrideCursor = null; } + } + + private async Task GenerateDocuments(bool doublePaged, string location, List docs, IEnumerable recipients, DateOnly postalDate, int printMode, int emailMode) { + App.MainDispatcher.Invoke(() => { + ProgressBar.Value = 0.0; + }); + using var ctx = new AppDbContext(); Dictionary> dcData = []; Dictionary<(int, int), (IDictionary, IDictionary, BillingData)> cnData = []; foreach (var doc in docs) { if (doc.Type == DocType.DeliveryConfirmation) { var year = (int)doc.Details!; - - try { - var b = new Billing(year); - await b.FinishSeason(); - await b.CalculateBuckets(); - App.HintContextChange(); - - dcData[year] = await DeliveryConfirmationDeliveryData.ForSeason(ctx.DeliveryParts, year); - } catch (Exception exc) { - MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); - UnlockInputs(); - GenerateButton.IsEnabled = true; - GenerateButton.Visibility = Visibility.Visible; - AbortButton.IsEnabled = false; - AbortButton.Visibility = Visibility.Hidden; - Mouse.OverrideCursor = null; - return; - } + var b = new Billing(year); + await b.FinishSeason(); + await b.CalculateBuckets(); + App.HintContextChange(); + dcData[year] = await DeliveryConfirmationDeliveryData.ForSeason(ctx.DeliveryParts, year); } else if (doc.Type == DocType.CreditNote) { var details = ((int, int))doc.Details!; var year = details.Item1; var avnr = details.Item2; - try { - cnData[(year, avnr)] = ( - await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr), - await ctx.MemberPayments.Where(p => p.Year == year && p.AvNr == avnr).ToDictionaryAsync(c => c.MgNr), - BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data) - ); - } catch (Exception exc) { - MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); - UnlockInputs(); - GenerateButton.IsEnabled = true; - GenerateButton.Visibility = Visibility.Visible; - AbortButton.IsEnabled = false; - AbortButton.Visibility = Visibility.Hidden; - Mouse.OverrideCursor = null; - return; - } + cnData[(year, avnr)] = ( + await CreditNoteDeliveryData.ForPaymentVariant(ctx.CreditNoteDeliveryRows, ctx.PaymentVariants, year, avnr), + await ctx.MemberPayments.Where(p => p.Year == year && p.AvNr == avnr).ToDictionaryAsync(c => c.MgNr), + BillingData.FromJson((await ctx.PaymentVariants.FindAsync(year, avnr))!.Data) + ); await ctx.GetMemberAreaCommitmentBuckets(year, 0); } } - var postalDate = DateOnly.ParseExact(PostalDate.Text, "dd.MM.yyyy"); - var memberDocs = recipients.Select(m => new { + var (printNum, emailNum) = App.MainDispatcher.Invoke(() => { + double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 1 ? PostalNoEmailCount : 0; + double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0; + return (printNum, emailNum); + }); + double totalNum = printNum + emailNum + recipients.Count() / 2; + double offset = 0; + + var memberDocs = recipients.Select((m, i) => new { Member = m, Docs = docs.SelectMany(doc => { try { @@ -781,34 +789,24 @@ namespace Elwig.Windows { } catch (Exception exc) { MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); return []; + } finally { + App.MainDispatcher.Invoke(() => { + ProgressBar.Value = offset + 100.0 * (i + 1) / 2 / totalNum; + }); } }).ToList() }).ToList(); + offset += 100.0 * memberDocs.Count / 2 / totalNum; var hasPreviewDocs = memberDocs.Any(m => m.Docs.Any(d => d.Doc.IsPreview)); if (hasPreviewDocs) { var res = MessageBox.Show("Einige der ausgewählten Dokumente sind nur als vorläufig zu betrachten und können daher nicht verschickt/ausgedruckt werden!\n\nDies könnte an einer noch nicht festgesetzen Auszahlungsvariante liegen oder daran, dass nicht alle Adressaten/Empfänger eine Traubengutschrift erhalten\n(\"Empfänger von Gutschriften\").\n\nSoll mit dem Generieren fortgefahren werden?", "Vorläufige Dokumente", MessageBoxButton.OKCancel, MessageBoxImage.Warning, MessageBoxResult.Cancel); if (res != MessageBoxResult.OK) { - UnlockInputs(); - GenerateButton.IsEnabled = true; - GenerateButton.Visibility = Visibility.Visible; - AbortButton.IsEnabled = false; - AbortButton.Visibility = Visibility.Hidden; - Mouse.OverrideCursor = null; - return; + throw new OperationCanceledException("Dokumentenerzeugung abgebrochen!"); } } - var printMode = PostalAllInput.IsChecked == true ? 3 : - PostalWishInput.IsChecked == true ? 2 : - PostalNoEmailInput.IsChecked == true ? 1 : 0; - var emailMode = EmailAllInput.IsChecked == true ? 2 : EmailWishInput.IsChecked == true ? 1 : 0; - - double printNum = printMode == 3 ? PostalAllCount : printMode == 2 ? PostalWishCount : printMode == 1 ? PostalNoEmailCount : 0; - double emailNum = emailMode == 2 ? EmailAllCount : emailMode == 1 ? EmailWishCount : 0; - double totalNum = printNum + emailNum; - var email = memberDocs .Where(d => d.Member.EmailAddresses.Count > 0 && (emailMode == 2 || (emailMode == 1 && d.Member.ContactViaEmail))) .ToDictionary(d => d.Member, m => { @@ -823,27 +821,16 @@ namespace Elwig.Windows { return docs; }); var emailRecipients = email.Select(d => d.Key.MgNr).ToHashSet(); - try { - foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) { - foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) { - await item2.Doc.Generate(CancelGeneration.Token, new Progress(v => { - ProgressBar.Value = v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum; - })); - } + foreach (var item1 in email.Select((e, i) => new { Index = i, e.Key, e.Value })) { + foreach (var item2 in item1.Value.Select((d, i) => new { Index = i, Doc = d })) { + await item2.Doc.Generate(CancelGeneration?.Token, new Progress(v => App.MainDispatcher.Invoke(() => { + ProgressBar.Value = offset + v * (item2.Index + 1) / item1.Value.Count / totalNum + 100.0 * item1.Index / totalNum; + }))); } - } catch (Exception exc) { - MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); - UnlockInputs(); - GenerateButton.IsEnabled = true; - GenerateButton.Visibility = Visibility.Visible; - AbortButton.IsEnabled = false; - AbortButton.Visibility = Visibility.Hidden; - Mouse.OverrideCursor = null; - return; } - if (email.Count > 0) { + if (email.Count > 0) EmailDocuments = email; - } + offset += 100.0 * email.Count / totalNum; var printMemberDocs = memberDocs .Where(d => @@ -869,39 +856,20 @@ namespace Elwig.Windows { .ToList(); if (printDocs.Count > 0) { - try { - var print = Document.Merge(printDocs); - print.IsDoublePaged = doublePaged; - await print.Generate(CancelGeneration.Token, new Progress(v => { - ProgressBar.Value = 100.0 * emailNum / totalNum + v * printNum / totalNum; - })); - PrintDocument = print; - PrintMemberDocuments = printMemberDocs.ToDictionary(m => m.Member, m => m.Docs.Select(d => d.Doc).ToList()); - } catch (Exception exc) { - MessageBox.Show(exc.Message, "Fehler", MessageBoxButton.OK, MessageBoxImage.Error); - UnlockInputs(); - GenerateButton.IsEnabled = true; - GenerateButton.Visibility = Visibility.Visible; - AbortButton.IsEnabled = false; - AbortButton.Visibility = Visibility.Hidden; - Mouse.OverrideCursor = null; - return; - } + var print = Document.Merge(printDocs); + await print.Generate(CancelGeneration?.Token, new Progress(v => App.MainDispatcher.Invoke(() => { + ProgressBar.Value = offset + v * printNum / totalNum; + }))); + PrintDocument = print; + PrintMemberDocuments = printMemberDocs.ToDictionary(m => m.Member, m => m.Docs.Select(d => d.Doc).ToList()); } else { PrintDocument = null; PrintMemberDocuments = null; } - ProgressBar.Value = 100.0; - - UnlockInputs(); - GenerateButton.IsEnabled = true; - GenerateButton.Visibility = Visibility.Visible; - AbortButton.IsEnabled = false; - AbortButton.Visibility = Visibility.Hidden; - Mouse.OverrideCursor = null; - PreviewButton.IsEnabled = true; - PrintButton.IsEnabled = PrintDocument != null && !hasPreviewDocs; - EmailButton.IsEnabled = EmailDocuments != null && !hasPreviewDocs && App.Config.Smtp != null; + App.MainDispatcher.Invoke(() => { + ProgressBar.Value = 100.0; + }); + return hasPreviewDocs; } private async void PreviewButton_Click(object sender, RoutedEventArgs evt) { diff --git a/Installer/Folders.wxs b/Installer/Folders.wxs index bce790b..46e4dea 100644 --- a/Installer/Folders.wxs +++ b/Installer/Folders.wxs @@ -1,13 +1,9 @@ - + - - - - - + diff --git a/Installer/HeatComponents.wxs b/Installer/HeatComponents.wxs index bb40eff..5b21e28 100644 --- a/Installer/HeatComponents.wxs +++ b/Installer/HeatComponents.wxs @@ -6,9 +6,6 @@ - - - diff --git a/Installer/Installer.wixproj b/Installer/Installer.wixproj index d1d24f9..4fa190c 100644 --- a/Installer/Installer.wixproj +++ b/Installer/Installer.wixproj @@ -30,7 +30,7 @@ - ProductVersion=$(ElwigFileVersion);BuildPath=..\Elwig\bin\Publish;DocumentPath=..\Elwig\Documents;ElwigProjectDir=..\Elwig + ProductVersion=$(ElwigFileVersion);BuildPath=..\Elwig\bin\Publish;ElwigProjectDir=..\Elwig diff --git a/Tests/UnitTests/DocumentTests/CreditNoteTest.cs b/Tests/UnitTests/DocumentTests/CreditNoteTest.cs index ef7da92..996fe1e 100644 --- a/Tests/UnitTests/DocumentTests/CreditNoteTest.cs +++ b/Tests/UnitTests/DocumentTests/CreditNoteTest.cs @@ -29,10 +29,12 @@ namespace Tests.UnitTests.DocumentTests { Assert.That(text, Contains.Substring("AT81 1234 5678 9012 3457")); Assert.That(text, Contains.Substring(""" 20201001X001 1 Grüner Veltliner 73 15,0 ungeb.: 3 219 0,5000 - - 1 609,50 - 20201001X003 1 Grüner Veltliner abgew. 75 15,4 ungeb.: 2 561 - - - 20201001X003 2 Grüner Veltliner Kabinett / abgew. - 87 17,6 ungeb.: 3 129 - - - 20201001X003 3 Grüner Veltliner abgew. 79 16,1 ungeb.: 1 280 - - + 20201001X003 1 Grüner Veltliner 75 15,4 ungeb.: 2 561 - - + abgew. + 20201001X003 2 Grüner Veltliner 87 17,6 ungeb.: 3 129 - - + Kabinett / abgew. + 20201001X003 3 Grüner Veltliner 79 16,1 ungeb.: 1 280 - - + abgew. 20201001X005 1 Welschriesling 84 17,0 ungeb.: 3 192 - - 20201001X005 2 Welschriesling 84 17,1 ungeb.: 2 190 - - """)); diff --git a/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs b/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs index 7671275..e8af381 100644 --- a/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs +++ b/Tests/UnitTests/DocumentTests/DeliveryConfirmationTest.cs @@ -24,12 +24,13 @@ namespace Tests.UnitTests.DocumentTests { Assert.That(text, Contains.Substring($"Wolkersdorf, am {Elwig.Helpers.Utils.Today:dd.MM.yyyy}")); Assert.That(text, Contains.Substring("Anlieferungsbestätigung 2020")); Assert.That(text, Contains.Substring(""" - 20201001X001 1 Grüner Veltliner Qualitätswein 73 15,0 ungeb.: 3 219 3 219 ☑ - 20201001X003 2 Grüner Veltliner Kabinett Wein 87 17,6 ungeb.: 3 129 3 129 ☑ - 20201001X003 3 Grüner Veltliner Wein 79 16,1 ungeb.: 1 280 1 280 ☑ - 20201001X003 1 Grüner Veltliner Wein 75 15,4 ungeb.: 2 561 2 561 ☑ - 20201001X005 2 Welschriesling Kabinett 84 17,1 ungeb.: 2 190 2 190 ☑ - 20201001X005 1 Welschriesling Kabinett 84 17,0 ungeb.: 3 192 3 192 ☑ + 20201001X001 1 Grüner Veltliner QUW 73 15,0 ungeb.: 3 219 3 219 ☑ + 20201001X003 2 Grüner Veltliner WEI 87 17,6 ungeb.: 3 129 3 129 ☑ + Kabinett + 20201001X003 3 Grüner Veltliner WEI 79 16,1 ungeb.: 1 280 1 280 ☑ + 20201001X003 1 Grüner Veltliner WEI 75 15,4 ungeb.: 2 561 2 561 ☑ + 20201001X005 2 Welschriesling KAB 84 17,1 ungeb.: 2 190 2 190 ☑ + 20201001X005 1 Welschriesling KAB 84 17,0 ungeb.: 3 192 3 192 ☑ """)); Assert.That(text, Contains.Substring("Gesamt: 15 571")); Assert.That(text, Contains.Substring(""" diff --git a/Tests/UnitTests/DocumentTests/DeliveryDepreciationListTest.cs b/Tests/UnitTests/DocumentTests/DeliveryDepreciationListTest.cs index ca5d23f..93a4fdb 100644 --- a/Tests/UnitTests/DocumentTests/DeliveryDepreciationListTest.cs +++ b/Tests/UnitTests/DocumentTests/DeliveryDepreciationListTest.cs @@ -17,14 +17,14 @@ namespace Tests.UnitTests.DocumentTests { Assert.That(text, Contains.Substring("Abwertungsliste")); Assert.That(text, Contains.Substring("Saison 2020")); Assert.That(table, Is.EqualTo(new string[][] { - ["101, MUSTERMANN Max", "Teil-Lfrg.:", "3", "81", "16,5", "6 970"], - ["20201001X003 1 01.10.2020 10:24", "Grüner Veltliner", "75", "15,4", "2 561"], - ["20201001X003 2 01.10.2020 10:24", "Grüner Veltliner", "Kabinett", "87", "17,6", "3 129"], - ["20201001X003 3 01.10.2020 10:24", "Grüner Veltliner", "79", "16,1", "1 280"], - ["103, MUSTERBAUER Matthäus", "Teil-Lfrg.:", "2", "79", "16,2", "6 099"], - ["20201002X001 1 02.10.2020 09:13", "Grüner Veltliner", "Bio", "80", "16,3", "3 198"], - ["20201002X002 1 02.10.2020 09:28", "Grüner Veltliner", "Bio", "78", "16,0", "2 901"], - ["Gesamt:", "(Teil-)Lieferungen: 3 (5)", "80", "16,3", "13 069"], + ["101, MUSTERMANN Max", "Teil-Lfrg.:", "3", "81", "16,5", "6 970"], + ["20201001X003 1 01.10.2020 10:24 Grüner Veltliner", "75", "15,4", "2 561"], + ["20201001X003 2 01.10.2020 10:24 Grüner Veltliner", "Kabinett", "87", "17,6", "3 129"], + ["20201001X003 3 01.10.2020 10:24 Grüner Veltliner", "79", "16,1", "1 280"], + ["103, MUSTERBAUER Matthäus", "Teil-Lfrg.:", "2", "79", "16,2", "6 099"], + ["20201002X001 1 02.10.2020 09:13 Grüner Veltliner", "Bio", "80", "16,3", "3 198"], + ["20201002X002 1 02.10.2020 09:28 Grüner Veltliner", "Bio", "78", "16,0", "2 901"], + ["Gesamt:", "(Teil-)Lieferungen: 3 (5)", "80", "16,3", "13 069"], })); }); } diff --git a/Tests/UnitTests/DocumentTests/MemberListTest.cs b/Tests/UnitTests/DocumentTests/MemberListTest.cs index c6b7994..2b0aca1 100644 --- a/Tests/UnitTests/DocumentTests/MemberListTest.cs +++ b/Tests/UnitTests/DocumentTests/MemberListTest.cs @@ -17,8 +17,8 @@ namespace Tests.UnitTests.DocumentTests { Assert.That(text, Contains.Substring("Mitgliederliste")); Assert.That(text, Contains.Substring("Alle Mitglieder")); Assert.That(table.Take(3), Is.EqualTo(new string[][] { - ["101 MUSTERMANN Max", "Winzerstraße 1", "2223", "Hohenruppersdorf", "0123463", "0", "Hohenruppersdorf"], - ["102 WEINBAUER Wernhardt", "Winzerstraße 2", "2223", "Hohenruppersdorf", "0123471", "0", "Hohenruppersdorf"], + ["101 MUSTERMANN Max", "Winzerstraße 1", "2223", "Hohenruppersdorf", "0123463", "0 Hohenruppersdorf"], + ["102 WEINBAUER Wernhardt", "Winzerstraße 2", "2223", "Hohenruppersdorf", "0123471", "0 Hohenruppersdorf"], [ "W&B Weinbauer GesbR", "Winzerstraße 2", "2223", "Hohenruppersdorf"], })); }); diff --git a/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs b/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs index 9bbae6f..385fa32 100644 --- a/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs +++ b/Tests/UnitTests/DocumentTests/PaymentVariantSummaryTest.cs @@ -18,10 +18,10 @@ namespace Tests.UnitTests.DocumentTests { Assert.That(text, Contains.Substring("Auszahlungsvariante")); Assert.That(text, Contains.Substring(v.Name)); Assert.That(table.Skip(17).ToArray(), Is.EqualTo(new string[][] { - [ "Gradation", "ungebunden", "attributlos gebunden", "gebunden", "Gesamt" ], - [ "[°Oe]", "[kg]", "[€/kg]", "[kg]", "[€/kg]", "[kg]", "[€/kg]", "[€]" ], - ["Grüner Veltliner", "3 219", "0", "0", "1 609,50"], - ["Qualitätswein", "73", "3 219", "0,5000", "-", "-", "-", "-", "1 609,50"] + ["Sorte/Attr./Bewirt.", "Gradation", "ungebunden", "attributlos gebunden", "gebunden", "Gesamt" ], + ["Qualitätsstufe", "[°Oe]", "[kg]", "[€/kg]", "[kg]", "[€/kg]", "[kg]", "[€/kg]", "[€]" ], + ["Grüner Veltliner", "3 219", "0", "0", "1 609,50"], + ["Qualitätswein", "73", "3 219", "0,5000", "-", "-", "-", "-", "1 609,50"] })); }); } diff --git a/Tests/UnitTests/DocumentTests/Setup.cs b/Tests/UnitTests/DocumentTests/Setup.cs index 914065a..070f6d2 100644 --- a/Tests/UnitTests/DocumentTests/Setup.cs +++ b/Tests/UnitTests/DocumentTests/Setup.cs @@ -25,7 +25,6 @@ namespace Tests.UnitTests.DocumentTests { [OneTimeSetUp] public async Task SetupPrinting() { - await Html.Init(); await Pdf.Init(); }