using Microsoft.EntityFrameworkCore; using Elwig.Models; 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; namespace Elwig.Helpers { 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 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 AreaCommitments { get; private set; } public DbSet Seasons { get; private set; } public DbSet Modifiers { get; private set; } public DbSet Deliveries { get; private set; } public DbSet DeliveryParts { get; private set; } public DbSet DeliveryPartAttributes { get; private set; } public DbSet DeliveryPartModifiers { get; private set; } public DbSet PaymentVariants { get; private set; } public DbSet MemberPayments { get; private set; } public DbSet Credits { 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 ConnectionString => $"Data Source=\"{App.Config.DatabaseFile}\"; Foreign Keys=True; Mode=ReadWrite; Cache=Default"; private readonly Dictionary>> _memberRightsAndObligations = new(); private readonly Dictionary>> _memberDeliveryBins = new(); private readonly Dictionary>> _memberPaymentBins = new(); 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() { var cnx = new SqliteConnection(ConnectionString); cnx.CreateFunction("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); cnx.Open(); return cnx; } public static async Task ConnectAsync() { var cnx = new SqliteConnection(ConnectionString); cnx.CreateFunction("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true); await cnx.OpenAsync(); return cnx; } 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 NextMgNr() { int c = await Members.Select(m => m.MgNr).MinAsync(); (await Members.OrderBy(m => m.MgNr).Select(m => m.MgNr).ToListAsync()) .ForEach(a => { if (a <= c + 1000) c = a; }); return c + 1; } public async Task NextFbNr() { int c = await AreaCommitments.Select(ac => ac.FbNr).MinAsync(); (await AreaCommitments.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync()) .ForEach(a => { if (a <= c + 1000) c = a; }); return c + 1; } public async Task NextLNr(DateOnly date) { var dateStr = date.ToString("yyyy-MM-dd"); int c = 0; (await Deliveries.Where(d => d.DateString == dateStr).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 GetWineQualityLevel(double kmw) { return await WineQualityLevels .Where(q => !q.IsPredicate && (q.MinKmw == null || q.MinKmw <= kmw)) .OrderBy(q => q.MinKmw) .LastOrDefaultAsync(); } public async Task UpdateDeliveryPartAttributes(DeliveryPart part, IEnumerable attributes) { foreach (var a in WineAttributes) { var attr = part.PartAttributes.Where(pa => pa.AttrId == a.AttrId).FirstOrDefault(); if (attributes.Contains(a)) { DeliveryPartAttr dpa = attr ?? this.CreateProxy(); dpa.Year = part.Year; dpa.DId = part.DId; dpa.DPNr = part.DPNr; dpa.AttrId = a.AttrId; if (attr == null) { await AddAsync(dpa); } else { Update(dpa); } } else { if (attr != null) { Remove(attr); } } } } public async Task UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable modifiers) { foreach (var m in Modifiers.Where(m => m.Year == part.Year)) { var mod = part.PartModifiers.Where(pa => pa.ModId == m.ModId).FirstOrDefault(); if (modifiers.Contains(m)) { DeliveryPartModifier dpm = mod ?? this.CreateProxy(); dpm.Year = part.Year; dpm.DId = part.DId; dpm.DPNr = part.DPNr; dpm.ModId = m.ModId; if (mod == null) { await AddAsync(dpm); } else { Update(dpm); } } else { if (mod != null) { Remove(mod); } } } } private async Task FetchMemberRightsAndObligations(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var bins = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $""" SELECT mgnr, t.vtrgid, ROUND(SUM(COALESCE(area * min_kg_per_ha, 0)) / 10000.0) AS min_kg, ROUND(SUM(COALESCE(area * max_kg_per_ha, 0)) / 10000.0) AS max_kg FROM area_commitment c JOIN area_commitment_type t ON t.vtrgid = c.vtrgid WHERE (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 """; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var vtrgid = reader.GetString(1); if (!bins.ContainsKey(mgnr)) bins[mgnr] = new(); bins[mgnr][vtrgid] = (reader.GetInt32(3), reader.GetInt32(2)); } } if (ownCnx) await cnx.DisposeAsync(); _memberRightsAndObligations[year] = bins; } private async Task FetchMemberDeliveryBins(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var bins = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT mgnr, bin, weight FROM v_delivery_bin WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bin = reader.GetString(1); if (!bins.ContainsKey(mgnr)) bins[mgnr] = new(); bins[mgnr][bin] = reader.GetInt32(2); } } if (ownCnx) await cnx.DisposeAsync(); _memberDeliveryBins[year] = bins; } private async Task FetchMemberPaymentBins(int year, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var bins = new Dictionary>(); using (var cmd = cnx.CreateCommand()) { cmd.CommandText = $"SELECT mgnr, bin, weight FROM v_payment_bin WHERE year = {year}"; using var reader = await cmd.ExecuteReaderAsync(); while (await reader.ReadAsync()) { var mgnr = reader.GetInt32(0); var bin = reader.GetString(1); if (!bins.ContainsKey(mgnr)) bins[mgnr] = new(); bins[mgnr][bin] = reader.GetInt32(2); } } if (ownCnx) await cnx.DisposeAsync(); _memberPaymentBins[year] = bins; } public async Task> GetMemberRightsAndObligations(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberRightsAndObligations.ContainsKey(year)) await FetchMemberRightsAndObligations(year, cnx); return _memberRightsAndObligations[year].GetValueOrDefault(mgnr, new()); } public async Task> GetMemberDeliveryBins(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberDeliveryBins.ContainsKey(year)) await FetchMemberDeliveryBins(year, cnx); return _memberDeliveryBins[year].GetValueOrDefault(mgnr, new()); } public async Task> GetMemberPaymentBins(int year, int mgnr, SqliteConnection? cnx = null) { if (!_memberPaymentBins.ContainsKey(year)) await FetchMemberPaymentBins(year, cnx); return _memberPaymentBins[year].GetValueOrDefault(mgnr, new()); } public async Task> GetMemberBins(int year, int mgnr, SqliteConnection? cnx = null) { var ownCnx = cnx == null; cnx ??= await ConnectAsync(); var rightsAndObligations = await GetMemberRightsAndObligations(year, mgnr, cnx); var deliveryBins = await GetMemberDeliveryBins(year, mgnr, cnx); var paymentBins = await GetMemberPaymentBins(year, mgnr, cnx); if (ownCnx) await cnx.DisposeAsync(); var bins = new Dictionary(); foreach (var id in rightsAndObligations.Keys.Union(deliveryBins.Keys).Union(paymentBins.Keys)) { var variety = await WineVarieties.FindAsync(id[..2]); var attrIds = id[2..]; var attrs = await WineAttributes.Where(a => attrIds.Contains(a.AttrId)).ToListAsync(); var name = (variety?.Name ?? "") + (attrs.Count > 0 ? $" ({string.Join(" / ", attrs.Select(a => a.Name))})" : ""); bins[id] = ( name, rightsAndObligations.GetValueOrDefault(id).Item1, rightsAndObligations.GetValueOrDefault(id).Item2, deliveryBins.GetValueOrDefault(id), paymentBins.GetValueOrDefault(id) ); } return bins; } } }