using Microsoft.Data.Sqlite; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Elwig.Helpers.Billing { public class Billing { protected readonly int Year; protected readonly AppDbContext Context; protected readonly Dictionary Attributes; protected readonly Dictionary Modifiers; 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.AttrId1, v.AttrId2, v.Discriminator, v.MinKgPerHa, v.MaxKgPerHa, v.PenaltyAmount)); } public async Task FinishSeason() { using var cnx = await AppDbContext.ConnectAsync(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" UPDATE season SET (start_date, end_date) = (SELECT MIN(date), MAX(date) FROM delivery WHERE year = {Year}) WHERE year = {Year} """; await cmd.ExecuteNonQueryAsync(); } using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"DELETE FROM delivery_part_bin 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(); using var cnx = await AppDbContext.ConnectAsync(); var memberOblRig = await GetMemberRightsObligations(cnx, Year); var inserts = new List<(int, int, int, string, int)>(); 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 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 """; 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(8) ? Array.Empty() : reader.GetString(8).Split(",").Order().ToArray(), reader.IsDBNull(9) ? null : reader.GetBoolean(9) )); } } int lastMgNr = 0; Dictionary? rights = null; Dictionary? obligations = null; Dictionary used = new(); foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attributes, modifiers, gebunden) in deliveries) { if (lastMgNr != mgnr) { var or = memberOblRig.GetValueOrDefault(mgnr, (new(), new())); rights = or.Item2; obligations = or.Item1; used = new(); } if ((honorGebunden && gebunden == false) || obligations == null || rights == null || obligations.Count == 0 || rights.Count == 0 || qualid == "WEI" || qualid == "RSW" || qualid == "LDW") { // Explizit als ungebunden markiert, // Mitglied hat keine Flächenbindungen, oder // Nicht mindestens Qualitätswein (QUW) // -> ungebunden inserts.Add((did, dpnr, 0, "_", weight)); continue; } if (attributes.Length > 2) throw new NotSupportedException(); int w = weight; foreach (var p in Utils.Permutate(attributes, attributes.Intersect(attrForced))) { var c = p.Count(); var key = sortid + string.Join("", p); if (rights.ContainsKey(key) && obligations.ContainsKey(key)) { int i = 4; if (c == 1) { i = (p.ElementAt(0) == attributes[0]) ? 2 : 3; } else if (c == 0) { i = 1; } var u = used.GetValueOrDefault(key, 0); var vr = Math.Max(0, Math.Min(rights[key] - u, w)); var vo = Math.Max(0, Math.Min(obligations[key] - u, w)); var v = (c == 0 || p.Select(a => attrVals[a]).Min() == 2) ? vr : vo; used[key] = u + v; inserts.Add((did, dpnr, i, key[2..], v)); w -= v; } if (w == 0 || !allowAttrsIntoLowerBins) break; } inserts.Add((did, dpnr, 0, "_", w)); lastMgNr = mgnr; } using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" INSERT INTO delivery_part_bin (year, did, dpnr, binnr, 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 """; await cmd.ExecuteNonQueryAsync(); } // TODO add second round to avoid under deliveries } public static async Task, Dictionary)>> GetMemberRightsObligations(SqliteConnection cnx, int year, int? mgnr = null) { var members = new Dictionary, Dictionary)>(); using var cmd = cnx.CreateCommand(); cmd.CommandText = $""" SELECT mgnr, t.vtrgid, SUM(COALESCE(area * min_kg_per_ha, 0)) / 10000 AS min_kg, SUM(COALESCE(area * max_kg_per_ha, 0)) / 10000 AS max_kg FROM area_commitment c JOIN area_commitment_type t ON t.vtrgid = c.vtrgid WHERE ({(mgnr == null ? "NULL" : mgnr)} IS NULL OR mgnr = {(mgnr == null ? "NULL" : mgnr)}) AND (year_from IS NULL OR year_from <= {year}) AND (year_to IS NULL OR year_to >= {year}) GROUP BY mgnr, t.vtrgid ORDER BY LENGTH(t.vtrgid) DESC, t.vtrgid """; var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var m = reader.GetInt32(0); var vtrgid = reader.GetString(1); if (!members.ContainsKey(m)) members[m] = (new(), new()); members[m].Item1[vtrgid] = reader.GetInt32(2); members[m].Item2[vtrgid] = reader.GetInt32(3); } return members; } public static async Task<(Dictionary, Dictionary)> GetMemberRightsObligations(SqliteConnection cnx, int year, int mgnr) { var members = await GetMemberRightsObligations(cnx, year, (int?)mgnr); return members.GetValueOrDefault(mgnr, (new(), new())); } public static async Task> GetMemberBinWeights(int mgnr, int year, SqliteConnection cnx) { var bins = new Dictionary(); using var cmd = cnx.CreateCommand(); cmd.CommandText = $""" SELECT bin, weight FROM v_delivery_bin WHERE (year, mgnr) = ({year}, {mgnr}) """; var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var bin = reader.GetString(0); bins[bin] = reader.GetInt32(1); } return bins; } } }