using Newtonsoft.Json; using NJsonSchema; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Reflection; using System.Text.Json.Nodes; using System.Threading.Tasks; namespace Elwig.Helpers.Billing { public class BillingData { public enum CalculationMode { Elwig, WgMaster } public enum CurveMode { Oe, Kmw } public record struct Curve(CurveMode Mode, Dictionary Normal, Dictionary? Gebunden); public static JsonSchema? Schema { get; private set; } public static async Task Init() { var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Elwig.Resources.Schemas.PaymentVariantData.json"); Schema = await JsonSchema.FromJsonAsync(stream ?? throw new ArgumentException("JSON schema not found")); } public readonly JsonObject Data; public readonly CalculationMode Mode; 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_auto_business_shares"); } 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) { Data = data; var mode = Data["mode"]?.GetValue(); Mode = (mode == "elwig") ? CalculationMode.Elwig : CalculationMode.WgMaster; } protected static JsonObject ParseJson(string json) { if (Schema == null) throw new InvalidOperationException("Schema has to be initialized first"); try { var errors = Schema.Validate(json); if (errors.Count != 0) throw new ArgumentException("Invalid JSON data"); return JsonNode.Parse(json)?.AsObject() ?? throw new ArgumentException("Invalid JSON data"); } catch (JsonReaderException) { throw new ArgumentException("Invalid JSON data"); } } public static BillingData FromJson(string json) { return new(ParseJson(json)); } protected JsonArray GetCurvesEntry() { return Data[Mode == CalculationMode.Elwig ? "curves" : "Kurven"]?.AsArray() ?? throw new InvalidOperationException(); } protected JsonNode GetPaymentEntry() { return Data[Mode == CalculationMode.Elwig ? "payment" : "AuszahlungSorten"] ?? throw new InvalidOperationException(); } protected JsonObject? GetQualityEntry() { return Data[Mode == CalculationMode.Elwig ? "quality" : "AuszahlungSortenQualitätsstufe"]?.AsObject(); } private static Dictionary GetCurveData(JsonObject data, CurveMode mode) { var dict = new Dictionary(); foreach (var (index, price) in data) { double idx; bool? gtlt = index.StartsWith('>') ? true : index.StartsWith('<') ? false : null; if (index.EndsWith("kmw")) { idx = double.Parse(index[(gtlt != null ? 1 : 0)..^3], CultureInfo.InvariantCulture); if (mode == CurveMode.Oe) { idx = Utils.KmwToOe(idx); } } else if (index.EndsWith("oe")) { idx = double.Parse(index[(gtlt != null ? 1 : 0)..^2], CultureInfo.InvariantCulture); if (mode == CurveMode.Kmw) { idx = Utils.OeToKmw(idx); } } else { throw new InvalidOperationException(); } if (gtlt == true) { idx = Math.BitIncrement(idx); } else if (gtlt == false) { idx = Math.BitDecrement(idx); } dict[idx] = price?.AsValue().GetValue() ?? throw new InvalidOperationException(); } return dict; } protected Dictionary GetCurves() { var dict = new Dictionary(); var curves = GetCurvesEntry(); foreach (var c in curves) { var obj = c?.AsObject() ?? throw new InvalidOperationException(); var id = obj["id"]?.GetValue() ?? throw new InvalidOperationException(); var cMode = (obj["mode"]?.GetValue() == "kmw") ? CurveMode.Kmw : CurveMode.Oe; double quw = cMode == CurveMode.Oe ? 73 : 15; Dictionary c1; Dictionary? c2 = null; var norm = obj["data"]; if (norm is JsonObject) { c1 = GetCurveData(norm.AsObject(), cMode); } else if (norm?.AsValue().TryGetValue(out decimal v) == true) { c1 = new() { { quw, v } }; } else { throw new InvalidOperationException(); } var geb = obj["geb"]; if (geb is JsonObject) { c2 = GetCurveData(geb.AsObject(), cMode); } else if (geb?.AsValue().TryGetValue(out decimal v) == true) { var splitVal = GetCurveValueAt(c1, quw); c2 = c1.ToDictionary(e => e.Key, e => e.Value + (e.Key >= quw ? v : 0)); c2[quw] = splitVal + v; c2[Math.BitDecrement(quw)] = splitVal; } dict.Add(id, new(cMode, c1, c2)); } return dict; } protected static Dictionary GetSelection(JsonNode value, IEnumerable vaributes) { if (value is JsonValue flatRate) { return vaributes.ToDictionary(e => e, _ => flatRate); } if (value is not JsonObject data) { throw new InvalidOperationException(); } Dictionary dict; if (data["default"] is JsonValue def) { dict = vaributes.ToDictionary(e => e, _ => def); } else { dict = []; } var varieties = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length == 2); var attributes = data.Where(p => p.Key.StartsWith('/')); var others = data.Where(p => !p.Key.StartsWith('/') && p.Key.Length > 2 && p.Key != "default"); foreach (var (idx, v) in varieties) { var curve = v?.AsValue() ?? throw new InvalidOperationException(); foreach (var i in vaributes.Where(e => e.StartsWith(idx[..^1]))) { dict[i] = curve; } } foreach (var (idx, v) in attributes) { var curve = v?.AsValue() ?? throw new InvalidOperationException(); foreach (var i in vaributes.Where(e => e[2..] == idx[1..])) { dict[i] = curve; } } foreach (var (idx, v) in others) { var curve = v?.AsValue() ?? throw new InvalidOperationException(); dict[idx.Replace("/", "")] = curve; } return dict; } public static decimal GetCurveValueAt(Dictionary curve, double key) { if (curve.Count == 1) return curve.First().Value; var lt = curve.Keys.Where(v => v <= key); var gt = curve.Keys.Where(v => v >= key); if (!lt.Any()) { return curve[gt.Min()]; } else if (!gt.Any()) { return curve[lt.Max()]; } var max = lt.Max(); var min = gt.Min(); if (max == min) return curve[key]; var p1 = ((decimal)key - (decimal)min) / ((decimal)max - (decimal)min); var p2 = 1 - p1; return curve[min] * p2 + curve[max] * p1; } protected static JsonObject GraphToJson(Graph graph, string mode) { var x = graph.DataX; var y = graph.DataY; var prec = graph.Precision; try { return new JsonObject() { ["15kmw"] = Math.Round(y.Distinct().Single(), prec) }; } catch { } var data = new JsonObject(); if (y[0] != y[1]) { data[$"{x[0]}{mode}"] = Math.Round(y[0], prec); } for (int i = 1; i < x.Length - 1; i++) { 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], prec); } return data; } 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(), }; curve["data"] = GraphToJson(entry.DataGraph, entry.Mode.ToString().ToLower()); if (entry.GebundenFlatBonus != null) { curve["geb"] = (decimal)entry.GebundenFlatBonus; } else if (entry.GebundenGraph != null) { curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower()); } return curve; } protected static void CollapsePaymentData(JsonObject data, IEnumerable vaributes, bool useDefault = true) { 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 >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) { foreach (var k in ks) data.Remove(k); data["default"] = v; CollapsePaymentData(data, vaributes, useDefault); return; } } foreach (var (v, ks) in rev2) { if ((ks.Count >= vaributes.Count() * 0.5 && useDefault) || ks.Count == vaributes.Count()) { foreach (var k in ks) data.Remove(k); data["default"] = v; CollapsePaymentData(data, vaributes, useDefault); 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 = vaributes.Count(e => e.EndsWith(idx)); foreach (var (v, ks) in rev1) { var myKs = ks.Where(k => k.EndsWith(idx)).ToList(); if (myKs.Count > 1 && ((myKs.Count >= len * 0.5 && useDefault) || myKs.Count == len)) { 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 * 0.5 && useDefault) || myKs.Count == len)) { foreach (var k in myKs) data.Remove(k); data[idx] = v; } } } } public static JsonObject FromGraphEntries( IEnumerable graphEntries, BillingData? origData = null, IEnumerable? vaributes = null, bool useDefaultPayment = true, bool useDefaultQuality = true ) { 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.Vaributes) { if (entry.Abgewertet) { qualityWei[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone(); } else { payment[$"{c.Variety?.SortId}/{c.Attribute?.AttrId}"] = node.DeepClone(); } } } CollapsePaymentData(payment, vaributes ?? payment.Select(e => e.Key).ToList(), useDefaultPayment); CollapsePaymentData(qualityWei, vaributes ?? qualityWei.Select(e => e.Key).ToList(), useDefaultQuality); var data = new JsonObject { ["mode"] = "elwig", ["version"] = 1, }; if (origData?.ConsiderDelieryModifiers == true) data["consider_delivery_modifiers"] = true; if (origData?.ConsiderContractPenalties == true) data["consider_contract_penalties"] = true; if (origData?.ConsiderTotalPenalty == true) data["consider_total_penalty"] = true; if (origData?.ConsiderAutoBusinessShares == true) data["consider_auto_business_shares"] = true; if (payment.Count == 0) { data["payment"] = 0; } else if (payment.Count == 1 && payment.First().Key == "default") { data["payment"] = payment.Single().Value?.DeepClone(); } else { data["payment"] = payment; } if (qualityWei.Count == 1 && qualityWei.First().Key == "default") { data["quality"] = new JsonObject() { ["WEI"] = qualityWei.Single().Value?.DeepClone() }; } else if (qualityWei.Count >= 1) { data["quality"] = new JsonObject() { ["WEI"] = qualityWei }; } data["curves"] = curves; return data; } } }