From 9eb013ce11f5b40746f860c06d473592abe27eb9 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Wed, 17 Jan 2024 14:57:45 +0100 Subject: [PATCH] PaymentVariantsWindow: Add possibility to switch options on/off --- Elwig/Documents/CreditNote.cs | 5 + Elwig/Documents/CreditNote.cshtml | 4 + Elwig/Helpers/AppDbUpdater.cs | 2 +- Elwig/Helpers/Billing/BillingData.cs | 38 +++++- Elwig/Helpers/Billing/BillingVariant.cs | 41 +++--- .../Resources/Schemas/PaymentVariantData.json | 4 + Elwig/Resources/Sql/12-13.sql | 31 +++++ Elwig/Windows/PaymentVariantsWindow.xaml | 25 +++- Elwig/Windows/PaymentVariantsWindow.xaml.cs | 124 ++++++++++++++++-- Tests/fetch-resources.bat | 2 +- 10 files changed, 242 insertions(+), 34 deletions(-) create mode 100644 Elwig/Resources/Sql/12-13.sql diff --git a/Elwig/Documents/CreditNote.cs b/Elwig/Documents/CreditNote.cs index 01445bb..082f3d6 100644 --- a/Elwig/Documents/CreditNote.cs +++ b/Elwig/Documents/CreditNote.cs @@ -17,6 +17,7 @@ namespace Elwig.Documents { public int Precision; public string MemberModifier; public IEnumerable<(string Name, int Kg, decimal Amount)>? MemberUnderDeliveries; + public decimal MemberTotalUnderDelivery; public CreditNote(AppDbContext ctx, PaymentMember p, CreditNoteData data, Dictionary? underDeliveries = null) : base($"{Name} {(p.Credit != null ? $"Nr. {p.Credit.Year}/{p.Credit.TgNr:000}" : p.Member.Name)} – {p.Variant.Name}", p.Member) { @@ -32,6 +33,10 @@ namespace Elwig.Documents { } else { MemberModifier = "Sonstige Zu-/Abschläge"; } + var total = data.Rows.SelectMany(r => r.Buckets).Sum(b => b.Value); + var totalUnderDelivery = total - p.Member.BusinessShares * season.MinKgPerBusinessShare; + MemberTotalUnderDelivery = totalUnderDelivery < 0 ? totalUnderDelivery * (season.PenaltyPerKg ?? 0) - (season.PenaltyAmount ?? 0) : 0; + if (total == 0) MemberTotalUnderDelivery -= (season.PenaltyNone ?? 0); Aside = Aside.Replace("", "") + $"Gutschrift" + $"TG-Nr.{(p.Credit != null ? $"{p.Credit.Year}/{p.Credit.TgNr:000}" : "-")}" + diff --git a/Elwig/Documents/CreditNote.cshtml b/Elwig/Documents/CreditNote.cshtml index 2d2b1ac..97061e5 100644 --- a/Elwig/Documents/CreditNote.cshtml +++ b/Elwig/Documents/CreditNote.cshtml @@ -136,6 +136,10 @@ } penalty = Math.Round(penalty, 2, MidpointRounding.AwayFromZero); } + @if (Model.MemberTotalUnderDelivery != 0) { + @Raw(FormatRow("Unterlieferung (GA)", Model.MemberTotalUnderDelivery, add: true)); + penalty += Math.Round(Model.MemberTotalUnderDelivery, 2, MidpointRounding.AwayFromZero); + } @if (Model.Credit == null) { @Raw(FormatRow("Auszahlungsbetrag", (Model.Payment?.Amount + penalty) ?? (sum + penalty), bold: true)) diff --git a/Elwig/Helpers/AppDbUpdater.cs b/Elwig/Helpers/AppDbUpdater.cs index d7b6a8c..1ae2150 100644 --- a/Elwig/Helpers/AppDbUpdater.cs +++ b/Elwig/Helpers/AppDbUpdater.cs @@ -9,7 +9,7 @@ namespace Elwig.Helpers { public static class AppDbUpdater { // Don't forget to update value in Tests/fetch-resources.bat! - public static readonly int RequiredSchemaVersion = 12; + public static readonly int RequiredSchemaVersion = 13; private static int VersionOffset = 0; diff --git a/Elwig/Helpers/Billing/BillingData.cs b/Elwig/Helpers/Billing/BillingData.cs index 02cfc8d..eff6cd9 100644 --- a/Elwig/Helpers/Billing/BillingData.cs +++ b/Elwig/Helpers/Billing/BillingData.cs @@ -23,12 +23,44 @@ namespace Elwig.Helpers.Billing { Schema = await JsonSchema.FromJsonAsync(stream ?? throw new ArgumentException("JSON schema not found")); } + public readonly JsonObject Data; + private readonly CalculationMode Mode; - private readonly JsonObject Data; private readonly Dictionary Curves; private readonly Dictionary PaymentData; private readonly Dictionary QualityData; + public bool ConsiderDelieryModifiers { + get => GetConsider("consider_delivery_modifiers"); + set => SetConsider(value, "consider_delivery_modifiers"); + } + public bool ConsiderContractPenalties { + get => GetConsider("consider_contract_penalties"); + set => SetConsider(value, "consider_contract_penalties"); + } + public bool ConsiderTotalPenalty { + get => GetConsider("consider_total_penalty"); + set => SetConsider(value, "consider_total_penalty"); + } + public bool ConsiderAutoBusinessShares { + get => GetConsider("consider_auto_business_shares"); + set => SetConsider(value, "consider_total_penalty"); + } + + private bool GetConsider(string name, string? wgMasterName = null) { + return ((Mode == CalculationMode.Elwig) ? Data[name] : Data[wgMasterName ?? ""])?.AsValue().GetValue() ?? false; + } + + private void SetConsider(bool value, string name, string? wgMasterName = null) { + if (Mode == CalculationMode.WgMaster && wgMasterName == null) { + return; + } else if (value) { + Data[(Mode == CalculationMode.Elwig) ? name : wgMasterName ?? ""] = value; + } else { + Data.Remove((Mode == CalculationMode.Elwig) ? name : wgMasterName ?? ""); + } + } + public BillingData(JsonObject data, IEnumerable attributeVariants) { if (attributeVariants.Any(e => e.Any(c => c < 'A' || c > 'Z'))) throw new ArgumentException("Invalid attributeVariants"); @@ -51,6 +83,10 @@ namespace Elwig.Helpers.Billing { } } + public static BillingData FromJson(string json) { + return FromJson(json, []); + } + public static BillingData FromJson(string json, IEnumerable attributeVariants) { return new(ParseJson(json), attributeVariants); } diff --git a/Elwig/Helpers/Billing/BillingVariant.cs b/Elwig/Helpers/Billing/BillingVariant.cs index b0bb84f..8d8f108 100644 --- a/Elwig/Helpers/Billing/BillingVariant.cs +++ b/Elwig/Helpers/Billing/BillingVariant.cs @@ -10,10 +10,19 @@ namespace Elwig.Helpers.Billing { protected readonly int AvNr; protected readonly PaymentVar PaymentVariant; + protected readonly BillingData Data; public BillingVariant(int year, int avnr) : base(year) { AvNr = avnr; PaymentVariant = Context.PaymentVariants.Find(Year, AvNr) ?? throw new ArgumentException("PaymentVar not found"); + var attrVariants = Context.DeliveryParts + .Where(d => d.Year == Year) + .Select(d => $"{d.SortId}{d.AttrId}") + .Distinct() + .ToList() + .Union(Context.WineVarieties.Select(v => v.SortId)) + .ToList(); + Data = BillingData.FromJson(PaymentVariant.Data, attrVariants); } public async Task Calculate() { @@ -22,7 +31,8 @@ namespace Elwig.Helpers.Billing { await DeleteInDb(cnx); await SetCalcTime(cnx); await CalculatePrices(cnx); - await CalculateModifiers(cnx); + if (Data.ConsiderDelieryModifiers) + await CalculateDeliveryModifiers(cnx); await CalculateMemberModifiers(cnx); await tx.CommitAsync(); } @@ -39,7 +49,11 @@ namespace Elwig.Helpers.Billing { 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, - ROUND(COALESCE(u.total_penalty, 0) / POW(10, 4 - 2)) AS modifiers, + ROUND( + IIF({Data.ConsiderContractPenalties}, COALESCE(u.total_penalty, 0) / POW(10, 4 - 2), 0) + + IIF({Data.ConsiderTotalPenalty}, COALESCE(b.total_penalty, 0), 0) + + IIF({Data.ConsiderAutoBusinessShares}, 0, 0) + ) AS modifiers, lc.modifiers AS prev_modifiers FROM season s JOIN payment_variant v ON v.year = s.year @@ -62,6 +76,14 @@ namespace Elwig.Helpers.Billing { 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) + LEFT JOIN (SELECT s.year, u.mgnr, + (COALESCE(IIF(u.weight = 0, -s.penalty_none, 0), 0) + + COALESCE(IIF(u.diff < 0, -s.penalty_amount, 0), 0) + + COALESCE(u.diff * s.penalty_per_kg, 0) + ) / POW(10, s.precision - 2) AS total_penalty + FROM v_total_under_delivery u + JOIN season s ON s.year = u.year + WHERE u.diff < 0) b ON (b.year, b.mgnr) = (s.year, m.mgnr) WHERE s.year = {Year} AND v.avnr = {AvNr}; UPDATE payment_variant SET test_variant = FALSE WHERE (year, avnr) = ({Year}, {AvNr}); @@ -119,17 +141,6 @@ namespace Elwig.Helpers.Billing { } protected async Task CalculatePrices(SqliteConnection cnx) { - var jsonData = PaymentVariant.Data; - var attrVariants = Context.DeliveryParts - .Where(d => d.Year == Year) - .Select(d => $"{d.SortId}{d.AttrId}") - .Distinct() - .ToList() - .Union(Context.WineVarieties.Select(v => v.SortId)) - .ToList(); - - var data = BillingData.FromJson(jsonData, attrVariants); - var parts = new List<(int Year, int DId, int DPNr, int BktNr, string SortId, string Discr, int Value, double Oe, double Kmw, string QualId)>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" @@ -151,7 +162,7 @@ namespace Elwig.Helpers.Billing { var inserts = new List<(int Year, int DId, int DPNr, int BktNr, long Price, long Amount)>(); foreach (var part in parts) { var attrId = (part.Discr == "_" || part.Discr == "") ? null : part.Discr; - var price = data.CalculatePrice(part.SortId, attrId, part.QualId, part.Discr != "_", part.Oe, part.Kmw); + var price = Data.CalculatePrice(part.SortId, attrId, part.QualId, part.Discr != "_", part.Oe, part.Kmw); var priceL = PaymentVariant.Season.DecToDb(price); inserts.Add((part.Year, part.DId, part.DPNr, part.BktNr, priceL, priceL * part.Value)); } @@ -162,7 +173,7 @@ namespace Elwig.Helpers.Billing { """); } - protected async Task CalculateModifiers(SqliteConnection cnx) { + protected async Task CalculateDeliveryModifiers(SqliteConnection cnx) { await AppDbContext.ExecuteBatch(cnx, $""" 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) diff --git a/Elwig/Resources/Schemas/PaymentVariantData.json b/Elwig/Resources/Schemas/PaymentVariantData.json index 3deabd0..8cc658d 100644 --- a/Elwig/Resources/Schemas/PaymentVariantData.json +++ b/Elwig/Resources/Schemas/PaymentVariantData.json @@ -8,6 +8,10 @@ "properties": { "mode": {"enum": ["elwig"]}, "version": {"enum": [1]}, + "consider_delivery_modifiers": {"type": "boolean"}, + "consider_contract_penalties": {"type": "boolean"}, + "consider_total_penalty": {"type": "boolean"}, + "consider_auto_business_shares": {"type": "boolean"}, "payment": {"$ref": "#/definitions/payment_1"}, "quality": {"$ref": "#/definitions/quality_1"}, "curves": { diff --git a/Elwig/Resources/Sql/12-13.sql b/Elwig/Resources/Sql/12-13.sql new file mode 100644 index 0000000..a21b477 --- /dev/null +++ b/Elwig/Resources/Sql/12-13.sql @@ -0,0 +1,31 @@ +-- schema version 11 to 12 + +ALTER TABLE season ADD COLUMN bs_value INTEGER; + +CREATE TABLE member_history ( + mgnr INTEGER NOT NULL, + date TEXT NOT NULL CHECK (date REGEXP '^[1-9][0-9]{3}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$') DEFAULT CURRENT_DATE, + + business_shares INTEGER NOT NULL, + type TEXT NOT NULL CHECK (type REGEXP '^[a-z_]+$'), + comment TEXT DEFAULT NULL, + + CONSTRAINT pk_member_history PRIMARY KEY (mgnr, date), + CONSTRAINT fk_member_history_member FOREIGN KEY (mgnr) REFERENCES member (mgnr) + ON UPDATE CASCADE + ON DELETE CASCADE +) STRICT; + +CREATE VIEW v_total_under_delivery AS +SELECT s.year, m.mgnr, m.business_shares, + m.business_shares * s.min_kg_per_bs AS min_kg, + m.business_shares * s.max_kg_per_bs AS max_kg, + COALESCE(d.sum, 0) AS weight, + IIF(COALESCE(d.sum, 0) < m.business_shares * s.min_kg_per_bs, + COALESCE(d.sum, 0) - m.business_shares * s.min_kg_per_bs, + IIF(COALESCE(d.sum, 0) > m.business_shares * s.max_kg_per_bs, + COALESCE(d.sum, 0) - m.business_shares * s.max_kg_per_bs, + 0)) AS diff +FROM member m, season s + LEFT JOIN v_stat_member d ON (d.year, d.mgnr) = (s.year, m.mgnr) +ORDER BY s.year, m.mgnr; diff --git a/Elwig/Windows/PaymentVariantsWindow.xaml b/Elwig/Windows/PaymentVariantsWindow.xaml index cbba99d..345037f 100644 --- a/Elwig/Windows/PaymentVariantsWindow.xaml +++ b/Elwig/Windows/PaymentVariantsWindow.xaml @@ -65,6 +65,12 @@ VerticalAlignment="Center" HorizontalAlignment="Right" Width="25" Height="25" Margin="5,60,5,0" Grid.RowSpan="2" Click="DeleteButton_Click"/> + +