diff --git a/Elwig/Helpers/Billing/BillingData.cs b/Elwig/Helpers/Billing/BillingData.cs index 9aba083..fb94b4b 100644 --- a/Elwig/Helpers/Billing/BillingData.cs +++ b/Elwig/Helpers/Billing/BillingData.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Reflection; using System.Text.Json.Nodes; using System.Threading.Tasks; +using System.Windows; namespace Elwig.Helpers.Billing { public class BillingData { @@ -206,30 +207,41 @@ namespace Elwig.Helpers.Billing { return curve[min] * p2 + curve[max] * p1; } - protected static JsonNode GraphToJson(Graph graph, string mode) { + protected static JsonObject GraphToJson(Graph graph, string mode) { var x = graph.DataX; var y = graph.DataY; - if (y.Distinct().Count() == 1) { - return JsonValue.Create((decimal)graph.DataY[0]); - } + var prec = graph.Precision; + + try { + return new JsonObject() { + ["15kmw"] = Math.Round(x.Distinct().Single(), prec) + }; + } catch { } var data = new JsonObject(); - if (y[0] != y[1]) { - data[$"{x[0]}{mode}"] = Math.Round(y[0], graph.Precision); + data[$"{x[0]}{mode}"] = Math.Round(y[0], prec); } for (int i = 1; i < x.Length - 1; i++) { - if (Math.Round(y[i] - y[i - 1], 10) != Math.Round(y[i + 1] - y[i], 10)) { - data[$"{x[i]}{mode}"] = Math.Round(y[i], graph.Precision); + var d1 = Math.Round(y[i] - y[i - 1], prec); + var d2 = Math.Round(y[i + 1] - y[i], prec); + if (d1 != d2) { + data[$"{x[i]}{mode}"] = Math.Round(y[i], prec); } } if (y[^1] != y[^2]) { - data[$"{x[^1]}{mode}"] = Math.Round(y[^1], graph.Precision); + data[$"{x[^1]}{mode}"] = Math.Round(y[^1], prec); } return data; } - protected static JsonObject GraphEntryToJson(GraphEntry entry) { + protected static JsonNode GraphEntryToJson(GraphEntry entry) { + try { + if (entry.GebundenFlatBonus == null) { + return JsonValue.Create((decimal)entry.DataGraph.DataY.Distinct().Single()); + } + } catch { } + var curve = new JsonObject { ["id"] = entry.Id, ["mode"] = entry.Mode.ToString().ToLower(), @@ -238,7 +250,7 @@ namespace Elwig.Helpers.Billing { curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower()); if (entry.GebundenFlatBonus != null) { - curve["geb"] = entry.GebundenFlatBonus; + curve["geb"] = (decimal)entry.GebundenFlatBonus; } else if (entry.GebundenGraph != null) { curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower()); } @@ -246,31 +258,122 @@ namespace Elwig.Helpers.Billing { return curve; } - public JsonObject FromGraphEntries(IEnumerable graphEntries) { + protected static void CollapsePaymentData(JsonObject data) { + Dictionary> rev1 = []; + Dictionary> rev2 = []; + foreach (var (k, v) in data) { + if (k == "default" || k.StartsWith('/') || !k.Contains('/') || v is not JsonValue val) { + continue; + } else if (val.TryGetValue(out var dec)) { + rev2[dec] = rev2.GetValueOrDefault(dec) ?? []; + rev2[dec].Add(k); + } else if (val.TryGetValue(out var cur)) { + rev1[cur] = rev1.GetValueOrDefault(cur) ?? []; + rev1[cur].Add(k); + } + } + if (!data.ContainsKey("default")) { + foreach (var (v, ks) in rev1) { + if (ks.Count >= data.Count / 2.0) { + foreach (var k in ks) data.Remove(k); + data["default"] = v; + CollapsePaymentData(data); + return; + } + } + foreach (var (v, ks) in rev2) { + if (ks.Count >= data.Count / 2.0) { + foreach (var k in ks) data.Remove(k); + data["default"] = v; + CollapsePaymentData(data); + return; + } + } + } + var attributes = data + .Select(e => e.Key) + .Where(k => k.Length > 3 && k.Contains('/')) + .Select(k => "/" + k.Split('/')[1]) + .Distinct() + .ToList(); + foreach (var idx in attributes) { + var len = data.Count(e => e.Key.EndsWith(idx)); + foreach (var (v, ks) in rev1) { + var myKs = ks.Where(k => k.EndsWith(idx)).ToList(); + if (myKs.Count > 1 && myKs.Count >= len / 2.0) { + foreach (var k in myKs) data.Remove(k); + data[idx] = v; + } + } + foreach (var (v, ks) in rev2) { + var myKs = ks.Where(k => k.EndsWith(idx)).ToList(); + if (myKs.Count > 1 && myKs.Count >= len / 2.0) { + foreach (var k in myKs) data.Remove(k); + data[idx] = v; + } + } + } + } + + public static JsonObject FromGraphEntries(IEnumerable graphEntries, BillingData? origData = null) { + var payment = new JsonObject(); + var qualityWei = new JsonObject(); + var curves = new JsonArray(); + int curveId = 0; + foreach (var entry in graphEntries) { + var curve = GraphEntryToJson(entry); + JsonValue node; + if (curve is JsonObject obj) { + obj["id"] = ++curveId; + node = JsonValue.Create($"curve:{curveId}"); + curves.Add(obj); + } else if (curve is JsonValue val && val.TryGetValue(out var flat)) { + node = JsonValue.Create(flat); + } else { + continue; + } + foreach (var c in entry.Contracts) { + if (entry.Abgewertet) { + qualityWei[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone(); + } else { + payment[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone(); + } + } + } + + CollapsePaymentData(payment); + CollapsePaymentData(qualityWei); + var data = new JsonObject { ["mode"] = "elwig", ["version"] = 1, }; - if (ConsiderDelieryModifiers) + + if (origData?.ConsiderDelieryModifiers == true) data["consider_delivery_modifiers"] = true; - if (ConsiderContractPenalties) + if (origData?.ConsiderContractPenalties == true) data["consider_contract_penalties"] = true; - if (ConsiderTotalPenalty) + if (origData?.ConsiderTotalPenalty == true) data["consider_total_penalty"] = true; - if (ConsiderAutoBusinessShares) + if (origData?.ConsiderAutoBusinessShares == true) data["consider_auto_business_shares"] = true; - var payment = new JsonObject(); - var curves = new JsonArray(); - - foreach (var entry in graphEntries) { - curves.Add(GraphEntryToJson(entry)); - foreach (var contract in entry.Contracts) { - payment[$"{contract.Variety?.SortId}/{contract.Attribute?.AttrId}"] = $"curve:{entry.Id}"; - } + if (payment.Count == 0) { + data["payment"] = 0; + } else if (payment.Count == 1) { + data["payment"] = payment.Single().Value?.DeepClone(); + } else { + data["payment"] = payment; + } + if (qualityWei.Count == 1) { + data["quality"] = new JsonObject() { + ["WEI"] = qualityWei.Single().Value + }; + } else if (qualityWei.Count > 1) { + data["quality"] = new JsonObject() { + ["WEI"] = qualityWei + }; } - - data["payment"] = payment; data["curves"] = curves; return data; diff --git a/Elwig/Helpers/Billing/GraphEntry.cs b/Elwig/Helpers/Billing/GraphEntry.cs index 1ecc40f..3647350 100644 --- a/Elwig/Helpers/Billing/GraphEntry.cs +++ b/Elwig/Helpers/Billing/GraphEntry.cs @@ -18,10 +18,11 @@ namespace Elwig.Helpers.Billing { public double? GebundenFlatBonus { get { try { - return GebundenGraph?.DataX.Zip(GebundenGraph.DataY) + var val = GebundenGraph?.DataX.Zip(GebundenGraph.DataY) .Select(e => Math.Round(e.Second - DataGraph.GetPriceAtOe(e.First), Precision)) .Distinct() .Single(); + return (val == 0) ? null : val; } catch { return null; } diff --git a/Elwig/Models/Entities/WineAttr.cs b/Elwig/Models/Entities/WineAttr.cs index b074794..3f1328f 100644 --- a/Elwig/Models/Entities/WineAttr.cs +++ b/Elwig/Models/Entities/WineAttr.cs @@ -23,6 +23,13 @@ namespace Elwig.Models.Entities { [Column("fill_lower")] public int FillLower { get; set; } + public WineAttr() { } + + public WineAttr(string attrId, string name) { + AttrId = attrId; + Name = name; + } + public override string ToString() { return Name; } diff --git a/Elwig/Models/Entities/WineVar.cs b/Elwig/Models/Entities/WineVar.cs index 5cfab14..3fec595 100644 --- a/Elwig/Models/Entities/WineVar.cs +++ b/Elwig/Models/Entities/WineVar.cs @@ -21,6 +21,13 @@ namespace Elwig.Models.Entities { public bool IsRed => Type == "R"; public bool IsWhite => Type == "W"; + public WineVar() { } + + public WineVar(string sortId, string name) { + SortId = sortId; + Name = name; + } + public override string ToString() { return Name; } diff --git a/Elwig/Windows/ChartWindow.xaml.cs b/Elwig/Windows/ChartWindow.xaml.cs index b1ecaaa..34f5280 100644 --- a/Elwig/Windows/ChartWindow.xaml.cs +++ b/Elwig/Windows/ChartWindow.xaml.cs @@ -638,7 +638,7 @@ namespace Elwig.Windows { private async void SaveButton_Click(object sender, RoutedEventArgs e) { var origData = BillingData.FromJson(PaymentVar.Data); - var data = origData.FromGraphEntries(GraphEntries); + var data = BillingData.FromGraphEntries(GraphEntries, origData); EntityEntry? tr = null; try { diff --git a/Tests/HelperTests/BillingDataTest.cs b/Tests/HelperTests/BillingDataTest.cs index 5fdbf55..2c4b053 100644 --- a/Tests/HelperTests/BillingDataTest.cs +++ b/Tests/HelperTests/BillingDataTest.cs @@ -1,10 +1,13 @@ using Elwig.Helpers; using Elwig.Helpers.Billing; +using Elwig.Models.Entities; +using System.Text.Json; namespace Tests.HelperTests { [TestFixture] public class BillingDataTest { + private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true }; private static readonly string[] AttributeVariants = ["GV", "GVD", "GVK", "GVS", "GVZ", "WR", "WRS", "ZW", "ZWS", "ZWZ"]; [OneTimeSetUp] @@ -41,7 +44,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_01_Flatrate() { + public void TestRead_01_Flatrate() { var data = PaymentBillingData.FromJson(""" { "mode": "elwig", @@ -57,7 +60,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_02_Simple() { + public void TestRead_02_Simple() { var data = PaymentBillingData.FromJson(""" { "mode": "elwig", @@ -92,7 +95,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_03_GreaterThanAndLessThan() { + public void TestRead_03_GreaterThanAndLessThan() { var data = PaymentBillingData.FromJson(""" { "mode": "elwig", @@ -131,7 +134,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_04_VariantsAndAttributes() { + public void TestRead_04_VariantsAndAttributes() { var data = PaymentBillingData.FromJson(""" { "mode": "elwig", @@ -161,7 +164,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_05_QualityLevel() { + public void TestRead_05_QualityLevel() { var data = PaymentBillingData.FromJson(""" { "mode": "elwig", @@ -192,7 +195,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_06_ModeOeAndKmw() { + public void TestRead_06_ModeOeAndKmw() { var data = PaymentBillingData.FromJson(""" { "mode": "elwig", @@ -234,7 +237,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_07_MultipleCurves() { + public void TestRead_07_MultipleCurves() { var data = PaymentBillingData.FromJson(""" { "mode": "elwig", @@ -303,7 +306,7 @@ namespace Tests.HelperTests { } [Test] - public void Test_08_WgMaster() { + public void TestRead_08_WgMaster() { var data = PaymentBillingData.FromJson(""" { "mode": "wgmaster", @@ -345,5 +348,162 @@ namespace Tests.HelperTests { TestCalcOe(data, "GVK", 115, 0.065m); }); } + + private static List GetSelection(IEnumerable attVars) { + return attVars.Select(s => { + var sortid = s[..2]; + var attrid = s.Length > 2 ? s[2..] : null; + return new ContractSelection( + new WineVar(sortid, sortid), + attrid == null ? null : new WineAttr(attrid, attrid) + ); + }).ToList(); + } + + [Test] + public void TestWrite_01_Empty() { + List entries = [ + new GraphEntry(1, 4, BillingData.CurveMode.Oe, new() { + [73] = 0.5m + }, null) + ]; + var updated = BillingData.FromGraphEntries(entries); + Assert.That(updated.ToJsonString(JsonOpts), Is.EqualTo(""" + { + "mode": "elwig", + "version": 1, + "payment": 0, + "curves": [] + } + """)); + } + + [Test] + public void TestWrite_02_Flatrate() { + List entries = [ + new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { + [73] = 0.5m + }, null), GetSelection(["GV"])) + ]; + var data = BillingData.FromGraphEntries(entries); + Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" + { + "mode": "elwig", + "version": 1, + "payment": 0.5, + "curves": [] + } + """)); + } + + [Test] + public void TestWrite_03_SingleCurve() { + List entries = [ + new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { + [73] = 0.5m, + [83] = 1.0m + }, null), GetSelection(["GV"])) + ]; + var data = BillingData.FromGraphEntries(entries); + Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" + { + "mode": "elwig", + "version": 1, + "payment": "curve:1", + "curves": [ + { + "id": 1, + "mode": "oe", + "data": { + "73oe": 0.5, + "83oe": 1 + } + } + ] + } + """)); + } + + [Test] + public void TestWrite_04_Simple() { + List entries = [ + new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { + [73] = 0.5m, + [84] = 1.0m + }, null), GetSelection(["GV", "ZW"])), + new GraphEntry(10, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { + [73] = 0.75m, + }, null), GetSelection(["WR"])) + ]; + var data = BillingData.FromGraphEntries(entries); + Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" + { + "mode": "elwig", + "version": 1, + "payment": { + "WR/": 0.75, + "default": "curve:1" + }, + "curves": [ + { + "id": 1, + "mode": "oe", + "data": { + "73oe": 0.5, + "84oe": 1 + } + } + ] + } + """)); + } + + [Test] + public void TestWrite_05_Attribute() { + List entries = [ + new GraphEntry(0, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { + [73] = 0.5m, + [84] = 1.0m + }, null), GetSelection(["GVB", "ZWB"])), + new GraphEntry(2, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { + [73] = 0.75m, + }, null), GetSelection(["WR", "BL", "RR", "FV"])), + new GraphEntry(4, 4, new BillingData.Curve(BillingData.CurveMode.Oe, new() { + [73] = 0.65m, + [84] = 1.2m + }, null), GetSelection(["BP", "SA"])) + ]; + var data = BillingData.FromGraphEntries(entries); + Assert.That(data.ToJsonString(JsonOpts), Is.EqualTo(""" + { + "mode": "elwig", + "version": 1, + "payment": { + "BP/": "curve:2", + "SA/": "curve:2", + "default": 0.75, + "/B": "curve:1" + }, + "curves": [ + { + "id": 1, + "mode": "oe", + "data": { + "73oe": 0.5, + "84oe": 1 + } + }, + { + "id": 2, + "mode": "oe", + "data": { + "73oe": 0.65, + "84oe": 1.2 + } + } + ] + } + """)); + } } }