From 8e71e82efc83e532fb033c38c61cd03d9c986985 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Sat, 23 Dec 2023 21:27:26 +0100 Subject: [PATCH] CreditNote: Display under deliveries of area commitments --- Elwig/Documents/CreditNote.cs | 18 +++++++- Elwig/Documents/CreditNote.cshtml | 36 +++++++++++++-- Elwig/Helpers/AppDbContext.cs | 50 ++++++++++++++++----- Elwig/Helpers/Billing/BillingVariant.cs | 15 +++++-- Elwig/Windows/PaymentVariantsWindow.xaml.cs | 3 +- 5 files changed, 100 insertions(+), 22 deletions(-) diff --git a/Elwig/Documents/CreditNote.cs b/Elwig/Documents/CreditNote.cs index 31160bd..544d2e0 100644 --- a/Elwig/Documents/CreditNote.cs +++ b/Elwig/Documents/CreditNote.cs @@ -1,6 +1,7 @@ using Elwig.Helpers; using Elwig.Models.Dtos; using Elwig.Models.Entities; +using System.Collections.Generic; using System.Linq; namespace Elwig.Documents { @@ -13,8 +14,9 @@ namespace Elwig.Documents { public string CurrencySymbol; public int Precision; public string MemberModifier; + public IEnumerable<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries; - public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data) : + public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data, Dictionary? underDeliveries = null) : base($"Traubengutschrift {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.Name)} – {p.Variant.Name}", p.Member) { UseBillingAddress = true; ShowDateAndLocation = true; @@ -38,5 +40,19 @@ namespace Elwig.Documents { DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : p.MgNr); CurrencySymbol = season.Currency.Symbol ?? season.Currency.Code; Precision = season.Precision; + + var variants = ctx.WineVarieties.ToDictionary(v => v.SortId, v => v); + var attributes = ctx.WineAttributes.ToDictionary(a => a.AttrId, a => a); + var comTypes = ctx.AreaCommitmentTypes.ToDictionary(t => t.VtrgId, t => t); + MemberUnderDeliveries = underDeliveries? + .OrderBy(u => u.Key) + .Select(u => ( + variants[u.Key[..2]].Name + (u.Key.Length > 2 ? " " + attributes[u.Key[2..]].Name : ""), + u.Value.Diff, + u.Value.Diff * (comTypes[u.Key].PenaltyPerKg ?? 0) + - (comTypes[u.Key].PenaltyAmount ?? 0) + - ((u.Value.Weight == 0 ? comTypes[u.Key].PenaltyNone : null) ?? 0))) + .Where(u => u.Item3 != 0) + .ToList(); } }} diff --git a/Elwig/Documents/CreditNote.cshtml b/Elwig/Documents/CreditNote.cshtml index 4e78032..3d98f80 100644 --- a/Elwig/Documents/CreditNote.cshtml +++ b/Elwig/Documents/CreditNote.cshtml @@ -79,12 +79,12 @@ } @{ - string FormatRow(string name, decimal? value, bool add = false, bool halfLine = true, bool bold = false) { + string FormatRow(string name, decimal? value, bool add = false, bool halfLine = true, bool bold = false, bool subCat = false) { return $"" + $"" - + $"{name}:" - + $"{(value < 0 ? "–" : (add ? "+" : ""))}" - + $"" + + $"{name}:" + + $"{(value < 0 ? "–" : (add ? "+" : ""))}" + + $"" + $"{Model.CurrencySymbol}{Math.Abs(value ?? 0):N2}" + $"\n"; } @@ -116,5 +116,33 @@ } } + + @{ decimal penalty = 0; } + + @if (Model.MemberUnderDeliveries != null && Model.MemberUnderDeliveries.Count() > 0) { + + + Anfallende Pönalen durch Unterlieferungen: + + + 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.Credit == null) { + @Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true)) + } else { + if (Model.Credit.Modifiers - penalty != 0) { + @Raw(FormatRow("Weitere Abzüge", Model.Credit.Modifiers - penalty, 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)) + } + diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index 87b2a95..06ed0b7 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -14,6 +14,7 @@ using Elwig.Models.Dtos; namespace Elwig.Helpers { public record struct AreaComBucket(int Area, int Obligation, int Right); + public record struct UnderDelivery(int Weight, int Diff); public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int Payment); public class AppDbContext : DbContext { @@ -64,10 +65,11 @@ namespace Elwig.Helpers { public static string ConnectionString => $"Data Source=\"{App.Config.DatabaseFile}\"; Foreign Keys=True; Mode=ReadWrite; Cache=Default"; - private readonly Dictionary>> _memberAreaCommitmentBuckets = new(); - private readonly Dictionary>> _memberDeliveryBuckets = new(); - private readonly Dictionary>> _memberDeliveryBucketsStrict = new(); - private readonly Dictionary>> _memberPaymentBuckets = new(); + private readonly Dictionary>> _memberAreaCommitmentBuckets = []; + private readonly Dictionary>> _memberDeliveryBuckets = []; + private readonly Dictionary>> _memberDeliveryBucketsStrict = []; + private readonly Dictionary>> _memberPaymentBuckets = []; + private readonly Dictionary>> _memberUnderDelivery = []; public AppDbContext() { if (App.Config.DatabaseLog != null) { @@ -217,7 +219,7 @@ namespace Elwig.Helpers { while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var vtrgid = reader.GetString(1); - if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); + if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][vtrgid] = new(reader.GetInt32(2), reader.GetInt32(3), reader.GetInt32(4)); } } @@ -235,7 +237,7 @@ namespace Elwig.Helpers { while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bucket = reader.GetString(1); - if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); + if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][bucket] = reader.GetInt32(2); } } @@ -253,7 +255,7 @@ namespace Elwig.Helpers { while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bucket = reader.GetString(1); - if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); + if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][bucket] = reader.GetInt32(2); } } @@ -271,7 +273,7 @@ namespace Elwig.Helpers { while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bucket = reader.GetString(1); - if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new(); + if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][bucket] = reader.GetInt32(2); } } @@ -279,28 +281,52 @@ namespace Elwig.Helpers { _memberPaymentBuckets[year] = buckets; } + private async Task FetchMemberUnderDelivery(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, weight, diff FROM v_under_delivery 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] = []; + buckets[mgnr][bucket] = new(reader.GetInt32(2), reader.GetInt32(3)); + } + } + if (ownCnx) await cnx.DisposeAsync(); + _memberUnderDelivery[year] = buckets; + } + public async Task> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberAreaCommitmentBuckets.ContainsKey(year)) await FetchMemberAreaCommitmentBuckets(year, cnx); - return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, new()); + return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberDeliveryBuckets(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberDeliveryBuckets.ContainsKey(year)) await FetchMemberDeliveryBuckets(year, cnx); - return _memberDeliveryBuckets[year].GetValueOrDefault(mgnr, new()); + return _memberDeliveryBuckets[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberDeliveryBucketsStrict(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberDeliveryBucketsStrict.ContainsKey(year)) await FetchMemberDeliveryBucketsStrict(year, cnx); - return _memberDeliveryBucketsStrict[year].GetValueOrDefault(mgnr, new()); + return _memberDeliveryBucketsStrict[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberPaymentBuckets(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberPaymentBuckets.ContainsKey(year)) await FetchMemberPaymentBuckets(year, cnx); - return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, new()); + return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, []); + } + + public async Task> GetMemberUnderDelivery(int year, int mgnr, SqliteConnection? cnx = null) { + if (!_memberUnderDelivery.ContainsKey(year)) + await FetchMemberUnderDelivery(year, cnx); + return _memberUnderDelivery[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberBuckets(int year, int mgnr, SqliteConnection? cnx = null) { diff --git a/Elwig/Helpers/Billing/BillingVariant.cs b/Elwig/Helpers/Billing/BillingVariant.cs index 7f86782..79a1797 100644 --- a/Elwig/Helpers/Billing/BillingVariant.cs +++ b/Elwig/Helpers/Billing/BillingVariant.cs @@ -23,18 +23,17 @@ namespace Elwig.Helpers.Billing { await Revert(); using var cnx = await AppDbContext.ConnectAsync(); using (var cmd = cnx.CreateCommand()) { - // TODO modifiers and prev_modifiers cmd.CommandText = $""" INSERT INTO credit (year, tgnr, mgnr, avnr, net_amount, prev_net_amount, vat, modifiers, prev_modifiers) SELECT s.year, COALESCE(t.tgnr, 0) + ROW_NUMBER() OVER(ORDER BY m.mgnr) AS tgnr, m.mgnr, v.avnr, - ROUND(p.amount / POW(10, s.precision - 2)), + ROUND(p.amount / POW(10, s.precision - 2)) AS net_amount, ROUND(lp.amount / POW(10, s.precision - 2)) AS prev_amount, IIF(m.buchführend, s.vat_normal, s.vat_flatrate) AS vat, - NULL, - NULL + ROUND(COALESCE(u.total_penalty, 0) / POW(10, 4 - 2)) AS modifiers, + lc.modifiers AS prev_modifiers FROM season s JOIN payment_variant v ON v.year = s.year LEFT JOIN payment_variant l ON l.year = s.year @@ -48,6 +47,14 @@ namespace Elwig.Helpers.Billing { JOIN member m ON m.mgnr = d.mgnr LEFT JOIN payment_member lp ON (lp.year, lp.avnr, lp.mgnr) = (l.year, l.avnr, m.mgnr) LEFT JOIN payment_member p ON (p.year, p.avnr, p.mgnr) = (v.year, v.avnr, m.mgnr) + LEFT JOIN credit lc ON (lc.year, lc.avnr, lc.mgnr) = (l.year, l.avnr, m.mgnr) + LEFT JOIN (SELECT year, mgnr, + SUM(COALESCE(IIF(u.weight = 0, -t.penalty_none, 0), 0) + + COALESCE(IIF(u.diff < 0, -t.penalty_amount, 0), 0) + + COALESCE(u.diff * t.penalty_per_kg, 0)) AS total_penalty + FROM v_under_delivery u + JOIN area_commitment_type t ON t.vtrgid = u.bucket + GROUP BY year, mgnr) u ON (u.year, u.mgnr) = (s.year, m.mgnr) WHERE s.year = {Year} AND v.avnr = {AvNr} """; await cmd.ExecuteNonQueryAsync(); diff --git a/Elwig/Windows/PaymentVariantsWindow.xaml.cs b/Elwig/Windows/PaymentVariantsWindow.xaml.cs index 8b3d9bf..c3cf985 100644 --- a/Elwig/Windows/PaymentVariantsWindow.xaml.cs +++ b/Elwig/Windows/PaymentVariantsWindow.xaml.cs @@ -140,8 +140,9 @@ namespace Elwig.Windows { IEnumerable list = await members.ToListAsync(); var data = await CreditNoteData.ForPaymentVariant(Context.CreditNoteRows, Context.Seasons, v.Year, v.AvNr); var payments = await Context.MemberPayments.Where(p => p.Year == v.Year && p.AvNr == v.AvNr).ToDictionaryAsync(c => c.MgNr); + await Context.GetMemberAreaCommitmentBuckets(Year, 0); using var doc = Document.Merge(list.Select(m => - new CreditNote(Context, payments[m.MgNr], data[m.MgNr]) + new CreditNote(Context, payments[m.MgNr], data[m.MgNr], Context.GetMemberUnderDelivery(Year, m.MgNr).GetAwaiter().GetResult()) )); await doc.Generate(new Progress(v => { ProgressBar.Value = v;