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 attributeVariants) { if (value is JsonValue flatRate) { return attributeVariants.ToDictionary(e => e, _ => flatRate); } if (value is not JsonObject data) { throw new InvalidOperationException(); } Dictionary dict; if (data["default"] is JsonValue def) { dict = attributeVariants.ToDictionary(e => e, _ => def); } else { dict = []; } var variants = 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 variants) { var curve = v?.AsValue() ?? throw new InvalidOperationException(); foreach (var i in attributeVariants.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 attributeVariants.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 JsonNode GraphToJson(Graph graph, string mode) { var x = graph.DataX; var y = graph.DataY; if (y.Distinct().Count() == 1) { return JsonValue.Create(graph.DataY[0]); } var data = new JsonObject(); if (y[0] != y[1]) { data.Add(new KeyValuePair(x[0] + mode, Math.Round(y[0], graph.Precision))); } 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.Add(new KeyValuePair(x[i] + mode, Math.Round(y[i], graph.Precision))); } } if (y[^1] != y[^2]) { data.Add(new KeyValuePair(x[^1] + mode, Math.Round(y[^1], graph.Precision))); } return data; } protected static JsonObject GraphEntryToJson(GraphEntry entry) { 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"] = entry.GebundenFlatBonus; } else if (entry.GebundenGraph != null) { curve["geb"] = GraphToJson(entry.GebundenGraph, entry.Mode.ToString().ToLower()); } return curve; } public JsonObject FromGraphEntries(IEnumerable graphEntries) { 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}"; } } var data = new JsonObject { ["mode"] = "elwig", ["version"] = 1, }; if (ConsiderDelieryModifiers) data["consider_delivery_modifiers"] = true; if (ConsiderContractPenalties) data["consider_contract_penalties"] = true; if (ConsiderTotalPenalty) data["consider_total_penalty"] = true; if (ConsiderAutoBusinessShares) data["consider_auto_business_shares"] = true; data["payment"] = payment; data["curves"] = curves; return data; } } }