From 08108f0c18f12a83cc14027927a7c406d538111d Mon Sep 17 00:00:00 2001 From: Thomas Hilscher Date: Thu, 30 Nov 2023 01:32:11 +0100 Subject: [PATCH] [#5] MemberDataSheet: Add generating a member data sheet --- Elwig/Documents/Document.cs | 2 + Elwig/Documents/MemberDataSheet.cs | 21 ++ Elwig/Documents/MemberDataSheet.cshtml | 324 ++++++++++++++++++++++++ Elwig/Documents/MemberDataSheet.css | 82 ++++++ Elwig/Helpers/AppDbContext.cs | 25 ++ Elwig/Helpers/Utils.cs | 4 + Elwig/Windows/MemberAdminWindow.xaml | 2 +- Elwig/Windows/MemberAdminWindow.xaml.cs | 14 + 8 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 Elwig/Documents/MemberDataSheet.cs create mode 100644 Elwig/Documents/MemberDataSheet.cshtml create mode 100644 Elwig/Documents/MemberDataSheet.css diff --git a/Elwig/Documents/Document.cs b/Elwig/Documents/Document.cs index 0d8e07a..0f273ec 100644 --- a/Elwig/Documents/Document.cs +++ b/Elwig/Documents/Document.cs @@ -67,6 +67,8 @@ namespace Elwig.Documents { name = "Letterhead"; } else if (this is DeliveryConfirmation) { name = "DeliveryConfirmation"; + } else if (this is MemberDataSheet) { + name = "MemberDataSheet"; } else { throw new InvalidOperationException("Invalid document object"); } diff --git a/Elwig/Documents/MemberDataSheet.cs b/Elwig/Documents/MemberDataSheet.cs new file mode 100644 index 0000000..4296288 --- /dev/null +++ b/Elwig/Documents/MemberDataSheet.cs @@ -0,0 +1,21 @@ +using Elwig.Helpers; +using Elwig.Models.Entities; +using System; +using System.Collections.Generic; + +namespace Elwig.Documents { + public class MemberDataSheet : BusinessDocument { + + public Season Season; + public int Year = 2023; + public Dictionary MemberBuckets; + public Dictionary BucketAreas; + + public MemberDataSheet(Member m, AppDbContext ctx) : base($"Stammdatenblatt {m.AdministrativeName}", m) { + DocumentId = $"Stammdatenblatt {m.MgNr}"; + Season = ctx.Seasons.Find(Year) ?? throw new ArgumentException("invalid season"); + MemberBuckets = ctx.GetMemberBuckets(Year, m.MgNr).GetAwaiter().GetResult(); + BucketAreas = ctx.GetMemberBucketAreas(Year, m.MgNr).GetAwaiter().GetResult(); + } + } +} \ No newline at end of file diff --git a/Elwig/Documents/MemberDataSheet.cshtml b/Elwig/Documents/MemberDataSheet.cshtml new file mode 100644 index 0000000..af70cfb --- /dev/null +++ b/Elwig/Documents/MemberDataSheet.cshtml @@ -0,0 +1,324 @@ +@using RazorLight +@inherits TemplatePage +@model Elwig.Documents.MemberDataSheet +@{ + Layout = "BusinessDocument"; +} + +
+

@Model.Title

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @foreach (var e in Model.Member.EmailAddresses) { + var emailNumber = Model.Member.EmailAddresses.Count > 1 ? 1 : 0; + + + + + emailNumber++; + } + @foreach (var k in Model.Member.TelephoneNumbers) { + + @if (k.Type.Equals("landline")) { + + } else if (k.Type.Equals("mobile")) { + + } else if (k.Type.Equals("fax")) { + + } + + + } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Persönliche Daten
Mitglieds-Nr.@Model.Member.MgNrVorgänger MgNr.@(Model.Member.PredecessorMgNr != null ? Model.Member.PredecessorMgNr : "-")
Titel (vorangestellt)@Model.Member.PrefixTitel (nachgestellt)@Model.Member.Suffix
Vorname@Model.Member.GivenNameNachname@Model.Member.FamilyName
Weitere Vornamen@Model.Member.MiddleName
Geburtsjahr/-tag@(Model.Member.Birthday == null ? "-" : string.Join('.', Model.Member.Birthday.Split('-').Reverse()))
Anschrift
Adresse@Model.Member.Address
PLZ/Ort@Model.Member.PostalDest.AtPlz?.Plz @Model.Member.PostalDest.AtPlz?.Dest (@Model.Member.PostalDest.AtPlz?.Ort.Name)
Rechnungsadresse
Name@Model.Member.BillingAddress?.Name
Adresse@Model.Member.BillingAddress?.Address
PLZ/Ort@Model.Member.BillingAddress?.PostalDest.AtPlz?.Plz @Model.Member.BillingAddress?.PostalDest.AtPlz?.Dest @((Model.Member.BillingAddress != null && Model.Member.BillingAddress.PostalDest.AtPlz != null) ? "(" + Model.Member.BillingAddress?.PostalDest.AtPlz?.Ort.Name + ")" : "")
Kontaktdaten
E-Mail-Adresse @(emailNumber != 0 ? $"({emailNumber})" : "")@e.Address
Tel.-Nr. (Festnetz)Tel.-Nr. (mobil)Fax-Nr.@k.Number
Bankverbindung
IBAN@(Model.Member.Iban != null ? Elwig.Helpers.Utils.FormatIban(Model.Member.Iban) : "")
BIC@Model.Member.Bic
Betrieb
UID@Model.Member.UstIdNrBetriebs-Nr.@Model.Member.LfbisNr
Buchführend@(Model.Member.IsBuchführend ? "Ja" : "Nein")Bio@(Model.Member.IsOrganic ? "Ja" : "Nein")
Genossenschaft
Eintritt@Model.Member.EntryDateAustritt@Model.Member.ExitDate
Aktiv@(Model.Member.IsActive ? "Ja" : "Nein")Geschäftsanteile@Model.Member.BusinessShares
Volllierferant@(Model.Member.IsVollLieferant ? "Ja" : "Nein")Funktionär@(Model.Member.IsFunktionär ? "Ja" : "Nein")
Stamm-Zweigstelle@Model.Member.Branch?.NameStammgemeinde@Model.Member.DefaultKg?.Name
Kontakt via Post@(Model.Member.ContactViaPost ? "Ja" : "Nein")Kontakt via E-Mail@(Model.Member.ContactViaEmail ? "Ja" : "Nein")
+ + @{ + var areaComs = Model.Member.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

+ + + + + + + + + + + + + + + + + + + + + + + + + @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)@areaCom.Rd?.Name@areaCom.GstNr.Replace(",", ", ")@($"{areaCom.Area:N0}")@areaCom.WineCult.NameAb @areaCom.YearFrom
Gesamt@($"{Model.Member.ActiveAreaCommitments.Select(a => a.Area).Sum():N0}")
+ + + + + + + + + + + + + + + + + + + + + + + @{ + string FormatRow(int mode, int area, int obligation, int right) { + return $"" + + $"" + + $""; + } + var mBuckets = Model.MemberBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0 || b.Value.Item4 > 0).ToList(); + var fbVars = mBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray(); + var fbs = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2).OrderBy(b => b.Value.Item1); + var vtr = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2).OrderBy(b => b.Value.Item1); + } + + + @Raw(FormatRow( + 0, + 0, + Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare, + Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare + )) + + @if (fbs.Any()) { + + } + @foreach (var (id, (name, right, obligation, _, _)) in fbs) { + + + @Raw(FormatRow(2, Model.BucketAreas[id], obligation, right)) + + } + @if (vtr.Any()) { + + } + @foreach (var (id, (name, right, obligation, _, _)) in vtr) { + + + @Raw(FormatRow(2, Model.BucketAreas[id], obligation, right)) + + } + +
FlächeLieferpflichtLieferrecht
[m²][kg][kg]
{(mode == 0 || mode == 1 ? "" : area == 0 ? "-" : $"{area:N0}")}{(mode == 1 ? "" : obligation == 0 ? "-" : $"{obligation:N0}")}{(mode == 1 ? "" : right == 0 ? "-" : $"{right:N0}")}
Laut gezeichneten GA
Flächenbindungen:
@name
Verträge:
@name
+ } else { + + + + + + + + + + + + + + + + + + + + + + + + +
LieferpflichtLieferrecht
[kg][kg]
Laut gezeichneten GA@($"{Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare:N0}")@($"{Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare:N0}")
+ } + +
\ No newline at end of file diff --git a/Elwig/Documents/MemberDataSheet.css b/Elwig/Documents/MemberDataSheet.css new file mode 100644 index 0000000..27eb02f --- /dev/null +++ b/Elwig/Documents/MemberDataSheet.css @@ -0,0 +1,82 @@ +table.member { + border: 0.5pt solid black; +} + +table.member, table.area-commitements { + font-size: 10pt; +} + +table.member tbody th { + font-weight: normal; + font-style: italic; + text-align: left; +} + + +table.member thead, +table.member tbody { + border: 0.5pt solid black; +} + +table.area-commitements td { + overflow: hidden; +} + +table.area-commitements thead tr th { + font-weight: normal; + font-style: italic; +} + +table.area-commitements tbody tr.contract-type { + margin-bottom: 10px; +} + +table.area-commitements tbody td { + vertical-align: top; +} + +table.area-commitements tr.new th { + border-top: 0.5pt solid black; +} + +table.delivery-confirmation-stats { + font-size: 10pt; + break-inside: avoid; +} + +table.delivery-confirmation-stats th, +table.delivery-confirmation-stats td { + padding: 0.125mm 0; + overflow: hidden; + white-space: nowrap; +} + +table.delivery-confirmation-stats tr.subheading th { + text-align: left; +} + +table.delivery-confirmation-stats thead th { + font-weight: normal; + font-style: italic; + text-align: right; + font-size: 10pt; +} + +table.delivery-confirmation-stats thead th:first-child { + text-align: left; +} + +table.delivery-confirmation-stats td { + text-align: right; +} + +table.delivery-confirmation-stats tbody th { + font-weight: normal; + font-style: italic; + text-align: left; +} + +table.delivery-confirmation-stats tr.subheading th { + font-weight: bold; + border-top: 0.5pt solid black; +} \ No newline at end of file diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index 477e9ed..10cebc9 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -63,6 +63,7 @@ namespace Elwig.Helpers { private readonly Dictionary>> _memberRightsAndObligations = new(); private readonly Dictionary>> _memberDeliveryBuckets = new(); private readonly Dictionary>> _memberPaymentBuckets = new(); + private readonly Dictionary>> _memberBucketAreas = new(); public AppDbContext() { if (App.Config.DatabaseLog != null) { @@ -256,6 +257,24 @@ namespace Elwig.Helpers { _memberPaymentBuckets[year] = buckets; } + private async Task FetchMemberBucketAreas(int year, SqliteConnection? cnx = null) { + var ownCnx = cnx == null; + cnx ??= await ConnectAsync(); + var buckets = new Dictionary>(); + using (var cmd = cnx.CreateCommand()) { + cmd.CommandText = $"SELECT mgnr, bucket, area FROM v_area_commitment_bucket_strict WHERE year = {year}"; + using var reader = await cmd.ExecuteReaderAsync(); + while (await reader.ReadAsync()) { + var mgnr = reader.GetInt32(0); + var bucket = reader.GetString(1); + if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); + buckets[mgnr][bucket] = reader.GetInt32(2); + } + } + if (ownCnx) await cnx.DisposeAsync(); + _memberBucketAreas[year] = buckets; + } + public async Task> GetMemberRightsAndObligations(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberRightsAndObligations.ContainsKey(year)) await FetchMemberRightsAndObligations(year, cnx); @@ -274,6 +293,12 @@ namespace Elwig.Helpers { return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, new()); } + public async Task> GetMemberBucketAreas(int year, int mgnr, SqliteConnection? cnx = null) { + if (!_memberBucketAreas.ContainsKey(year)) + await FetchMemberBucketAreas(year, cnx); + return _memberBucketAreas[year].GetValueOrDefault(mgnr, new()); + } + public async Task> GetMemberBuckets(int year, int mgnr, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); diff --git a/Elwig/Helpers/Utils.cs b/Elwig/Helpers/Utils.cs index 4ed104d..cd92723 100644 --- a/Elwig/Helpers/Utils.cs +++ b/Elwig/Helpers/Utils.cs @@ -146,6 +146,10 @@ namespace Elwig.Helpers { return CalcCrc16Modbus(Encoding.ASCII.GetBytes(data)); } + public static string FormatIban(string iban) { + return Regex.Replace(iban, ".{4}", "$0 "); + } + public static void RunBackground(string title, Func a) { Task.Run(async () => { try { diff --git a/Elwig/Windows/MemberAdminWindow.xaml b/Elwig/Windows/MemberAdminWindow.xaml index 22d0948..1af994b 100644 --- a/Elwig/Windows/MemberAdminWindow.xaml +++ b/Elwig/Windows/MemberAdminWindow.xaml @@ -53,7 +53,7 @@ - + diff --git a/Elwig/Windows/MemberAdminWindow.xaml.cs b/Elwig/Windows/MemberAdminWindow.xaml.cs index 6d7ff0b..c6ca2d3 100644 --- a/Elwig/Windows/MemberAdminWindow.xaml.cs +++ b/Elwig/Windows/MemberAdminWindow.xaml.cs @@ -337,6 +337,20 @@ namespace Elwig.Windows { await PrintLetterheads(2); } + private async void Menu_Print_Memberdata_Click(object sender, RoutedEventArgs evt) { + if (MemberList.SelectedItem is not Member m) + return; + Mouse.OverrideCursor = Cursors.AppStarting; + using var doc = new MemberDataSheet(m, Context); + await doc.Generate(); + Mouse.OverrideCursor = null; + if (App.Config.Debug) { + doc.Show(); + } else { + await doc.Print(); + } + } + private void FocusSearchInput(object sender, RoutedEventArgs evt) { if (!IsEditing && !IsCreating) { SearchInput.Focus();