From b9a2893d80c98cb412b570b41a909122362ed82c Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Fri, 22 Dec 2023 15:30:39 +0100 Subject: [PATCH] [#17] CreditNote: Overhaul CreditNote --- Elwig/Documents/CreditNote.cs | 2 + Elwig/Documents/CreditNote.cshtml | 96 +++++++++++---------- Elwig/Documents/CreditNote.css | 21 +++-- Elwig/Documents/DeliveryConfirmation.cshtml | 6 +- Elwig/Documents/DeliveryConfirmation.css | 2 +- Elwig/Helpers/AppDbUpdater.cs | 74 ++++++++++++++-- Elwig/Helpers/Billing/BillingVariant.cs | 24 +++++- Elwig/Models/Dtos/CreditNoteData.cs | 50 ++++++++--- Elwig/Models/Entities/PaymentMember.cs | 26 +++++- Elwig/Windows/PaymentVariantsWindow.xaml.cs | 4 +- 10 files changed, 226 insertions(+), 79 deletions(-) diff --git a/Elwig/Documents/CreditNote.cs b/Elwig/Documents/CreditNote.cs index b9c11fa..71a5a1f 100644 --- a/Elwig/Documents/CreditNote.cs +++ b/Elwig/Documents/CreditNote.cs @@ -5,6 +5,7 @@ using Elwig.Models.Entities; namespace Elwig.Documents { public class CreditNote : BusinessDocument { + public PaymentMember? Payment; public Credit? Credit; public CreditNoteData Data; public string? Text; @@ -16,6 +17,7 @@ namespace Elwig.Documents { UseBillingAddress = true; ShowDateAndLocation = true; Data = data; + Payment = p; Credit = p.Credit; Aside = Aside.Replace("", "") + $"Gutschrift" + diff --git a/Elwig/Documents/CreditNote.cshtml b/Elwig/Documents/CreditNote.cshtml index da19de7..3d7fec9 100644 --- a/Elwig/Documents/CreditNote.cshtml +++ b/Elwig/Documents/CreditNote.cshtml @@ -1,3 +1,4 @@ +@using Elwig.Helpers @using RazorLight @inherits TemplatePage @model Elwig.Documents.CreditNote @@ -8,84 +9,89 @@ - + + - - - - + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - + + + + + + @foreach (var p in Model.Data.Rows) { var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1); var first = true; - //var pmt = p.Payment; - var abs = 0; // pmt?.ModAbs == null || pmt?.ModAbs == 0 ? "-" : pmt?.ModAbs.ToString("0." + string.Concat(Enumerable.Repeat('0', Model.Precision))); - var rel = 0; // pmt?.ModRel == null || pmt?.ModRel == 0 ? "-" : $"{pmt?.ModRel * 100:0.00##}"; @for (int i = 0; i < rows; i++) { - i + 1 ? "trailing" : "")"> + 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 (first) { - - - - - first = false; + @if (i == p.Buckets.Length - 1) { + var totalMod = p.TotalModifiers ?? 0; + + + } else { + } + first = false; } } + @if (Model.Payment == null) { + + + + + + } else { + + + + + + }
Lieferschein-Nr.Pos.SorteAttributGradationFlächenbindungPreisZu-/AbschlägeBetragLieferschein-Nr.Pos.SorteAttributGradationFlächenbindungPreisZu-/AbschlägeBetrag
Rel.Abs.
[°Oe][°KMW][kg][@Model.CurrencySymbol/kg][%][@Model.CurrencySymbol/kg][@Model.CurrencySymbol][°Oe][°KMW][kg][@Model.CurrencySymbol/kg][@Model.CurrencySymbol][@Model.CurrencySymbol]
@p.LsNr@p.DPNr@p.Variant@p.Attribute@($"{p.Gradation.Oe:N0}")@($"{p.Gradation.Kmw:N1}")@p.LsNr@p.DPNr@p.Variant@p.Attribute@($"{p.Gradation.Oe:N0}")@($"{p.Gradation.Kmw:N1}")@(p.Modifiers[i - 1])@p.Modifiers[i - 1]@bucket.Name:@($"{bucket.Value:N0}")@($"{bucket.Price:N4}")@bucket.Name:@($"{bucket.Value:N0}")@($"{bucket.Price:N4}")@rel@abs@($"{p.Buckets.Sum(b => b.Amount):N2}")@(totalMod == 0 ? "-" : Utils.GetSign(totalMod) + $"{Math.Abs(totalMod):N2}")@($"{p.Amount:N2}")
Gesamt:@($"{Model.Data.Rows.Sum(p => p.Amount):N2}")
Gesamt:@($"{Model.Payment.NetAmount:N2}")
diff --git a/Elwig/Documents/CreditNote.css b/Elwig/Documents/CreditNote.css index 47088b9..adc096b 100644 --- a/Elwig/Documents/CreditNote.css +++ b/Elwig/Documents/CreditNote.css @@ -1,6 +1,7 @@ -table.credit .amount.sum { - padding-bottom: 1mm; + +table.credit .mod { + padding-left: 5mm; } table.credit tbody tr:not(.first):not(.last) { @@ -8,10 +9,18 @@ table.credit tbody tr:not(.first):not(.last) { break-after: avoid; } -table.credit tbody tr.first td { - padding-top: 1mm; +table.credit tr:not(.first) td { + padding-top: 0; } -table.credit tbody tr.last td { - padding-bottom: 1mm; +table.credit tr.last td { + padding-bottom: 0; +} + +table.credit tr.sum { + font-size: 12pt; +} + +table.credit tr.sum td { + padding-top: 1mm; } diff --git a/Elwig/Documents/DeliveryConfirmation.cshtml b/Elwig/Documents/DeliveryConfirmation.cshtml index 503b5a7..bdc724e 100644 --- a/Elwig/Documents/DeliveryConfirmation.cshtml +++ b/Elwig/Documents/DeliveryConfirmation.cshtml @@ -9,7 +9,7 @@ - + @@ -35,7 +35,7 @@ - + @@ -48,7 +48,7 @@ var rows = Math.Max(p.Buckets.Length, p.Modifiers.Length + 1); var first = true; @for (int i = 0; i < rows; i++) { - i + 1 ? "trailing" : "")"> + i + 1 ? "last" : "")"> @if (first) { diff --git a/Elwig/Documents/DeliveryConfirmation.css b/Elwig/Documents/DeliveryConfirmation.css index eb356e3..23d074c 100644 --- a/Elwig/Documents/DeliveryConfirmation.css +++ b/Elwig/Documents/DeliveryConfirmation.css @@ -11,7 +11,7 @@ table.delivery-confirmation tr:not(.first) td { padding-top: 0; } -table.delivery-confirmation tr.trailing td { +table.delivery-confirmation tr.last td { padding-bottom: 0; } diff --git a/Elwig/Helpers/AppDbUpdater.cs b/Elwig/Helpers/AppDbUpdater.cs index 4d192db..42407b8 100644 --- a/Elwig/Helpers/AppDbUpdater.cs +++ b/Elwig/Helpers/AppDbUpdater.cs @@ -1,17 +1,16 @@ using Microsoft.Data.Sqlite; using System; -using System.Windows; namespace Elwig.Helpers { public static class AppDbUpdater { - public static readonly int RequiredSchemaVersion = 10; + public static readonly int RequiredSchemaVersion = 11; private static int _versionOffset = 0; private static readonly Action[] _updaters = new[] { UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3, UpdateDbSchema_3_To_4, UpdateDbSchema_4_To_5, UpdateDbSchema_5_To_6, UpdateDBSchema_6_To_7, UpdateDbSchema_7_To_8, UpdateDbSchema_8_To_9, - UpdateDbSchema_9_To_10, + UpdateDbSchema_9_To_10, UpdateDbSchema_10_To_11 }; private static void ExecuteNonQuery(SqliteConnection cnx, string sql) { @@ -47,8 +46,7 @@ namespace Elwig.Helpers { if (App.VersionMajor > major || (App.VersionMajor == major && App.VersionMinor > minor) || - (App.VersionMajor == major && App.VersionMinor == minor && App.VersionPatch > patch)) - { + (App.VersionMajor == major && App.VersionMinor == minor && App.VersionPatch > patch)) { long vers = (App.VersionMajor << 24) | (App.VersionMinor << 16) | App.VersionPatch; ExecuteNonQuery(cnx, $"PRAGMA user_version = {vers}"); } @@ -736,5 +734,71 @@ namespace Elwig.Helpers { ORDER BY year, mgnr, bucket; """); } + + private static void UpdateDbSchema_10_To_11(SqliteConnection cnx) { + // Drop columns, if they exist... + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_1"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_2"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_3"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_4"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_5"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_6"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_7"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_8"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part DROP COLUMN price_9"); + + ExecuteNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_i"); + ExecuteNonQuery(cnx, """ + CREATE TRIGGER t_payment_delivery_part_i + AFTER INSERT ON payment_delivery_part FOR EACH ROW + BEGIN + INSERT INTO payment_member (year, avnr, mgnr, net_amount) + SELECT year, NEW.avnr, mgnr, NEW.amount FROM delivery WHERE (year, did) = (NEW.year, NEW.did) + ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount; + END; + """); + + ExecuteNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_u"); + ExecuteNonQuery(cnx, """ + CREATE TRIGGER t_payment_delivery_part_u + AFTER UPDATE ON payment_delivery_part FOR EACH ROW + BEGIN + UPDATE payment_member + SET net_amount = net_amount - OLD.amount + WHERE (year, avnr, mgnr) IN (SELECT year, OLD.avnr, mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did)); + INSERT INTO payment_member (year, avnr, mgnr, net_amount) + SELECT year, NEW.avnr, mgnr, NEW.amount FROM delivery WHERE (year, did) = (NEW.year, NEW.did) + ON CONFLICT DO UPDATE SET net_amount = net_amount + excluded.net_amount; + END; + """); + + ExecuteNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_d"); + ExecuteNonQuery(cnx, """ + CREATE TRIGGER t_payment_delivery_part_d + AFTER DELETE ON payment_delivery_part FOR EACH ROW + BEGIN + UPDATE payment_member + SET net_amount = net_amount - OLD.amount + WHERE (year, avnr, mgnr) IN (SELECT year, OLD.avnr, mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did)); + END; + """); + + ExecuteNonQuery(cnx, "DROP TRIGGER t_payment_delivery_part_bucket_u"); + ExecuteNonQuery(cnx, """ + CREATE TRIGGER t_payment_delivery_part_bucket_u + AFTER UPDATE ON payment_delivery_part_bucket FOR EACH ROW + BEGIN + UPDATE payment_delivery_part + SET net_amount = net_amount - OLD.amount + WHERE (year, did, dpnr, avnr) = (OLD.year, OLD.did, OLD.dpnr, OLD.avnr); + UPDATE payment_delivery_part + SET net_amount = net_amount + NEW.amount + WHERE (year, did, dpnr, avnr) = (NEW.year, NEW.did, NEW.dpnr, NEW.avnr); + END; + """); + + ExecuteNonQuery(cnx, "ALTER TABLE payment_member DROP COLUMN amount"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_abs)) VIRTUAL"); + } } } diff --git a/Elwig/Helpers/Billing/BillingVariant.cs b/Elwig/Helpers/Billing/BillingVariant.cs index 6a0b477..cf0beac 100644 --- a/Elwig/Helpers/Billing/BillingVariant.cs +++ b/Elwig/Helpers/Billing/BillingVariant.cs @@ -11,6 +11,12 @@ namespace Elwig.Helpers.Billing { AvNr = avnr; } + public async Task Calculate() { + await DeleteInDb(); + await CalculatePrices(); + await CalculateModifiers(); + } + protected async Task DeleteInDb() { using var cnx = await AppDbContext.ConnectAsync(); using (var cmd = cnx.CreateCommand()) { @@ -27,8 +33,7 @@ namespace Elwig.Helpers.Billing { } } - public async Task CalculatePrices() { - await DeleteInDb(); + protected async Task CalculatePrices() { using var cnx = await AppDbContext.ConnectAsync(); var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, bool MinQuw, double Oe, double Kmw)>(); @@ -65,5 +70,20 @@ namespace Elwig.Helpers.Billing { await cmd.ExecuteNonQueryAsync(); } } + + protected async Task CalculateModifiers() { + using var cnx = await AppDbContext.ConnectAsync(); + using var cmd = cnx.CreateCommand(); + cmd.CommandText = $""" + INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount, mod_abs, mod_rel) + SELECT d.year, d.did, d.dpnr, {AvNr}, 0, COALESCE(m.abs, 0), COALESCE(m.rel, 0) + FROM delivery_part d + LEFT JOIN delivery_part_modifier p ON (p.year, p.did, p.dpnr) = (d.year, d.did, d.dpnr) + LEFT JOIN modifier m ON m.modid = p.modid + WHERE d.year = {Year} + ON CONFLICT DO UPDATE SET mod_abs = mod_abs + excluded.mod_abs, mod_rel = mod_rel + excluded.mod_rel; + """; + await cmd.ExecuteNonQueryAsync(); + } } } diff --git a/Elwig/Models/Dtos/CreditNoteData.cs b/Elwig/Models/Dtos/CreditNoteData.cs index addff16..09a88d2 100644 --- a/Elwig/Models/Dtos/CreditNoteData.cs +++ b/Elwig/Models/Dtos/CreditNoteData.cs @@ -1,4 +1,5 @@ using Elwig.Helpers; +using Elwig.Helpers.Billing; using Elwig.Models.Entities; using Microsoft.EntityFrameworkCore; using System; @@ -27,11 +28,11 @@ namespace Elwig.Models.Dtos { MgNr = mgnr; } - public static async Task> ForPaymentVariant(DbSet table, int year, int avnr) { + public static async Task> ForPaymentVariant(DbSet table, DbSet seasons, int year, int avnr) { return (await FromDbSet(table, year, avnr)) .GroupBy( r => new { r.Year, r.AvNr, r.MgNr, r.TgNr, r.DId, r.DPNr }, - (k, g) => new CreditNoteRow(g)) + (k, g) => new CreditNoteRow(g, seasons)) .GroupBy( r => new { r.Year, r.AvNr, r.MgNr, r.TgNr }, (k, g) => new CreditNoteData(g, k.Year, k.TgNr, mgnr: k.MgNr)) @@ -43,17 +44,20 @@ namespace Elwig.Models.Dtos { var v = avnr?.ToString() ?? "NULL"; var m = mgnr?.ToString() ?? "NULL"; return await table.FromSqlRaw($""" - SELECT d.year, c.tgnr, p.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, b.bktnr, d.sortid, b.discr, b.value, p.price, p.amount, - v.name AS variant, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw + SELECT d.year, c.tgnr, v.avnr, d.mgnr, d.did, d.lsnr, d.dpnr, d.weight, d.modifiers, + b.bktnr, d.sortid, b.discr, b.value, pb.price, pb.amount, p.net_amount, p.amount AS total_amount, + s.name AS variant, a.name AS attribute, q.name AS quality_level, d.oe, d.kmw FROM v_delivery d - JOIN wine_variety v ON d.sortid = v.sortid + JOIN wine_variety s ON s.sortid = d.sortid LEFT JOIN wine_attribute a ON a.attrid = d.attrid JOIN wine_quality_level q ON q.qualid = d.qualid LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr) - LEFT JOIN payment_delivery_part_bucket p ON (p.year, p.did, p.dpnr, p.bktnr) = (b.year, b.did, b.dpnr, b.bktnr) - LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, p.avnr, d.mgnr) - WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (p.avnr = {v} OR {v} IS NULL OR p.avnr IS NULL) AND (d.mgnr = {m} OR {m} IS NULL) - ORDER BY d.year, p.avnr, d.mgnr, d.lsnr, d.dpnr + LEFT JOIN payment_variant v ON v.year = d.year + LEFT JOIN payment_delivery_part p ON (p.year, p.did, p.dpnr, p.avnr) = (d.year, d.did, d.dpnr, v.avnr) + LEFT JOIN payment_delivery_part_bucket pb ON (pb.year, pb.did, pb.dpnr, pb.bktnr) = (b.year, b.did, b.dpnr, b.bktnr) + LEFT JOIN credit c ON (c.year, c.avnr, c.mgnr) = (d.year, v.avnr, d.mgnr) + WHERE b.value > 0 AND (d.year = {y} OR {y} IS NULL) AND (v.avnr = {v} OR {v} IS NULL) AND (d.mgnr = {m} OR {m} IS NULL) + ORDER BY d.year, v.avnr, d.mgnr, d.lsnr, d.dpnr """).ToListAsync(); } } @@ -73,28 +77,38 @@ namespace Elwig.Models.Dtos { public string QualityLevel; public (double Oe, double Kmw) Gradation; public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets; + public decimal? TotalModifiers; + public decimal? Amount; - public CreditNoteRow(IEnumerable rows) { + public CreditNoteRow(IEnumerable rows, DbSet seasons) { var f = rows.First(); Year = f.Year; TgNr = f.TgNr; MgNr = f.MgNr; + var season = seasons.Find(Year); LsNr = f.LsNr; DPNr = f.DPNr; Variant = f.Variant; Attribute = f.Attribute; - Modifiers = Array.Empty(); // TODO + var modifiers = (IEnumerable)(f.Modifiers ?? "").Split(',') + .Select(m => season?.Modifiers.FirstOrDefault(s => s.ModId == m)) + .Where(m => m != null) + .OrderBy(m => m.Ordering) + .ToList(); + Modifiers = modifiers.Select(m => m.Name).ToArray(); QualityLevel = f.QualityLevel; Gradation = (f.Oe, f.Kmw); Buckets = rows .Where(b => b.Value > 0) .OrderByDescending(b => b.BktNr) - // FIXME precision .Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value, - b.Price != null ? (decimal?)Utils.DecFromDb((long)b.Price, 4) : null, - b.Amount != null ? (decimal?)Utils.DecFromDb((long)b.Amount, 4) : null)) + b.Price != null ? season?.DecFromDb((long)b.Price) : null, + b.Amount != null ? season?.DecFromDb((long)b.Amount) : null)) .ToArray(); + Amount = f.TotalAmount != null ? season?.DecFromDb((long)f.TotalAmount) : null; + var netAmount = f.NetAmount != null ? season?.DecFromDb((long)f.NetAmount) : null; + TotalModifiers = Amount - netAmount; } } @@ -114,6 +128,10 @@ namespace Elwig.Models.Dtos { public string LsNr { get; set; } [Column("dpnr")] public int DPNr { get; set; } + [Column("weight")] + public int Weight { get; set; } + [Column("modifiers")] + public string? Modifiers { get; set; } [Column("bktnr")] public int BktNr { get; set; } [Column("sortid")] @@ -126,6 +144,10 @@ namespace Elwig.Models.Dtos { public long? Price { get; set; } [Column("amount")] public long? Amount { get; set; } + [Column("net_amount")] + public long? NetAmount { get; set; } + [Column("total_amount")] + public long? TotalAmount { get; set; } [Column("variant")] public string Variant { get; set; } [Column("attribute")] diff --git a/Elwig/Models/Entities/PaymentMember.cs b/Elwig/Models/Entities/PaymentMember.cs index 063cc86..3bda987 100644 --- a/Elwig/Models/Entities/PaymentMember.cs +++ b/Elwig/Models/Entities/PaymentMember.cs @@ -13,9 +13,33 @@ namespace Elwig.Models.Entities { [Column("mgnr")] public int MgNr { get; set; } + + [Column("net_amount")] + public long NetAmountValue { get; set; } + [NotMapped] + public decimal NetAmount { + get => Variant.Season.DecFromDb(NetAmountValue); + set => NetAmountValue = Variant.Season.DecToDb(value); + } + + [Column("mod_abs")] + public long ModAbsValue { get; set; } + [NotMapped] + public decimal ModAbs { + get => Variant.Season.DecFromDb(ModAbsValue); + set => ModAbsValue = Variant.Season.DecToDb(value); + } + + [Column("mod_rel")] + public double ModRelValue { get; set; } + [NotMapped] + public decimal ModRel { + get => (decimal)ModRelValue; + set => ModRelValue = (double)value; + } + [Column("amount")] public long AmountValue { get; set; } - [NotMapped] public decimal Amount { get => Variant.Season.DecFromDb(AmountValue); diff --git a/Elwig/Windows/PaymentVariantsWindow.xaml.cs b/Elwig/Windows/PaymentVariantsWindow.xaml.cs index ee48187..94e8abb 100644 --- a/Elwig/Windows/PaymentVariantsWindow.xaml.cs +++ b/Elwig/Windows/PaymentVariantsWindow.xaml.cs @@ -75,7 +75,7 @@ namespace Elwig.Windows { CalculateButton.IsEnabled = false; Mouse.OverrideCursor = Cursors.AppStarting; var b = new BillingVariant(v.Year, v.AvNr); - await b.CalculatePrices(); + await b.Calculate(); Mouse.OverrideCursor = null; CalculateButton.IsEnabled = true; } @@ -110,7 +110,7 @@ namespace Elwig.Windows { members = members.OrderBy(m => m.MgNr); IEnumerable list = await members.ToListAsync(); - var data = await CreditNoteData.ForPaymentVariant(Context.CreditNoteRows, v.Year, v.AvNr); + 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); using var doc = Document.Merge(list.Select(m => new CreditNote(Context, payments[m.MgNr], data[m.MgNr])
[°Oe] [°KMW][kg][kg] [kg] [kg]
@p.LsNr @p.DPNr