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 Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; namespace Elwig.Documents { public class MemberDataSheet : BusinessDocument { public new static string Name => "Stammdatenblatt"; public Season? Season; public int MemberDeliveredWeightRed; public int MemberDeliveredWeightWhite; public List MemberHistory = []; public Dictionary MemberBuckets = []; public List ActiveAreaCommitments = []; public MemberDataSheet(Member m) : base($"{Name} {m.AdministrativeName}", m, DateOnly.FromDateTime(m.ModifiedAt)) { ShowDateAndLocation = true; DocumentId = $"{Name} {m.MgNr}"; } public static async Task Initialize(int mgnr) { using var ctx = new AppDbContext(); return new MemberDataSheet(await ctx.FetchMembers(mgnr, includeContactInfo: true).SingleAsync()); } protected override async Task LoadData(AppDbContext ctx) { await base.LoadData(ctx); Season = await ctx.FetchSeasons().FirstOrDefaultAsync() ?? throw new ArgumentException("Invalid season"); MemberHistory = await ctx.MemberHistory .Where(h => h.FromMgNr == Member.MgNr || h.ToMgNr == Member.MgNr) .OrderBy(h => h.DateString).ThenBy(h => h.HistNr) .ToListAsync(); MemberBuckets = await ctx.GetMemberBuckets(Utils.CurrentYear, Member.MgNr); ActiveAreaCommitments = await Member.ActiveAreaCommitments(ctx) .Include(c => c.Contract).ThenInclude(c => c.Revisions) .ToListAsync(); var weights = await ctx.Deliveries .Where(d => d.Year == Season.Year && d.MgNr == Member.MgNr) .SelectMany(d => d.Parts) .GroupBy(p => p.Variety.Type) .ToDictionaryAsync(g => g.Key, g => g.Sum(p => p.Weight)); MemberDeliveredWeightRed = weights.GetValueOrDefault("R", 0); MemberDeliveredWeightWhite = weights.GetValueOrDefault("W", 0); } protected override void RenderBody(iText.Layout.Document doc, PdfDocument pdf) { if (Season == null) throw new Exception("Call LoadData before RenderBody"); base.RenderBody(doc, pdf); doc.Add(NewMemberData(Season).SetMarginBottomMM(5)); doc.Add(NewBucketTable(Season, MemberBuckets, MemberDeliveredWeightRed, MemberDeliveredWeightWhite, 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, 6, 0)); doc.Add(NewAreaComTable(ActiveAreaCommitments)); } if (App.Client.EnableMemberHistory) { bool firstOnPage = false; if (pdf.GetNumberOfPages() == 1) { doc.Add(new AreaBreak(AreaBreakType.NEXT_PAGE)); firstOnPage = true; } doc.Add(new KernedParagraph(12).Add(Bold($"Verlauf der Geschäftsanteile per {Date:dd.MM.yyyy}")).SetMargins(firstOnPage ? 0 : 24, 0, 6, 0)); doc.Add(NewMemberBusinessSharesTable(MemberHistory)); } } 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(Season season) { 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)); } var shares = (Member.Shares != 0 || (Member.SharesRed == 0 && Member.SharesWhite == 0) ? $"{Member.Shares:N0}" : "") + (Member.SharesRed != 0 || Member.SharesWhite != 0 ? (Member.Shares != 0 ? " / " : "") + $"{Member.SharesRed:N0} (rot) / {Member.SharesWhite:N0} (weiß)" : "") + (Member.SharesDormant != 0 ? $" / {Member.SharesDormant:N0} (ruhend)" : ""); 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(shares, 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(IEnumerable activeAreaComs) { var areaComs = activeAreaComs.GroupBy(a => a.AreaComType).Select(group => new { Type = group.Key, AreaComs = group.OrderBy(c => c.Contract.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) { var c = areaCom.Contract; tbl.AddCell(NewTd(new KernedParagraph(10).Add(Normal($"{c.Kg.AtKg.Name} ")).Add(Normal($"({c.Kg.AtKg.KgNr:00000})", 8)))) .AddCell(NewTd(c.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(c.YearTo == null ? (c.YearFrom == null ? "unbefristet" : $"ab {c.YearFrom}") : (c.YearFrom == null ? $"bis {c.YearTo}" : $"{c.YearFrom}–{c.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($"{activeAreaComs.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; } protected Cell[] NewBusinessSharesCells(MemberHistoryPoint p, bool isDiff = true) { if (App.Client.HasRedWhite) { return [ NewTd(isDiff ? (p.SharesRed == 0 ? "" : p.SharesRed < 0 ? $"–{-p.SharesRed:N0}" : $"+{p.SharesRed:N0}") : $"{p.SharesRed:N0}", right: true, bold: !isDiff, borderTop: !isDiff), NewTd(isDiff ? (p.SharesWhite == 0 ? "" : p.SharesWhite < 0 ? $"–{-p.SharesWhite:N0}" : $"+{p.SharesWhite:N0}") : $"{p.SharesWhite:N0}", right: true, bold: !isDiff, borderTop: !isDiff), NewTd(isDiff ? (p.SharesDormant == 0 ? "" : p.SharesDormant < 0 ? $"–{-p.SharesDormant:N0}" : $"+{p.SharesDormant:N0}") : $"{p.SharesDormant:N0}", right: true, bold: !isDiff, borderTop: !isDiff), ]; } else { return [ NewTd(isDiff ? (p.Shares == 0 ? "" : p.Shares < 0 ? $"–{-p.Shares:N0}" : $"+{p.Shares:N0}") : $"{p.Shares:N0}", right: true, bold: !isDiff, borderTop: !isDiff), NewTd(isDiff ? (p.SharesDormant == 0 ? "" : p.SharesDormant < 0 ? $"–{-p.SharesDormant:N0}" : $"+{p.SharesDormant:N0}") : $"{p.SharesDormant:N0}", right : true, bold: !isDiff, borderTop: !isDiff), ]; } } protected Table NewMemberBusinessSharesTable(IEnumerable history) { var tbl = new Table(ColsMM(18, 111, 12, 12, 12), true) .SetWidth(UnitValue.CreatePercentValue(100)).SetFixedLayout() .SetBorderCollapse(BorderCollapsePropertyValue.COLLAPSE) .SetBorder(Border.NO_BORDER) .SetFontSize(10); var tw = App.Client.HasRedWhite ? 1 : 2; tbl.AddHeaderCell(NewTh("Datum", rowspan: 2)) .AddHeaderCell(NewTh("Text", rowspan: 2, colspan: tw, left: true)) .AddHeaderCell(NewTh("Geschäftsanteile", colspan: App.Client.HasRedWhite ? 3 : 2)); if (App.Client.HasRedWhite) { tbl.AddHeaderCell(NewTh("rot")) .AddHeaderCell(NewTh("weiß")) .AddHeaderCell(NewTh("ruhend")); } else { tbl.AddHeaderCell(NewTh("normal")) .AddHeaderCell(NewTh("ruhend")); } if (!history.Any() || Member.EntryDate <= history.First().Date) { tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}")) .AddCell(NewTd("Eintritt", colspan: tw, bold: true)) .AddCells(NewBusinessSharesCells(new())); } var recorded = history.Aggregate(new MemberHistoryPoint(), (s, h) => new( s.Shares + (h.ToMgNr == Member.MgNr && h.ToType == 1 ? h.Shares : 0) - (h.FromMgNr == Member.MgNr && h.FromType == 1 ? h.Shares : 0), s.SharesRed + (h.ToMgNr == Member.MgNr && h.ToType == 2 ? h.Shares : 0) - (h.FromMgNr == Member.MgNr && h.FromType == 2 ? h.Shares : 0), s.SharesWhite + (h.ToMgNr == Member.MgNr && h.ToType == 3 ? h.Shares : 0) - (h.FromMgNr == Member.MgNr && h.FromType == 3 ? h.Shares : 0), s.SharesDormant + (h.ToMgNr == Member.MgNr && h.ToType == 9 ? h.Shares : 0) - (h.FromMgNr == Member.MgNr && h.FromType == 9 ? h.Shares : 0))); var cur = new MemberHistoryPoint(Member.Shares - recorded.Shares, Member.SharesRed - recorded.SharesRed, Member.SharesWhite - recorded.SharesWhite, Member.SharesDormant - recorded.SharesDormant); if (cur.Shares != 0 || cur.SharesRed != 0 || cur.SharesWhite != 0 || cur.SharesDormant != 0) { tbl.AddCell(NewTd()) .AddCell(NewTd("Nicht erfasst", colspan: tw, italic: true)) .AddCells(NewBusinessSharesCells(cur)); if (history.Any()) { tbl.AddCell(NewTd(borderTop: true)) .AddCell(NewTd($"Bis Lese {history.First().Date.Year - 1}", borderTop: true, colspan: tw, bold: true)) .AddCells(NewBusinessSharesCells(cur, isDiff: false)); } } DateOnly? lastDate = null; foreach (var row in history) { int s = 0, r = 0, w = 0, d = 0; if (row.FromMgNr == Member.MgNr) { switch (row.FromType) { case 1: s -= row.Shares; break; case 2: r -= row.Shares; break; case 3: w -= row.Shares; break; case 9: d -= row.Shares; break; } } if (row.ToMgNr == Member.MgNr) { switch (row.ToType) { case 1: s += row.Shares; break; case 2: r += row.Shares; break; case 3: w += row.Shares; break; case 9: d += row.Shares; break; } } if (lastDate < Member.EntryDate && row.Date <= Member.EntryDate) { tbl.AddCell(NewTd($"{Member.EntryDate:dd.MM.yyyy}")) .AddCell(NewTd("Eintritt", colspan: tw, bold: true)) .AddCells(NewBusinessSharesCells(new())); } if (lastDate != null && row.Date.Year != lastDate?.Year) { tbl.AddCell(NewTd(borderTop: true)) .AddCell(NewTd(lastDate?.Year == row.Date.Year - 1 ? $"Lese {lastDate?.Year}" : $"Lese {lastDate?.Year}–{row.Date.Year - 1}", borderTop: true, colspan: tw, bold: true)) .AddCells(NewBusinessSharesCells(cur, isDiff: false)); } if (lastDate < Member.ExitDate && row.Date <= Member.ExitDate) { tbl.AddCell(NewTd($"{Member.ExitDate:dd.MM.yyyy}")) .AddCell(NewTd("Austritt", colspan: tw, bold: true)) .AddCells(NewBusinessSharesCells(new())); } cur = new(cur.Shares + s, cur.SharesRed + r, cur.SharesWhite + w, cur.SharesDormant + d); var reason = row.Reason; if (reason == "auto") { reason = "Automatische Nachzeichnung"; } else if (reason == "transfer") { if (row.ToMgNr == Member.MgNr) { reason = $"Übertragen von {row.FromMember.AdministrativeName} (MgNr. {row.FromMgNr})"; } else { reason = $"Übertragen an {row.ToMember.AdministrativeName} (MgNr. {row.ToMgNr})"; } } tbl.AddCell(NewTd($"{row.Date:dd.MM.yyyy}")) .AddCell(NewTd(reason, colspan: tw)) .AddCells(NewBusinessSharesCells(new(s, r, w, d))); lastDate = row.Date; } if (Member.ExitDate != null && (!history.Any() || Member.ExitDate > history.Last().Date)) { tbl.AddCell(NewTd($"{Member.ExitDate:dd.MM.yyyy}")) .AddCell(NewTd("Austritt", colspan: tw, bold: true)) .AddCells(NewBusinessSharesCells(new())); } tbl.AddCell(NewTd(borderTop: true)) .AddCell(NewTd(lastDate == null ? "Aktuell" : $"Ab Lese {lastDate?.Year}", borderTop: true, colspan: tw, bold: true)) .AddCells(NewBusinessSharesCells(cur, isDiff: false)); return tbl; } } }