diff --git a/Elwig/App.xaml.cs b/Elwig/App.xaml.cs index 2c78bf4..f67678c 100644 --- a/Elwig/App.xaml.cs +++ b/Elwig/App.xaml.cs @@ -236,5 +236,13 @@ namespace Elwig { w.FocusKgNr(kgnr); return w; } + + public static PaymentVariantsWindow FocusPaymentVariantsWindow(int year) { + return FocusWindow(() => new(year), w => w.Year == year); + } + + public static ChartWindow FocusChartWindow(int year, int avnr) { + return FocusWindow(() => new(year, avnr), w => w.Year == year && w.AvNr == avnr); + } } } diff --git a/Elwig/Documents/CreditNote.cs b/Elwig/Documents/CreditNote.cs index 138c1b7..b9c11fa 100644 --- a/Elwig/Documents/CreditNote.cs +++ b/Elwig/Documents/CreditNote.cs @@ -1,40 +1,31 @@ using Elwig.Helpers; +using Elwig.Models.Dtos; using Elwig.Models.Entities; -using Microsoft.EntityFrameworkCore; -using System.Collections.Generic; -using System.Linq; namespace Elwig.Documents { public class CreditNote : BusinessDocument { - public Credit Credit; + public Credit? Credit; + public CreditNoteData Data; public string? Text; public string CurrencySymbol; - public string[] BucketNames; public int Precision; - public IEnumerable Parts; - public CreditNote(Credit c, AppDbContext ctx) : base($"Traubengutschrift Nr. {c.TgId} – {c.Payment.Variant.Name}", c.Member) { + public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data) : + base($"Traubengutschrift {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr}" : p.Member.Name)} – {p.Variant.Name}", p.Member) { UseBillingAddress = true; ShowDateAndLocation = true; - Credit = c; + Data = data; + Credit = p.Credit; Aside = Aside.Replace("", "") + $"Gutschrift" + - $"TG-Nr.{c.TgId}" + - $"Überw. am{c.Payment.Variant.TransferDate:dd.MM.yyyy}" + - $"Datum/Zeit{c.ModifiedTimestamp:dd.MM.yyyy} / {c.ModifiedTimestamp:HH:mm}" + + $"TG-Nr.{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr}" : "-")}" + + $"Überw. am{p.Variant.TransferDate:dd.MM.yyyy}" + + $"Datum/Zeit{p.Credit?.ModifiedTimestamp:dd.MM.yyyy} / {p.Credit?.ModifiedTimestamp:HH:mm}" + $""; Text = App.Client.TextDeliveryNote; - DocumentId = $"Tr.-Gutschr. {c.TgId}"; - CurrencySymbol = c.Payment.Variant.Season.Currency.Symbol ?? c.Payment.Variant.Season.Currency.Code; - BucketNames = new string[0]; // FIXME - Precision = c.Payment.Variant.Season.Precision; - Parts = ctx.DeliveryParts.FromSql($""" - SELECT p.* - FROM v_delivery v - JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (v.year, v.did, v.dpnr) - WHERE (v.year, v.mgnr) = ({c.Year}, {c.Member.MgNr}) - ORDER BY sortid, attribute_prio DESC, COALESCE(attrid, '~'), kmw DESC, date, time, dpnr - """).ToList(); + DocumentId = $"Tr.-Gutschr. " + (p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr}" : p.MgNr); + CurrencySymbol = p.Variant.Season.Currency.Symbol ?? p.Variant.Season.Currency.Code; + Precision = p.Variant.Season.Precision; } }} diff --git a/Elwig/Documents/CreditNote.cshtml b/Elwig/Documents/CreditNote.cshtml index c1593e3..e56f112 100644 --- a/Elwig/Documents/CreditNote.cshtml +++ b/Elwig/Documents/CreditNote.cshtml @@ -3,24 +3,22 @@ @model Elwig.Documents.CreditNote @{ Layout = "BusinessDocument"; } -@{ - var bucketNum = Model.BucketNames.Length; -}

@Model.Title

- - - + + + + + + + + - - - - - + @@ -30,57 +28,62 @@ - + + - - - + - @{ - string FormatRow(int? weight, decimal? amount) { - var w = weight == null || weight == 0 ? "-" : $"{weight:N0}"; - return $""; - } - string? last = null; - } - @foreach (var part in Model.Parts) { - var pmt = part.Payment; - var abs = pmt?.ModAbs == null || pmt?.ModAbs == 0 ? "-" : pmt?.ModAbs.ToString("0." + string.Concat(Enumerable.Repeat('0', Model.Precision))); - var rel = pmt?.ModRel == null || pmt?.ModRel == 0 ? "-" : $"{pmt?.ModRel * 100:0.00##}"; - - - - - - - - - - - @Raw(FormatRow(pmt?.DeliveryPart.Buckets?.ElementAtOrDefault(0)?.Value, 0)) - - - @for (int i = 1; i < bucketNum; i++) { - - - @Raw(FormatRow(pmt?.DeliveryPart.Buckets?.ElementAtOrDefault(i)?.Value, 0)) + @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" : "")"> + @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; + } } - last = part.SortId; }
Attribut Gradation Zu-/Abschläge@Raw(string.Join("
", Model.BucketNames))
FlächenbindungPreis Betrag
Abs. Rel.GewichtPreis
[°Oe] [°KMW] [@Model.CurrencySymbol/kg] [%][kg][kg] [@Model.CurrencySymbol/kg] [@Model.CurrencySymbol]
{w}{amount?.ToString("0." + string.Concat(Enumerable.Repeat('0', Model.Precision)))}
@part.Delivery.LsNr@part.DPNr@part.Variant.Name@part.Attribute?.Name@($"{part.Oe:N0}")@($"{part.Kmw:N1}")@abs@rel@($"{pmt?.Amount:N2}")
@p.LsNr@p.DPNr@p.Variant@p.Attribute@($"{p.Gradation.Oe:N0}")@($"{p.Gradation.Kmw:N1}")@abs@rel@(p.Modifiers[i - 1])@bucket.Name:@($"{bucket.Value:N0}")@($"{bucket.Price:N4}")@($"{1000:N2}")
diff --git a/Elwig/Documents/CreditNote.css b/Elwig/Documents/CreditNote.css index 145e051..aeeb9e1 100644 --- a/Elwig/Documents/CreditNote.css +++ b/Elwig/Documents/CreditNote.css @@ -62,3 +62,7 @@ table.credit tbody tr.last td { table.credit tbody tr.new { border-top: 0.5pt solid black; } + +table.credit .small { + font-size: 8pt; +} diff --git a/Elwig/Documents/DeliveryConfirmation.cshtml b/Elwig/Documents/DeliveryConfirmation.cshtml index a4a2dd0..152cd72 100644 --- a/Elwig/Documents/DeliveryConfirmation.cshtml +++ b/Elwig/Documents/DeliveryConfirmation.cshtml @@ -79,8 +79,8 @@ first = false; } - lastVariant = p.Variant; } + lastVariant = p.Variant; } Gesamt: diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index b0f8071..477e9ed 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -51,6 +51,7 @@ namespace Elwig.Helpers { public DbSet OverUnderDeliveryRows { get; private set; } public DbSet AreaComUnderDeliveryRows { get; private set; } public DbSet MemberDeliveryPerVariantRows { get; private set; } + public DbSet CreditNoteRows { get; private set; } private readonly StreamWriter? LogFile = null; public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile); diff --git a/Elwig/Helpers/AppDbUpdater.cs b/Elwig/Helpers/AppDbUpdater.cs index 2be5297..09f2b37 100644 --- a/Elwig/Helpers/AppDbUpdater.cs +++ b/Elwig/Helpers/AppDbUpdater.cs @@ -1,15 +1,16 @@ using Microsoft.Data.Sqlite; using System; +using System.Windows; namespace Elwig.Helpers { public static class AppDbUpdater { - public static readonly int RequiredSchemaVersion = 8; + public static readonly int RequiredSchemaVersion = 9; 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_5_To_6, UpdateDBSchema_6_To_7, UpdateDbSchema_7_To_8, UpdateDbSchema_8_To_9, }; private static void ExecuteNonQuery(SqliteConnection cnx, string sql) { @@ -617,5 +618,101 @@ namespace Elwig.Helpers { WHERE gkz / 10000 = 8; """); } + + private static void UpdateDbSchema_8_To_9(SqliteConnection cnx) { + ExecuteNonQuery(cnx, """ + CREATE TABLE payment_delivery_part_bucket ( + year INTEGER NOT NULL, + did INTEGER NOT NULL, + dpnr INTEGER NOT NULL, + bktnr INTEGER NOT NULL, + avnr INTEGER NOT NULL, + + price INTEGER NOT NULL, + amount INTEGER NOT NULL, + + CONSTRAINT pk_payment_delivery_part_bucket PRIMARY KEY (year, did, dpnr, bktnr, avnr), + CONSTRAINT fk_payment_delivery_part_bucket_delivery_part_bucket FOREIGN KEY (year, did, dpnr, bktnr) REFERENCES delivery_part_bucket (year, did, dpnr, bktnr) + ON UPDATE CASCADE + ON DELETE CASCADE, + CONSTRAINT fk_payment_delivery_part_bucket_payment_variant FOREIGN KEY (year, avnr) REFERENCES payment_variant (year, avnr) + ON UPDATE CASCADE + ON DELETE CASCADE + ) STRICT; + """); + + ExecuteNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_i"); + ExecuteNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_u"); + ExecuteNonQuery(cnx, "DROP TRIGGER IF EXISTS t_payment_delivery_part_d"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part RENAME COLUMN amount TO net_amount"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_delivery_part ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_abs)) VIRTUAL"); + + ExecuteNonQuery(cnx, """ + CREATE TRIGGER t_payment_delivery_part_bucket_i + AFTER INSERT ON payment_delivery_part_bucket FOR EACH ROW + BEGIN + INSERT INTO payment_delivery_part (year, did, dpnr, avnr, net_amount) + VALUES (NEW.year, NEW.did, NEW.dpnr, NEW.avnr, NEW.amount) + ON CONFLICT DO UPDATE SET net_amount = net_amount + NEW.amount; + END; + """); + ExecuteNonQuery(cnx, """ + CREATE TRIGGER t_payment_delivery_part_bucket_u + AFTER UPDATE OF amount 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) = (NEW.year, NEW.did, NEW.dpnr, NEW.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, """ + CREATE TRIGGER t_payment_delivery_part_bucket_d + AFTER DELETE 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); + END; + """); + + ExecuteNonQuery(cnx, "ALTER TABLE payment_member RENAME COLUMN amount TO net_amount"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN mod_abs INTEGER NOT NULL DEFAULT 0"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN mod_rel REAL NOT NULL DEFAULT 0"); + ExecuteNonQuery(cnx, "ALTER TABLE payment_member ADD COLUMN amount INTEGER NOT NULL GENERATED ALWAYS AS (ROUND(net_amount * (1 + mod_rel) + mod_rel)) VIRTUAL"); + + 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) + VALUES (NEW.year, NEW.avnr, (SELECT mgnr FROM delivery WHERE (year, did) = (NEW.year, NEW.did)), NEW.amount) + ON CONFLICT DO UPDATE SET amount = amount + excluded.amount; + END; + """); + ExecuteNonQuery(cnx, """ + CREATE TRIGGER t_payment_delivery_part_u + AFTER UPDATE OF amount ON payment_delivery_part FOR EACH ROW + BEGIN + UPDATE payment_member + SET net_amount = net_amount - OLD.amount + WHERE (year, avnr, mgnr) = (NEW.year, NEW.avnr, (SELECT mgnr FROM delivery WHERE (year, did) = (NEW.year, NEW.did))); + UPDATE payment_member + SET net_amount = net_amount + NEW.amount + WHERE (year, avnr, mgnr) = (NEW.year, NEW.avnr, (SELECT mgnr FROM delivery WHERE (year, did) = (NEW.year, NEW.did))); + END; + """); + 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) = (OLD.year, OLD.avnr, (SELECT mgnr FROM delivery WHERE (year, did) = (OLD.year, OLD.did))); + END; + """); + } } } diff --git a/Elwig/Models/Dtos/CreditNoteData.cs b/Elwig/Models/Dtos/CreditNoteData.cs new file mode 100644 index 0000000..fc0e8c9 --- /dev/null +++ b/Elwig/Models/Dtos/CreditNoteData.cs @@ -0,0 +1,139 @@ +using Elwig.Helpers; +using Elwig.Models.Entities; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using System.Threading.Tasks; + +namespace Elwig.Models.Dtos { + public class CreditNoteData : DataTable { + + private static readonly (string, string, string?)[] FieldNames = new[] { + ("", "", (string?)null), // TODO + }; + + private readonly int Year; + private readonly int? TgNr; + private readonly int? AvNr; + private readonly int? MgNr; + + private CreditNoteData(IEnumerable rows, int year, int? tgnr, int? avnr = null, int? mgnr = null) : + base($"Traubengutschrift {year}/{tgnr}", rows, FieldNames) { + Year = year; + TgNr = tgnr; + AvNr = avnr; + MgNr = mgnr; + } + + public static async Task> ForPaymentVariant(DbSet table, 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)) + .GroupBy( + r => new { r.Year, r.AvNr, r.MgNr, r.TgNr }, + (k, g) => new CreditNoteData(g, k.Year, k.TgNr, mgnr: k.MgNr)) + .ToDictionary(d => d.MgNr ?? 0); + } + + private static async Task> FromDbSet(DbSet table, int? year = null, int? avnr = null, int? mgnr = null) { + var y = year?.ToString() ?? "NULL"; + 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 + FROM v_delivery d + JOIN wine_variety v ON d.sortid = v.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 + """).ToListAsync(); + } + } + + public class CreditNoteRow { + + public int Year; + public int? TgNr; + public int AvNr; + public int MgNr; + + public string LsNr; + public int DPNr; + public string Variant; + public string? Attribute; + public string[] Modifiers; + public string QualityLevel; + public (double Oe, double Kmw) Gradation; + public (string Name, int Value, decimal? Price, decimal? Amount)[] Buckets; + + public CreditNoteRow(IEnumerable rows) { + var f = rows.First(); + Year = f.Year; + TgNr = f.TgNr; + MgNr = f.MgNr; + + LsNr = f.LsNr; + DPNr = f.DPNr; + Variant = f.Variant; + Attribute = f.Attribute; + Modifiers = Array.Empty(); // TODO + QualityLevel = f.QualityLevel; + Gradation = (f.Oe, f.Kmw); + Buckets = rows + .Where(b => b.Value > 0) + .OrderByDescending(b => b.BktNr) + .Select(b => (b.Discr == "_" ? "ungeb." : $"geb. {f.SortId}{b.Discr}", b.Value, + b.Price != null ? (decimal?)Utils.DecFromDb((long)b.Price, 0) : null, + b.Amount != null ? (decimal?)Utils.DecFromDb((long)b.Amount, 0) : null)) + .ToArray(); + } + } + + [Keyless] + public class CreditNoteRowSingle { + [Column("year")] + public int Year { get; set; } + [Column("tgnr")] + public int? TgNr { get; set; } + [Column("avnr")] + public int? AvNr { get; set; } + [Column("mgnr")] + public int MgNr { get; set; } + [Column("did")] + public int DId { get; set; } + [Column("lsnr")] + public string LsNr { get; set; } + [Column("dpnr")] + public int DPNr { get; set; } + [Column("bktnr")] + public int BktNr { get; set; } + [Column("sortid")] + public string SortId { get; set; } + [Column("discr")] + public string Discr { get; set; } + [Column("value")] + public int Value { get; set; } + [Column("price")] + public long? Price { get; set; } + [Column("amount")] + public long? Amount { get; set; } + [Column("variant")] + public string Variant { get; set; } + [Column("attribute")] + public string? Attribute { get; set; } + [Column("quality_level")] + public string QualityLevel { get; set; } + [Column("oe")] + public double Oe { get; set; } + [Column("kmw")] + public double Kmw { get; set; } + } +} diff --git a/Elwig/Windows/ChartWindow.xaml b/Elwig/Windows/ChartWindow.xaml index c6515b1..f0be64b 100644 --- a/Elwig/Windows/ChartWindow.xaml +++ b/Elwig/Windows/ChartWindow.xaml @@ -1,4 +1,5 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + +