using Microsoft.EntityFrameworkCore; using Elwig.Models.Entities; using System.Linq; using System.Threading.Tasks; using System.IO; using System; using System.Windows; using Microsoft.Extensions.Logging; using Microsoft.Data.Sqlite; using System.Text.RegularExpressions; using System.Collections.Generic; using Elwig.Models.Dtos; using System.Reflection; using System.Data; namespace Elwig.Helpers { public record struct AreaComBucket(int Area, int Obligation, int Right); public record struct UnderDelivery(int Weight, int Diff); public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int DeliveryStrict, int DeliveryTotal, int Payment); public record struct MemberStat(string Variety, string Discr, int Weight); public record struct ModifierStat(string ModId, string Name, int Count, decimal? Min, decimal? Max, decimal Sum); public class AppDbContext : DbContext { public DbSet Countries { get; private set; } public DbSet Currencies { get; private set; } public DbSet Gemeinden { get; private set; } public DbSet Katastralgemeinden { get; private set; } public DbSet Orte { get; private set; } public DbSet Postleitzahlen { get; private set; } public DbSet PlzDestinations { get; private set; } public DbSet PostalDestinations { get; private set; } public DbSet WineOrigins { get; private set; } public DbSet WineQualityLevels { get; private set; } public DbSet WineVarieties { get; private set; } public DbSet ClientParameters { get; private set; } public DbSet WbGls { get; private set; } public DbSet WbGems { get; private set; } public DbSet WbKgs { get; private set; } public DbSet WbRde { get; private set; } public DbSet WineAttributes { get; private set; } public DbSet WineCultivations { get; private set; } public DbSet Branches { get; private set; } public DbSet AreaCommitmentTypes { get; private set; } public DbSet Members { get; private set; } public DbSet BillingAddresses { get; private set; } public DbSet MemberTelephoneNrs { get; private set; } public DbSet MemberEmailAddrs { get; private set; } public DbSet MemberHistory { get; private set; } public DbSet AreaCommitments { get; private set; } public DbSet Seasons { get; private set; } public DbSet DeliverySchedules { get; private set; } public DbSet DeliveryScheduleWineVarieties { get; private set; } public DbSet DeliveryAnnouncements { get; private set; } public DbSet Modifiers { get; private set; } public DbSet Deliveries { get; private set; } public DbSet DeliveryParts { get; private set; } public DbSet DeliveryPartModifiers { get; private set; } public DbSet PaymentVariants { get; private set; } public DbSet MemberPayments { get; private set; } public DbSet PaymentDeliveryParts { get; private set; } public DbSet CustomPayments { get; private set; } public DbSet Credits { get; private set; } public DbSet DeliveryPartBuckets { get; private set; } public DbSet OverUnderDeliveryRows { get; private set; } public DbSet AreaComUnderDeliveryRows { get; private set; } public DbSet MemberDeliveryPerVariantRows { get; private set; } public DbSet MemberAreaComsRows { get; private set; } public DbSet CreditNoteDeliveryRows { get; private set; } public DbSet CreditNoteRows { get; private set; } public DbSet WeightBreakDownRows { get; private set; } public DbSet PaymentVariantSummaryRows { get; private set; } private readonly StreamWriter? LogFile = null; public static DateTime LastWriteTime => File.GetLastWriteTime(App.Config.DatabaseFile); public DateTime SavedLastWriteTime { get; private set; } public bool HasBackendChanged => SavedLastWriteTime != LastWriteTime; public static string? ConnectionStringOverride { get; set; } = null; public static string ConnectionString => ConnectionStringOverride ?? $"Data Source=\"{App.Config.DatabaseFile}\"; Mode=ReadWrite; Foreign Keys=True; Cache=Default; Pooling=False"; private readonly Dictionary>> _memberAreaCommitmentBuckets = []; private readonly Dictionary>> _memberDeliveryBuckets = []; private readonly Dictionary>> _memberDeliveryBucketsStrict = []; private readonly Dictionary>> _memberPaymentBuckets = []; private readonly Dictionary>> _memberUnderDelivery = []; public AppDbContext() { if (App.Config.DatabaseLog != null) { try { var file = File.Open(App.Config.DatabaseLog, FileMode.Append, FileAccess.Write, FileShare.Write); LogFile = new(file) { AutoFlush = true }; } catch (Exception e) { MessageBox.Show($"Unable to open database log file:\n\n{e.Message}", "Database Log", MessageBoxButton.OK, MessageBoxImage.Error); } } SavedLastWriteTime = LastWriteTime; SavedChanges += OnSavedChanges; } public static SqliteConnection Connect(string? connectionString = null) { var cnx = new SqliteConnection(connectionString ?? ConnectionString); cnx.CreateFunction("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); cnx.Open(); return cnx; } public static async Task ConnectAsync(string? connectionString = null) { var cnx = new SqliteConnection(connectionString ?? ConnectionString); cnx.CreateFunction("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); await cnx.OpenAsync(); return cnx; } public static async Task ExecuteBatch(SqliteConnection cnx, string sql) { using var cmd = cnx.CreateCommand(); cmd.CommandText = sql; await (await cmd.ExecuteReaderAsync()).CloseAsync(); } public static async Task ExecuteEmbeddedScript(SqliteConnection cnx, Assembly asm, string name) { using var stream = asm.GetManifestResourceStream(name) ?? throw new FileNotFoundException("Unable to load embedded resource"); using var reader = new StreamReader(stream); await ExecuteBatch(cnx, await reader.ReadToEndAsync()); } public static async Task ExecuteScalar(SqliteConnection cnx, string sql) { using var cmd = cnx.CreateCommand(); cmd.CommandText = sql; return await cmd.ExecuteScalarAsync(); } public static async Task<(string Table, long RowId, string Parent, long FkId)[]> ForeignKeyCheck(SqliteConnection cnx) { using var cmd = cnx.CreateCommand(); cmd.CommandText = "PRAGMA foreign_key_check"; using var reader = await cmd.ExecuteReaderAsync(); var list = new List<(string, long, string, long)>(); while (await reader.ReadAsync()) { var table = reader.GetString(0); var rowid = reader.GetInt64(1); var parent = reader.GetString(2); var fkid = reader.GetInt64(3); list.Add((table, rowid, parent, fkid)); } return [.. list]; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlite(ConnectionString); optionsBuilder.UseLazyLoadingProxies(); optionsBuilder.LogTo(Log, LogLevel.Information); base.OnConfiguring(optionsBuilder); } public override void Dispose() { base.Dispose(); LogFile?.Dispose(); GC.SuppressFinalize(this); } private void OnSavedChanges(object? sender, SavedChangesEventArgs evt) { SavedLastWriteTime = LastWriteTime; } protected void Log(string msg) { LogFile?.WriteLine(msg); } public async Task MgNrExists(int mgnr) { return await Members.FindAsync(mgnr) != null; } public async Task FbNrExists(int fbnr) { return await AreaCommitments.FindAsync(fbnr) != null; } public async Task SortIdExists(string sortId) { return await WineVarieties.FindAsync(sortId) != null; } public async Task AttrIdExists(string attrId) { return await WineAttributes.FindAsync(attrId) != null; } public async Task CultIdExists(string cultId) { return await WineCultivations.FindAsync(cultId) != null; } public async Task NextMgNr() { int c = 0; (await Members.OrderBy(m => m.MgNr).Select(m => m.MgNr).ToListAsync()) .ForEach(a => { if (a <= c + 10000) c = a; }); return c + 1; } public async Task NextFbNr() { int c = 0; (await AreaCommitments.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync()) .ForEach(a => { if (a <= c + 10000) c = a; }); return c + 1; } public async Task NextLNr(DateOnly date, string zwstid) { var dateStr = date.ToString("yyyy-MM-dd"); int c = 0; (await Deliveries.Where(d => d.DateString == dateStr && d.ZwstId == zwstid).Select(d => d.LNr).ToListAsync()) .ForEach(a => { if (a <= c + 100) c = a; }); return c + 1; } public async Task NextDId(int year) { int c = 0; (await Deliveries.Where(d => d.Year == year).Select(d => d.DId).ToListAsync()) .ForEach(a => { if (a <= c + 100) c = a; }); return c + 1; } public async Task NextDPNr(int year, int did) { int c = 0; (await DeliveryParts.Where(p => p.Year == year && p.DId == did).Select(d => d.DPNr).ToListAsync()) .ForEach(a => { if (a <= c + 100) c = a; }); return c + 1; } public async Task NextRdNr(int kgnr) { int c = 0; (await WbRde.Where(r => r.KgNr == kgnr).Select(r => r.RdNr).ToListAsync()) .ForEach(a => { if (a <= c + 100) c = a; }); return c + 1; } public async Task NextAvNr(int year) { int c = 0; (await PaymentVariants.Where(v => v.Year == year).Select(v => v.AvNr).ToListAsync()) .ForEach(a => { if (a <= c + 100) c = a; }); return c + 1; } public async Task NextDsNr(int year) { int c = 0; (await DeliverySchedules.Where(s => s.Year == year).Select(s => s.DsNr).ToListAsync()) .ForEach(a => { if (a <= c + 100) c = a; }); return c + 1; } public async Task GetWineQualityLevel(double kmw) { return await WineQualityLevels .Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw)) .OrderBy(q => q.MinKmw) .LastAsync(); } public void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable oldModifiers, IEnumerable newModifiers) { foreach (var m in Modifiers.Where(m => m.Year == part.Year)) { var mod = new DeliveryPartModifier { Year = part.Year, DId = part.DId, DPNr = part.DPNr, ModId = m.ModId, }; var old = oldModifiers.Where(pa => pa.ModId == m.ModId).FirstOrDefault(); if (newModifiers.Any(md => md.ModId == m.ModId)) { if (old == null) { Add(mod); } else { Update(mod); } } else { if (old != null) { Remove(mod); } } } } public void UpdateDeliveryScheduleWineVarieties(DeliverySchedule schedule, IEnumerable<(WineVar, int)> oldVarieties, IEnumerable<(WineVar, int)> newVarieties) { foreach (var v in WineVarieties) { var e = new DeliveryScheduleWineVar { Year = schedule.Year, DsNr = schedule.DsNr, SortId = v.SortId, Priority = 1, }; var o = oldVarieties.Where(x => x.Item1.SortId == e.SortId).Select(x => x.Item2).FirstOrDefault(-1); var n = newVarieties.Where(x => x.Item1.SortId == e.SortId).Select(x => x.Item2).FirstOrDefault(-1); if (n != -1) { e.Priority = n; if (o == -1) { Add(e); } else { Update(e); } } else { if (o != -1) { Remove(e); } } } } private async Task FetchMemberAreaCommitmentBuckets(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var buckets = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT mgnr, bucket, area, min_kg, max_kg FROM v_area_commitment_bucket WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var vtrgid = reader.GetString(1); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][vtrgid] = new(reader.GetInt32(2), reader.GetInt32(3), reader.GetInt32(4)); } } if (ownCnx) await cnx.DisposeAsync(); _memberAreaCommitmentBuckets[year] = buckets; } private async Task FetchMemberDeliveryBuckets(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var buckets = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT mgnr, bucket, weight FROM v_delivery_bucket WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bucket = reader.GetString(1); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][bucket] = reader.GetInt32(2); } } if (ownCnx) await cnx.DisposeAsync(); _memberDeliveryBuckets[year] = buckets; } private async Task FetchMemberDeliveryBucketsStrict(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var buckets = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT mgnr, bucket, weight FROM v_delivery_bucket_strict WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bucket = reader.GetString(1); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][bucket] = reader.GetInt32(2); } } if (ownCnx) await cnx.DisposeAsync(); _memberDeliveryBucketsStrict[year] = buckets; } private async Task FetchMemberPaymentBuckets(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var buckets = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT mgnr, bucket, weight FROM v_payment_bucket WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bucket = reader.GetString(1); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][bucket] = reader.GetInt32(2); } } if (ownCnx) await cnx.DisposeAsync(); _memberPaymentBuckets[year] = buckets; } private async Task FetchMemberUnderDelivery(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var buckets = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT mgnr, bucket, weight, diff FROM v_under_delivery WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bucket = reader.GetString(1); if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = []; buckets[mgnr][bucket] = new(reader.GetInt32(2), reader.GetInt32(3)); } } if (ownCnx) await cnx.DisposeAsync(); _memberUnderDelivery[year] = buckets; } public async Task> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberAreaCommitmentBuckets.ContainsKey(year)) await FetchMemberAreaCommitmentBuckets(year, cnx); return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberDeliveryBuckets(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberDeliveryBuckets.ContainsKey(year)) await FetchMemberDeliveryBuckets(year, cnx); return _memberDeliveryBuckets[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberDeliveryBucketsStrict(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberDeliveryBucketsStrict.ContainsKey(year)) await FetchMemberDeliveryBucketsStrict(year, cnx); return _memberDeliveryBucketsStrict[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberPaymentBuckets(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberPaymentBuckets.ContainsKey(year)) await FetchMemberPaymentBuckets(year, cnx); return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberUnderDelivery(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberUnderDelivery.ContainsKey(year)) await FetchMemberUnderDelivery(year, cnx); return _memberUnderDelivery[year].GetValueOrDefault(mgnr, []); } public async Task> GetMemberBuckets(int year, int mgnr, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var rightsAndObligations = await GetMemberAreaCommitmentBuckets(year, mgnr, cnx); var deliveryBuckets = await GetMemberDeliveryBuckets(year, mgnr, cnx); var deliveryBucketsStrict = await GetMemberDeliveryBucketsStrict(year, mgnr, cnx); var paymentBuckets = await GetMemberPaymentBuckets(year, mgnr, cnx); if (ownCnx) await cnx.DisposeAsync(); var buckets = new Dictionary(); foreach (var id in rightsAndObligations.Keys.Union(deliveryBuckets.Keys).Union(paymentBuckets.Keys)) { var variety = await WineVarieties.FindAsync(id[..2]); var attribute = await WineAttributes.FindAsync(id[2..]); var name = (variety?.Name ?? "") + (id[2..] == "_" ? " (kein Qual.Wein)" : attribute != null ? $" ({attribute})" : ""); buckets[id] = new( name, rightsAndObligations.GetValueOrDefault(id).Area, rightsAndObligations.GetValueOrDefault(id).Obligation, rightsAndObligations.GetValueOrDefault(id).Right, deliveryBuckets.GetValueOrDefault(id), deliveryBucketsStrict.GetValueOrDefault(id), deliveryBuckets.GetValueOrDefault(id) + deliveryBuckets.GetValueOrDefault(id + "_"), paymentBuckets.GetValueOrDefault(id) ); } return buckets; } public static async Task> GetMemberStats(int year, int mgnr, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var list = new List(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" SELECT v.name AS variety, COALESCE(a.name, '') || IIF(a.name IS NOT NULL AND c.name IS NOT NULL, ' / ', '') || COALESCE(c.name, '') AS disc, SUM(weight) AS weight FROM v_delivery d LEFT JOIN wine_variety v ON v.sortid = d.sortid LEFT JOIN wine_attribute a ON a.attrid = d.attrid LEFT JOIN wine_cultivation c ON c.cultid = d.cultid WHERE d.year = {year} AND d.mgnr = {mgnr} GROUP BY d.sortid, d.attrid, d.cultid ORDER BY variety, disc; """; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { list.Add(new(reader.GetString(0), reader.GetString(1), reader.GetInt32(2))); } } if (ownCnx) await cnx.DisposeAsync(); return list; } public static async Task> GetModifierStats(int year, int avnr, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var list = new List(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" SELECT m.modid, m.name, m.count, m.min, m.max, m.sum, s.precision FROM v_stat_modifier m JOIN season s ON s.year = m.year WHERE m.year = {year} AND m.avnr = {avnr} """; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var prec = (byte)reader.GetInt16(6); long? min = reader.IsDBNull(3) ? null : reader.GetInt64(3); long? max = reader.IsDBNull(4) ? null : reader.GetInt64(4); var sum = reader.GetInt64(5); if (min != null && max != null && Math.Abs((long)min) > Math.Abs((long)max)) (min, max) = (max, min); list.Add(new(reader.GetString(0), reader.GetString(1), reader.GetInt32(2), min == null ? null : Utils.DecFromDb((long)min, prec), max == null ? null : Utils.DecFromDb((long)max, prec), Utils.DecFromDb(sum, prec))); } } if (ownCnx) await cnx.DisposeAsync(); return list; } } }