diff --git a/Elwig/Documents/CreditNote.cs b/Elwig/Documents/CreditNote.cs index fa278c1..bf6a16f 100644 --- a/Elwig/Documents/CreditNote.cs +++ b/Elwig/Documents/CreditNote.cs @@ -34,7 +34,7 @@ namespace Elwig.Documents { 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, LENGTH(attributes) DESC, attributes, kmw DESC, date, time, dpnr + ORDER BY sortid, attribute_prio DESC, COALESCE(attrid, '~'), kmw DESC, date, time, dpnr """).ToList(); } }} diff --git a/Elwig/Documents/DeliveryConfirmation.cs b/Elwig/Documents/DeliveryConfirmation.cs index 9eccf4c..5d01dcd 100644 --- a/Elwig/Documents/DeliveryConfirmation.cs +++ b/Elwig/Documents/DeliveryConfirmation.cs @@ -1,35 +1,34 @@ using Elwig.Helpers; using Elwig.Models; using Microsoft.EntityFrameworkCore; +using System; using System.Collections.Generic; using System.Linq; namespace Elwig.Documents { public class DeliveryConfirmation : BusinessDocument { - public int Year; + public Season Season; public IEnumerable Deliveries; public string? Text = App.Client.TextDeliveryConfirmation; public Dictionary MemberBins; public DeliveryConfirmation(AppDbContext ctx, int year, Member m, IEnumerable? deliveries = null) : base($"Anlieferungsbestätigung {year}", m) { - Year = year; + Season = ctx.Seasons.Find(year) ?? throw new ArgumentException("invalid season"); ShowDateAndLocation = true; UseBillingAddress = true; IncludeSender = true; - DocumentId = $"Anl.-Best. {Year}/{m.MgNr}"; + DocumentId = $"Anl.-Best. {Season.Year}/{m.MgNr}"; Deliveries = deliveries ?? ctx.DeliveryParts.FromSqlRaw($""" 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) = ({Year}, {m.MgNr}) - ORDER BY v.sortid, v.abgewertet ASC, - COALESCE(LENGTH(v.attributes), 0) ASC, attribute_prio DESC, COALESCE(v.attributes, '~'), - v.kmw DESC, v.lsnr, v.dpnr + WHERE (v.year, v.mgnr) = ({Season.Year}, {m.MgNr}) + ORDER BY v.sortid, v.abgewertet ASC, v.attribute_prio DESC, COALESCE(v.attrid, '~'), v.kmw DESC, v.lsnr, v.dpnr """) .ToList(); - MemberBins = ctx.GetMemberBins(Year, m.MgNr).GetAwaiter().GetResult(); + MemberBins = ctx.GetMemberBins(Season.Year, m.MgNr).GetAwaiter().GetResult(); } } } diff --git a/Elwig/Documents/DeliveryConfirmation.cshtml b/Elwig/Documents/DeliveryConfirmation.cshtml index fc3e2ac..25ddd5f 100644 --- a/Elwig/Documents/DeliveryConfirmation.cshtml +++ b/Elwig/Documents/DeliveryConfirmation.cshtml @@ -44,7 +44,7 @@ var lastSortId = ""; } @foreach (var p in Model.Deliveries) { - var bins = p.Bins.Where(b => b.Value > 0).OrderByDescending(b => b.BinNr).ToArray(); + var bins = p.Bins.Where(b => b.Value > 0).OrderByDescending(b => b.BktNr).ToArray(); var rowsBins = bins.Length; var mods = p.Modifiers.Select(m => m.Name).ToArray(); var rowsMod = mods.Length + 1; @@ -106,7 +106,7 @@ - Lese @Model.Year per @($"{Model.Date:dd.MM.yyyy}") [kg] + Lese @Model.Season.Year per @($"{Model.Date:dd.MM.yyyy}") [kg] Lieferpflicht Lieferrecht Unterliefert @@ -131,12 +131,18 @@ } var mBins = Model.MemberBins.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0 || b.Value.Item4 > 0).ToList(); var fbVars = mBins.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray(); - var fbs = mBins.Where(b => fbVars.Contains(b.Key)).OrderBy(b => b.Value.Item1); + var fbs = mBins.Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2).OrderBy(b => b.Value.Item1); + var vtr = mBins.Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2).OrderBy(b => b.Value.Item1); var rem = mBins.Where(b => !fbVars.Contains(b.Key)).OrderBy(b => b.Value.Item1); } Gesamtlieferung lt. gez. GA - @Raw(FormatRow(0, Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Year).Sum(d => d.Weight))) + @Raw(FormatRow( + 0, + Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare, + Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare, + Model.Member.Deliveries.Where(d => d.Year == Model.Season.Year).Sum(d => d.Weight) + )) @if (rem.Any()) { Sortenaufteilung: @@ -156,6 +162,15 @@ @Raw(FormatRow(2, obligation, right, sum, payment)) } + @if (vtr.Any()) { + Verträge: + } + @foreach (var (id, (name, right, obligation, sum, payment)) in vtr) { + + @name + @Raw(FormatRow(2, obligation, right, sum, payment)) + + }
diff --git a/Elwig/Documents/DeliveryNote.cshtml b/Elwig/Documents/DeliveryNote.cshtml index 7680ee5..354613a 100644 --- a/Elwig/Documents/DeliveryNote.cshtml +++ b/Elwig/Documents/DeliveryNote.cshtml @@ -112,7 +112,11 @@ } Gesamtlieferung lt. gez. GA - @Raw(FormatRow(Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Delivery.Year).Sum(d => d.Weight))) + @Raw(FormatRow( + Model.Member.BusinessShares * Model.Delivery.Season.MinKgPerBusinessShare, + Model.Member.BusinessShares * Model.Delivery.Season.MaxKgPerBusinessShare, + Model.Member.Deliveries.Where(d => d.Year == Model.Delivery.Year).Sum(d => d.Weight) + )) @if (Model.DisplayStats > 1) { diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index f093bff..f0e256b 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -192,7 +192,7 @@ namespace Elwig.Helpers { cnx ??= await ConnectAsync(); var bins = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { - cmd.CommandText = $"SELECT mgnr, bin, min_kg, max_kg FROM v_area_commitment_bin WHERE year = {year}"; + cmd.CommandText = $"SELECT mgnr, bucket, min_kg, max_kg FROM v_area_commitment_bucket WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); @@ -210,7 +210,7 @@ namespace Elwig.Helpers { cnx ??= await ConnectAsync(); var bins = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { - cmd.CommandText = $"SELECT mgnr, bin, weight FROM v_delivery_bin WHERE year = {year}"; + cmd.CommandText = $"SELECT mgnr, bucket, weight FROM v_delivery_bucket WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); @@ -228,7 +228,7 @@ namespace Elwig.Helpers { cnx ??= await ConnectAsync(); var bins = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { - cmd.CommandText = $"SELECT mgnr, bin, weight FROM v_payment_bin WHERE year = {year}"; + cmd.CommandText = $"SELECT mgnr, bucket, weight FROM v_payment_bucket WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); diff --git a/Elwig/Helpers/AppDbUpdater.cs b/Elwig/Helpers/AppDbUpdater.cs index fbe1f6b..06be37f 100644 --- a/Elwig/Helpers/AppDbUpdater.cs +++ b/Elwig/Helpers/AppDbUpdater.cs @@ -4,11 +4,11 @@ using System; namespace Elwig.Helpers { public static class AppDbUpdater { - public static readonly int RequiredSchemaVersion = 5; + public static readonly int RequiredSchemaVersion = 6; 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_1_To_2, UpdateDbSchema_2_To_3, UpdateDbSchema_3_To_4, UpdateDbSchema_4_To_5, UpdateDbSchema_5_To_6 }; private static void ExecuteNonQuery(SqliteConnection cnx, string sql) { @@ -330,5 +330,167 @@ namespace Elwig.Helpers { ORDER BY year, sortid, attrid; """); } + + private static void UpdateDbSchema_5_To_6(SqliteConnection cnx) { + ExecuteNonQuery(cnx, "DROP VIEW IF EXISTS v_area_commitment"); + + ExecuteNonQuery(cnx, "PRAGMA writable_schema = ON"); + ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute DROP COLUMN fill_lower_bins"); + ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN strict INTEGER NOT NULL CHECK (strict IN (TRUE, FALSE)) DEFAULT FALSE"); + ExecuteNonQuery(cnx, "ALTER TABLE wine_attribute ADD COLUMN fill_lower INTEGER NOT NULL CHECK (fill_lower IN (0, 1, 2)) DEFAULT 0"); + ExecuteNonQuery(cnx, "DROP VIEW v_delivery"); + ExecuteNonQuery(cnx, """ + CREATE VIEW v_delivery AS + SELECT p.year, p.did, p.dpnr, + d.date, d.time, d.zwstid, d.lnr, d.lsnr, + m.mgnr, m.family_name, m.given_name, + p.sortid, a.attrid, + p.weight, p.kmw, ROUND(p.kmw * (4.54 + 0.022 * p.kmw), 0) AS oe, p.qualid, + p.hkid, p.kgnr, p.rdnr, + p.gerebelt, p.gebunden, + p.qualid IN (SELECT l.qualid FROM wine_quality_level l WHERE NOT l.predicate AND (p.kmw >= l.min_kmw OR l.min_kmw IS NULL) ORDER BY l.min_kmw DESC LIMIT 1,100) AS abgewertet, + p.qualid NOT IN ('WEI', 'RSW', 'LDW') AS min_quw, + IIF(a.strict, COALESCE(a.fill_lower, 0), 0) AS attribute_prio, + GROUP_CONCAT(o.modid) AS modifiers, + d.comment, p.comment AS part_comment + FROM delivery_part p + JOIN delivery d ON (d.year, d.did) = (p.year, p.did) + JOIN member m ON m.mgnr = d.mgnr + LEFT JOIN wine_attribute a ON a.attrid = p.attrid + LEFT JOIN delivery_part_modifier o ON (o.year, o.did, o.dpnr) = (p.year, p.did, p.dpnr) + GROUP BY p.year, p.did, p.dpnr + ORDER BY p.year, p.did, p.dpnr, o.modid; + """); + ExecuteNonQuery(cnx, "PRAGMA writable_schema = OFF"); + + ExecuteNonQuery(cnx, "DROP VIEW v_area_commitment_bin"); + ExecuteNonQuery(cnx, "DROP VIEW v_delivery_bin"); + ExecuteNonQuery(cnx, "DROP VIEW v_payment_bin"); + + ExecuteNonQuery(cnx, "ALTER TABLE area_commitment_type DROP COLUMN max_kg_per_ha"); + ExecuteNonQuery(cnx, "ALTER TABLE area_commitment_type ADD COLUMN penalty_per_kg INTEGER DEFAULT NULL"); + ExecuteNonQuery(cnx, "ALTER TABLE area_commitment_type ADD COLUMN penalty_none INTEGER DEFAULT NULL"); + + ExecuteNonQuery(cnx, "ALTER TABLE wine_cultivation ADD COLUMN description TEXT DEFAULT NULL"); + ExecuteNonQuery(cnx, "ALTER TABLE member ADD COLUMN organic INTEGER NOT NULL CHECK (organic IN (TRUE, FALSE)) DEFAULT FALSE"); + + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN max_kg_per_ha INTEGER NOT NULL DEFAULT 10000"); + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN vat_normal REAL NOT NULL DEFAULT 0.10"); + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN vat_flatrate REAL NOT NULL DEFAULT 0.13"); + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN min_kg_per_bs INTEGER NOT NULL DEFAULT 750"); + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN max_kg_per_bs INTEGER NOT NULL DEFAULT 3000"); + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_per_kg INTEGER DEFAULT NULL"); + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_amount INTEGER DEFAULT NULL"); + ExecuteNonQuery(cnx, "ALTER TABLE season ADD COLUMN penalty_none INTEGER DEFAULT NULL"); + + ExecuteNonQuery(cnx, "DELETE FROM client_parameter WHERE param IN ('DELIVERY_RIGHT', 'DELIVERY_OBLIGATION', 'VAT_NORMAL', 'VAT_REDUCED', 'VAT_FLATRATE')"); + + ExecuteNonQuery(cnx, """ + CREATE TABLE delivery_part_bucket ( + year INTEGER NOT NULL, + did INTEGER NOT NULL, + dpnr INTEGER NOT NULL, + bktnr INTEGER NOT NULL, + + discr TEXT NOT NULL, + value INTEGER NOT NULL, + + CONSTRAINT pk_delivery_part_bucket PRIMARY KEY (year, did, dpnr, bktnr), + CONSTRAINT fk_delivery_part_bucket_delivery_part FOREIGN KEY (year, did, dpnr) REFERENCES delivery_part (year, did, dpnr) + ON UPDATE CASCADE + ON DELETE CASCADE + ) STRICT; + """); + ExecuteNonQuery(cnx, """ + INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) + SELECT year, did, dpnr, binnr, discr, value + FROM delivery_part_bin + """); + ExecuteNonQuery(cnx, "DROP TABLE delivery_part_bin"); + + ExecuteNonQuery(cnx, """ + CREATE VIEW v_area_commitment_bucket_strict AS + SELECT s.year, c.mgnr, + t.sortid || COALESCE(a.attrid, '') AS bucket, + t.sortid, a.attrid, + CAST(ROUND(SUM(area) * COALESCE(t.min_kg_per_ha, 0) / 10000.0, 0) AS INTEGER) AS min_kg, + CAST(ROUND(SUM(area) * COALESCE(a.max_kg_per_ha, s.max_kg_per_ha) / 10000.0, 0) AS INTEGER) AS max_kg, + CAST(ROUND(SUM(area) * s.max_kg_per_ha / 10000.0, 0) AS INTEGER) AS upper_max_kg + FROM season s, area_commitment c + JOIN area_commitment_type t ON t.vtrgid = c.vtrgid + LEFT JOIN wine_attribute a ON a.attrid = t.attrid + WHERE (year_from IS NULL OR year_from <= s.year) AND + (year_to IS NULL OR year_to >= s.year) + GROUP BY s.year, c.mgnr, bucket + ORDER BY s.year, c.mgnr, bucket; + """); + ExecuteNonQuery(cnx, """ + CREATE VIEW v_area_commitment_bucket AS + SELECT year, mgnr, bucket, min_kg, max_kg + FROM v_area_commitment_bucket_strict + WHERE attrid IS NOT NULL + UNION ALL + SELECT b.year, b.mgnr, b.sortid, + SUM(b.min_kg) AS min_kg, + SUM(b.upper_max_kg) AS max_kg + FROM v_area_commitment_bucket_strict b + LEFT JOIN wine_attribute a ON a.attrid = b.attrid + WHERE a.strict IS NULL OR a.strict = FALSE + GROUP BY b.year, b.mgnr, b.sortid + ORDER BY year, mgnr, bucket; + """); + ExecuteNonQuery(cnx, """ + CREATE VIEW v_delivery_bucket_strict AS + SELECT year, mgnr, + sortid || IIF(min_quw, COALESCE(attrid, ''), '_') AS bucket, + sortid, IIF(min_quw, attrid, NULL) AS attrid, + SUM(weight) AS weight, + min_quw + FROM v_delivery + GROUP BY year, mgnr, bucket + ORDER BY year, mgnr, bucket; + """); + ExecuteNonQuery(cnx, """ + CREATE VIEW v_delivery_bucket AS + SELECT year, mgnr, bucket, weight + FROM v_delivery_bucket_strict + WHERE attrid IS NOT NULL OR NOT min_quw + UNION ALL + SELECT b.year, b.mgnr, b.sortid, + SUM(b.weight) AS weight + FROM v_delivery_bucket_strict b + LEFT JOIN wine_attribute a ON a.attrid = b.attrid + WHERE min_quw AND (a.strict IS NULL OR a.strict = FALSE) + GROUP BY b.year, b.mgnr, b.sortid + ORDER BY year, mgnr, bucket; + """); + ExecuteNonQuery(cnx, """ + CREATE VIEW v_payment_bucket_strict AS + SELECT d.year, d.mgnr, + d.sortid || b.discr AS bucket, + d.sortid, IIF(b.discr IN ('', '_'), NULL, b.discr) AS attrid, + SUM(b.value) AS weight, + b.discr != '_' AS gebunden + FROM v_delivery d + LEFT JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr) + GROUP BY d.year, d.mgnr, bucket + HAVING SUM(b.value) > 0 + ORDER BY d.year, d.mgnr, bucket; + """); + ExecuteNonQuery(cnx, """ + CREATE VIEW v_payment_bucket AS + SELECT year, mgnr, bucket, weight + FROM v_payment_bucket_strict + WHERE attrid IS NOT NULL OR NOT gebunden + UNION ALL + SELECT b.year, b.mgnr, b.sortid, + SUM(b.weight) AS weight + FROM v_payment_bucket_strict b + LEFT JOIN wine_attribute a ON a.attrid = b.attrid + WHERE gebunden AND (a.strict IS NULL OR a.strict = FALSE) + GROUP BY b.year, b.mgnr, b.sortid + ORDER BY year, mgnr, bucket; + """); + } } } diff --git a/Elwig/Helpers/Billing/Billing.cs b/Elwig/Helpers/Billing/Billing.cs index 0698e7d..91826e0 100644 --- a/Elwig/Helpers/Billing/Billing.cs +++ b/Elwig/Helpers/Billing/Billing.cs @@ -11,14 +11,14 @@ namespace Elwig.Helpers.Billing { protected readonly AppDbContext Context; protected readonly Dictionary Attributes; protected readonly Dictionary Modifiers; - protected readonly Dictionary AreaComTypes; + protected readonly Dictionary AreaComTypes; public Billing(int year) { Year = year; Context = new AppDbContext(); Attributes = Context.WineAttributes.ToDictionary(a => a.AttrId, a => a.Name); Modifiers = Context.Modifiers.Where(m => m.Year == Year).ToDictionary(m => m.ModId, m => (m.Abs, m.Rel)); - AreaComTypes = Context.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.MaxKgPerHa, v.PenaltyAmount)); + AreaComTypes = Context.AreaCommitmentTypes.ToDictionary(v => v.VtrgId, v => (v.SortId, v.AttrId, v.Discriminator, v.MinKgPerHa, v.PenaltyAmount)); } public async Task FinishSeason() { @@ -33,34 +33,33 @@ namespace Elwig.Helpers.Billing { } using (var cmd = cnx.CreateCommand()) { - cmd.CommandText = $"DELETE FROM delivery_part_bin WHERE year = {Year}"; + cmd.CommandText = $"DELETE FROM delivery_part_bucket WHERE year = {Year}"; await cmd.ExecuteNonQueryAsync(); } } public async Task CalculateBins(bool allowAttrsIntoLowerBins, bool avoidUnderDeliveries, bool honorGebunden) { - var attrVals = Context.WineAttributes.ToDictionary(a => a.AttrId, a => a.FillLowerBins); - var attrForced = attrVals.Where(a => a.Value == 0).Select(a => a.Key).ToArray(); + var attrVals = Context.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower)); + var attrForced = attrVals.Where(a => a.Value.IsStrict && a.Value.FillLower == 0).Select(a => a.Key).ToArray(); using var cnx = await AppDbContext.ConnectAsync(); await Context.GetMemberRightsAndObligations(Year, 0, cnx); var inserts = new List<(int, int, int, string, int)>(); - var deliveries = new List<(int, int, int, string, int, double, string, string[], string[], bool?)>(); + var deliveries = new List<(int, int, int, string, int, double, string, string?, string[], bool?)>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" - SELECT mgnr, did, dpnr, sortid, weight, kmw, qualid, attributes, modifiers, gebunden + SELECT mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden FROM v_delivery WHERE year = {Year} ORDER BY mgnr, sortid, abgewertet ASC, {(honorGebunden ? "gebunden IS NOT NULL DESC, gebunden DESC," : "")} - COALESCE(LENGTH(attributes), 0) ASC, attribute_prio DESC, COALESCE(attributes, '~'), - kmw DESC, lsnr, dpnr + attribute_prio DESC, COALESCE(attrid, '~'), kmw DESC, lsnr, dpnr """; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { deliveries.Add(( reader.GetInt32(0), reader.GetInt32(1), reader.GetInt32(2), reader.GetString(3), reader.GetInt32(4), reader.GetDouble(5), reader.GetString(6), - reader.IsDBNull(7) ? Array.Empty() : reader.GetString(7).Split(",").Order().ToArray(), + reader.IsDBNull(7) ? null : reader.GetString(7), reader.IsDBNull(8) ? Array.Empty() : reader.GetString(8).Split(",").Order().ToArray(), reader.IsDBNull(9) ? null : reader.GetBoolean(9) )); @@ -70,7 +69,7 @@ namespace Elwig.Helpers.Billing { int lastMgNr = 0; Dictionary? rightsAndObligations = null; Dictionary used = new(); - foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attributes, modifiers, gebunden) in deliveries) { + foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden) in deliveries) { if (lastMgNr != mgnr) { rightsAndObligations = await Context.GetMemberRightsAndObligations(Year, mgnr); used = new(); @@ -87,29 +86,24 @@ namespace Elwig.Helpers.Billing { continue; } - if (attributes.Length > 2) - throw new NotSupportedException(); - int w = weight; + var attributes = attrid == null ? Array.Empty() : new string[] { attrid }; + var isStrict = attrid != null && attrVals[attrid].IsStrict; foreach (var p in Utils.Permutate(attributes, attributes.Intersect(attrForced))) { var c = p.Count(); var key = sortid + string.Join("", p); if (rightsAndObligations.ContainsKey(key)) { - int i = 4; - if (c == 1) { - i = (p.ElementAt(0) == attributes[0]) ? 2 : 3; - } else if (c == 0) { - i = 1; - } + int i = (c == 0) ? 1 : 2; var u = used.GetValueOrDefault(key, 0); var vr = Math.Max(0, Math.Min(rightsAndObligations[key].Item1 - u, w)); var vo = Math.Max(0, Math.Min(rightsAndObligations[key].Item2 - u, w)); - var v = (attributes.Length == 0 || attributes.Select(a => attrVals[a]).Min() == 2) ? vr : vo; + var v = (attributes.Length == 0 || attributes.Select(a => !attrVals[a].IsStrict ? 2 : attrVals[a].FillLower).Min() == 2) ? vr : vo; used[key] = u + v; + if (key.Length > 2 && !isStrict) used[key[..2]] = used.GetValueOrDefault(key[..2], 0) + v; inserts.Add((did, dpnr, i, key[2..], v)); w -= v; } - if (w == 0 || !allowAttrsIntoLowerBins) break; + if (w == 0 || (!allowAttrsIntoLowerBins && isStrict)) break; } inserts.Add((did, dpnr, 0, "_", w)); lastMgNr = mgnr; @@ -117,7 +111,7 @@ namespace Elwig.Helpers.Billing { using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" - INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value) + INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) VALUES {string.Join(",\n ", inserts.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '{i.Item4}', {i.Item5})"))} ON CONFLICT DO UPDATE SET discr = excluded.discr, value = value + excluded.value @@ -128,13 +122,15 @@ namespace Elwig.Helpers.Billing { if (!avoidUnderDeliveries) return; + // FIXME avoidUnderDelivery-calculations not always right! + var underDeliveries = new Dictionary<(int, string), int>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" - SELECT c.mgnr, c.bin, COALESCE(p.weight, 0) - c.min_kg AS diff - FROM v_area_commitment_bin c - LEFT JOIN v_payment_bin p ON (p.year, p.mgnr, p.bin) = (c.year, c.mgnr, c.bin) - WHERE c.year = {Year} AND LENGTH(c.bin) = 2 AND diff < 0 + SELECT c.mgnr, c.bucket, COALESCE(p.weight, 0) - c.min_kg AS diff + FROM v_area_commitment_bucket c + LEFT JOIN v_payment_bucket p ON (p.year, p.mgnr, p.bucket) = (c.year, c.mgnr, c.bucket) + WHERE c.year = {Year} AND LENGTH(c.bucket) = 2 AND diff < 0 """; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { @@ -145,10 +141,10 @@ namespace Elwig.Helpers.Billing { var fittingBins = new Dictionary<(int, string), int>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" - SELECT c.mgnr, c.bin, COALESCE(p.weight, 0) - c.min_kg AS diff - FROM v_area_commitment_bin c - LEFT JOIN v_payment_bin p ON (p.year, p.mgnr, p.bin) = (c.year, c.mgnr, c.bin) - WHERE c.year = {Year} AND LENGTH(c.bin) = 3 AND diff > 0 + SELECT c.mgnr, c.bucket, COALESCE(p.weight, 0) - c.min_kg AS diff + FROM v_area_commitment_bucket c + LEFT JOIN v_payment_bucket p ON (p.year, p.mgnr, p.bucket) = (c.year, c.mgnr, c.bucket) + WHERE c.year = {Year} AND LENGTH(c.bucket) = 3 AND diff > 0 """; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { @@ -169,8 +165,8 @@ namespace Elwig.Helpers.Billing { cmd.CommandText = $""" SELECT d.did, d.dpnr, d.weight, b.value FROM v_delivery d - JOIN delivery_part_bin b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr) AND b.discr = '{attr}' - WHERE d.year = {Year} AND mgnr = {mgnr} AND sortid = '{id}' AND attributes = '{attr}' + JOIN delivery_part_bucket b ON (b.year, b.did, b.dpnr) = (d.year, d.did, d.dpnr) AND b.discr = '{attr}' + WHERE d.year = {Year} AND mgnr = {mgnr} AND sortid = '{id}' AND attrid = '{attr}' ORDER BY kmw ASC, lsnr DESC, d.dpnr DESC """; using var reader = await cmd.ExecuteReaderAsync(); @@ -193,7 +189,7 @@ namespace Elwig.Helpers.Billing { using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" - INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value) + INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) VALUES {string.Join(",\n ", posChanges.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '', {i.Item4})"))} ON CONFLICT DO UPDATE SET value = value + excluded.value @@ -203,7 +199,7 @@ namespace Elwig.Helpers.Billing { using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" - INSERT INTO delivery_part_bin (year, did, dpnr, binnr, discr, value) + INSERT INTO delivery_part_bucket (year, did, dpnr, bktnr, discr, value) VALUES {string.Join(",\n ", negChanges.Select(i => $"({Year}, {i.Item1}, {i.Item2}, {i.Item3}, '', {i.Item4})"))} ON CONFLICT DO UPDATE SET value = excluded.value diff --git a/Elwig/Helpers/ClientParameters.cs b/Elwig/Helpers/ClientParameters.cs index 6a412cc..2ba4f86 100644 --- a/Elwig/Helpers/ClientParameters.cs +++ b/Elwig/Helpers/ClientParameters.cs @@ -56,12 +56,6 @@ namespace Elwig.Helpers { public string? EmailAddress; public string? Website; - public int DeliveryObligation; - public int DeliveryRight; - public decimal VatNormal; - public decimal VatReduced; - public decimal VatFlatRate; - public int ModeDeliveryNoteStats; public string? TextDeliveryNote; @@ -93,12 +87,6 @@ namespace Elwig.Helpers { Bic = parameters.GetValueOrDefault("CLIENT_BIC"); Iban = parameters.GetValueOrDefault("CLIENT_IBAN"); - DeliveryObligation = int.Parse(parameters["DELIVERY_OBLIGATION"] ?? ""); - DeliveryRight = int.Parse(parameters["DELIVERY_RIGHT"] ?? ""); - VatNormal = decimal.Parse((parameters["VAT_NORMAL"] ?? "").Replace(".", ",")); - VatReduced = decimal.Parse((parameters["VAT_REDUCED"] ?? "").Replace(".", ",")); - VatFlatRate = decimal.Parse((parameters["VAT_FLATRATE"] ?? "").Replace(".", ",")); - switch (parameters.GetValueOrDefault("MODE_DELIVERYNOTE_STATS", "SHORT")?.ToUpper()) { case "NONE": ModeDeliveryNoteStats = 0; break; case "GA_ONLY": ModeDeliveryNoteStats = 1; break; @@ -141,11 +129,6 @@ namespace Elwig.Helpers { ("CLIENT_USTIDNR", UstIdNr), ("CLIENT_BIC", Bic), ("CLIENT_IBAN", Iban), - ("DELIVERY_OBLIGATION", DeliveryObligation.ToString()), - ("DELIVERY_RIGHT", DeliveryRight.ToString()), - ("VAT_NORMAL", VatNormal.ToString().Replace(",", ".")), - ("VAT_REDUCED", VatReduced.ToString().Replace(",", ".")), - ("VAT_FLATRATE", VatFlatRate.ToString().Replace(",", ".")), ("MODE_DELIVERYNOTE_STATS", deliveryNoteStats), ("DOCUMENT_SENDER", Sender2), ("TEXT_DELIVERYNOTE", TextDeliveryNote), diff --git a/Elwig/Models/AreaComType.cs b/Elwig/Models/AreaComType.cs index c9e411b..70f2e56 100644 --- a/Elwig/Models/AreaComType.cs +++ b/Elwig/Models/AreaComType.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema; using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { - [Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId", "Discriminator")] + [Table("area_commitment_type"), PrimaryKey("VtrgId"), Index("SortId", "AttrId", "Discriminator", IsUnique = true)] public class AreaComType { [Column("vtrgid")] public string VtrgId { get; set; } @@ -21,18 +21,30 @@ namespace Elwig.Models { [Column("min_kg_per_ha")] public int? MinKgPerHa { get; set; } - [Column("max_kg_per_ha")] - public int? MaxKgPerHa { get; set; } + [Column("penalty_per_kg")] + public long? PenaltyPerKgValue { get; set; } + [NotMapped] + public decimal? PenaltyPerKg { + get => PenaltyPerKgValue != null ? Utils.DecFromDb(PenaltyPerKgValue.Value, 4) : null; + set => PenaltyPerKgValue = value != null ? Utils.DecToDb(value.Value, 4) : null; + } [Column("penalty_amount")] public long? PenaltyAmoutValue { get; set; } - [NotMapped] public decimal? PenaltyAmount { get => PenaltyAmoutValue != null ? Utils.DecFromDb(PenaltyAmoutValue.Value, 4) : null; set => PenaltyAmoutValue = value != null ? Utils.DecToDb(value.Value, 4) : null; } + [Column("penalty_none")] + public long? PenaltyNoneValue { get; set; } + [NotMapped] + public decimal? PenaltyNone { + get => PenaltyNoneValue != null ? Utils.DecFromDb(PenaltyNoneValue.Value, 4) : null; + set => PenaltyNoneValue = value != null ? Utils.DecToDb(value.Value, 4) : null; + } + [ForeignKey("SortId")] public virtual WineVar WineVar { get; private set; } diff --git a/Elwig/Models/Branch.cs b/Elwig/Models/Branch.cs index 5a00a48..7eb2534 100644 --- a/Elwig/Models/Branch.cs +++ b/Elwig/Models/Branch.cs @@ -1,9 +1,10 @@ using Microsoft.EntityFrameworkCore; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { - [Table("branch"), PrimaryKey("ZwstId")] + [Table("branch"), PrimaryKey("ZwstId"), Index("Name", IsUnique = true)] public class Branch { [Column("zwstid")] public string ZwstId { get; set; } diff --git a/Elwig/Models/DeliveryPart.cs b/Elwig/Models/DeliveryPart.cs index 3554e17..dd8b4e9 100644 --- a/Elwig/Models/DeliveryPart.cs +++ b/Elwig/Models/DeliveryPart.cs @@ -115,6 +115,6 @@ namespace Elwig.Models { public string OriginString => Origin.OriginString + "\n" + (Kg?.Gl != null ? $" / {Kg.Gl.Name}" : "") + (Kg != null ? $" / {Kg.AtKg.Gem.Name} / KG {Kg.AtKg.Name}" : "") + (Rd != null ? $" / Ried {Rd.Name}" : ""); [InverseProperty("Part")] - public virtual ISet Bins { get; private set; } + public virtual ISet Bins { get; private set; } } } diff --git a/Elwig/Models/DeliveryPartBin.cs b/Elwig/Models/DeliveryPartBucket.cs similarity index 75% rename from Elwig/Models/DeliveryPartBin.cs rename to Elwig/Models/DeliveryPartBucket.cs index a441760..6f0446f 100644 --- a/Elwig/Models/DeliveryPartBin.cs +++ b/Elwig/Models/DeliveryPartBucket.cs @@ -2,8 +2,8 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations.Schema; namespace Elwig.Models { - [Table("delivery_part_bin"), PrimaryKey("Year", "DId", "DPNr", "BinNr")] - public class DeliveryPartBin { + [Table("delivery_part_bucket"), PrimaryKey("Year", "DId", "DPNr", "BktNr")] + public class DeliveryPartBucket { [Column("year")] public int Year { get; set; } @@ -13,8 +13,8 @@ namespace Elwig.Models { [Column("dpnr")] public int DPNr { get; set; } - [Column("binnr")] - public int BinNr { get; set; } + [Column("bktnr")] + public int BktNr { get; set; } [Column("discr")] public string Discr { get; set; } diff --git a/Elwig/Models/Member.cs b/Elwig/Models/Member.cs index b25adac..1220fef 100644 --- a/Elwig/Models/Member.cs +++ b/Elwig/Models/Member.cs @@ -104,6 +104,9 @@ namespace Elwig.Models { [Column("buchführend")] public bool IsBuchführend { get; set; } + [Column("organic")] + public bool IsOrganic { get; set; } + [Column("funktionär")] public bool IsFunktionär { get; set; } @@ -176,9 +179,6 @@ namespace Elwig.Models { public string FullAddress => $"{Address}, {PostalDest.AtPlz.Plz} {PostalDest.AtPlz.Ort.Name}"; - public int DeliveryRight => BusinessShares * App.Client.DeliveryRight; - public int DeliveryObligation => BusinessShares * App.Client.DeliveryObligation; - public int SearchScore(IEnumerable keywords) { return Utils.GetSearchScore(new string?[] { MgNr.ToString(), diff --git a/Elwig/Models/PaymentDeliveryPartBin.cs b/Elwig/Models/PaymentDeliveryPartBucket.cs similarity index 84% rename from Elwig/Models/PaymentDeliveryPartBin.cs rename to Elwig/Models/PaymentDeliveryPartBucket.cs index 5c1e3b0..ea53b6f 100644 --- a/Elwig/Models/PaymentDeliveryPartBin.cs +++ b/Elwig/Models/PaymentDeliveryPartBucket.cs @@ -2,8 +2,8 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations.Schema; namespace Elwig.Models { - [Table("payment_delivery_part_bin"), PrimaryKey("Year", "DId", "DPNr", "BinNr", "AvNr")] - public class PaymentDeliveryPartBin { + [Table("payment_delivery_part_bucket"), PrimaryKey("Year", "DId", "DPNr", "BktNr", "AvNr")] + public class PaymentDeliveryPartBucket { [Column("year")] public int Year { get; set; } @@ -13,8 +13,8 @@ namespace Elwig.Models { [Column("dpnr")] public int DPNr { get; set; } - [Column("binnr")] - public int BinNr { get; set; } + [Column("bktnr")] + public int BktNr { get; set; } [Column("avnr")] public int AvNr { get; set; } diff --git a/Elwig/Models/Season.cs b/Elwig/Models/Season.cs index 6c6d09d..e9bbae4 100644 --- a/Elwig/Models/Season.cs +++ b/Elwig/Models/Season.cs @@ -16,17 +16,52 @@ namespace Elwig.Models { [Column("precision")] public byte Precision { get; set; } + [Column("max_kg_per_ha")] + public int MaxKgPerHa { get; set; } + + [Column("vat_normal")] + public double VatNormal { get; set; } + + [Column("vat_flatrate")] + public double VatFlatrate { get; set; } + + [Column("min_kg_per_bs")] + public int MinKgPerBusinessShare { get; set; } + + [Column("max_kg_per_bs")] + public int MaxKgPerBusinessShare { get; set; } + + [Column("penalty_per_kg")] + public long? PenaltyPerKgValue { get; set; } + [NotMapped] + public decimal? PenaltyPerKg { + get => PenaltyPerKgValue != null ? DecFromDb(PenaltyPerKgValue.Value) : null; + set => PenaltyPerKgValue = value != null ? DecToDb(value.Value) : null; + } + + [Column("penalty_amount")] + public long? PenaltyAmoutValue { get; set; } + [NotMapped] + public decimal? PenaltyAmount { + get => PenaltyAmoutValue != null ? DecFromDb(PenaltyAmoutValue.Value) : null; + set => PenaltyAmoutValue = value != null ? DecToDb(value.Value) : null; + } + + [Column("penalty_none")] + public long? PenaltyNoneValue { get; set; } + [NotMapped] + public decimal? PenaltyNone { + get => PenaltyNoneValue != null ? DecFromDb(PenaltyNoneValue.Value) : null; + set => PenaltyNoneValue = value != null ? DecToDb(value.Value) : null; + } + [Column("start_date")] public string? StartDateString { get; set; } [NotMapped] public DateOnly? StartDate { - get { - return StartDateString != null ? DateOnly.ParseExact(StartDateString, "yyyy-MM-dd") : null; - } - set { - StartDateString = value?.ToString("yyyy-MM-dd"); - } + get => StartDateString != null ? DateOnly.ParseExact(StartDateString, "yyyy-MM-dd") : null; + set => StartDateString = value?.ToString("yyyy-MM-dd"); } [Column("end_date")] @@ -34,12 +69,8 @@ namespace Elwig.Models { [NotMapped] public DateOnly? EndDate { - get { - return EndDateString != null ? DateOnly.ParseExact(EndDateString, "yyyy-MM-dd") : null; - } - set { - EndDateString = value?.ToString("yyyy-MM-dd"); - } + get => EndDateString != null ? DateOnly.ParseExact(EndDateString, "yyyy-MM-dd") : null; + set => EndDateString = value?.ToString("yyyy-MM-dd"); } [ForeignKey("CurrencyCode")] diff --git a/Elwig/Models/WineAttr.cs b/Elwig/Models/WineAttr.cs index 8013707..a4f16dd 100644 --- a/Elwig/Models/WineAttr.cs +++ b/Elwig/Models/WineAttr.cs @@ -1,8 +1,9 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations.Schema; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { - [Table("wine_attribute"), PrimaryKey("AttrId")] + [Table("wine_attribute"), PrimaryKey("AttrId"), Index("Name", IsUnique = true)] public class WineAttr { [Column("attrid")] public string AttrId { get; set; } @@ -10,14 +11,18 @@ namespace Elwig.Models { [Column("name")] public string Name { get; set; } + [Column("active")] + public bool IsActive { get; set; } + [Column("max_kg_per_ha")] public int? MaxKgPerHa { get; set; } - [Column("fill_lower_bins")] - public int FillLowerBins { get; set; } + [Column("strict")] + public bool IsStrict { get; set; } + + [Column("fill_lower")] + public int FillLower { get; set; } - [Column("active")] - public bool IsActive { get; set; } public override string ToString() { return Name; } diff --git a/Elwig/Models/WineCult.cs b/Elwig/Models/WineCult.cs index 51fd910..4c66c05 100644 --- a/Elwig/Models/WineCult.cs +++ b/Elwig/Models/WineCult.cs @@ -1,13 +1,17 @@ using Microsoft.EntityFrameworkCore; using System.ComponentModel.DataAnnotations.Schema; +using IndexAttribute = Microsoft.EntityFrameworkCore.IndexAttribute; namespace Elwig.Models { - [Table("wine_cultivation"), PrimaryKey("CultId")] + [Table("wine_cultivation"), PrimaryKey("CultId"), Index("Name", IsUnique = true)] public class WineCult { [Column("cultid")] public string CultId { get; set; } [Column("name")] public string Name { get; set; } + + [Column("description")] + public string Description { get; set; } } } diff --git a/Elwig/Windows/DeliveryConfirmationsWindow.xaml.cs b/Elwig/Windows/DeliveryConfirmationsWindow.xaml.cs index c43d211..e170610 100644 --- a/Elwig/Windows/DeliveryConfirmationsWindow.xaml.cs +++ b/Elwig/Windows/DeliveryConfirmationsWindow.xaml.cs @@ -89,18 +89,16 @@ namespace Elwig.Dialogs { FROM v_delivery v JOIN delivery_part p ON (p.year, p.did, p.dpnr) = (v.year, v.did, v.dpnr) WHERE v.year = {Year} - ORDER BY v.sortid, v.abgewertet ASC, - COALESCE(LENGTH(v.attributes), 0) ASC, attribute_prio DESC, COALESCE(v.attributes, '~'), - v.kmw DESC, v.lsnr, v.dpnr + ORDER BY v.sortid, v.abgewertet ASC, v.attribute_prio DESC, COALESCE(v.attrid, '~'), v.kmw DESC, v.lsnr, v.dpnr """) .ToListAsync(); using var doc = Document.Merge(list.Select(m => new DeliveryConfirmation(Context, Year, m, deliveries.Where(d => d.Delivery.MgNr == m.MgNr).ToList()) { - DoubleSided = true + //DoubleSided = true } )); - doc.DoubleSided = true; + //doc.DoubleSided = true; await doc.Generate(new Progress(v => { ProgressBar.Value = v; })); diff --git a/Elwig/Windows/SeasonFinishWindow.xaml b/Elwig/Windows/SeasonFinishWindow.xaml index 6f4e2f4..2c59cd2 100644 --- a/Elwig/Windows/SeasonFinishWindow.xaml +++ b/Elwig/Windows/SeasonFinishWindow.xaml @@ -27,9 +27,9 @@