503 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			503 lines
		
	
	
		
			25 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| 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<Country> Countries { get; private set; }
 | |
|         public DbSet<Currency> Currencies { get; private set; }
 | |
|         public DbSet<AT_Gem> Gemeinden { get; private set; }
 | |
|         public DbSet<AT_Kg> Katastralgemeinden { get; private set; }
 | |
|         public DbSet<AT_Ort> Orte { get; private set; }
 | |
|         public DbSet<AT_Plz> Postleitzahlen { get; private set; }
 | |
|         public DbSet<AT_PlzDest> PlzDestinations { get; private set; }
 | |
|         public DbSet<PostalDest> PostalDestinations { get; private set; }
 | |
|         public DbSet<WineOrigin> WineOrigins { get; private set; }
 | |
|         public DbSet<WineQualLevel> WineQualityLevels { get; private set; }
 | |
|         public DbSet<WineVar> WineVarieties { get; private set; }
 | |
| 
 | |
|         public DbSet<ClientParam> ClientParameters { get; private set; }
 | |
|         public DbSet<WbGl> WbGls { get; private set; }
 | |
|         public DbSet<WbGem> WbGems { get; private set; }
 | |
|         public DbSet<WbKg> WbKgs { get; private set; }
 | |
|         public DbSet<WbRd> WbRde { get; private set; }
 | |
|         public DbSet<WineAttr> WineAttributes { get; private set; }
 | |
|         public DbSet<WineCult> WineCultivations { get; private set; }
 | |
|         public DbSet<Branch> Branches { get; private set; }
 | |
|         public DbSet<AreaComType> AreaCommitmentTypes { get; private set; }
 | |
|         public DbSet<Member> Members { get; private set; }
 | |
|         public DbSet<BillingAddr> BillingAddresses { get; private set; }
 | |
|         public DbSet<MemberTelNr> MemberTelephoneNrs { get; private set; }
 | |
|         public DbSet<MemberEmailAddr> MemberEmailAddrs { get; private set; }
 | |
|         public DbSet<MemberHistory> MemberHistory { get; private set; }
 | |
|         public DbSet<AreaCom> AreaCommitments { get; private set; }
 | |
|         public DbSet<Season> Seasons { get; private set; }
 | |
|         public DbSet<DeliverySchedule> DeliverySchedules { get; private set; }
 | |
|         public DbSet<DeliveryScheduleWineVar> DeliveryScheduleWineVarieties { get; private set; }
 | |
|         public DbSet<DeliveryAncmt> DeliveryAnnouncements { get; private set; }
 | |
|         public DbSet<Modifier> Modifiers { get; private set; }
 | |
|         public DbSet<Delivery> Deliveries { get; private set; }
 | |
|         public DbSet<DeliveryPart> DeliveryParts { get; private set; }
 | |
|         public DbSet<DeliveryPartModifier> DeliveryPartModifiers { get; private set; }
 | |
|         public DbSet<PaymentVar> PaymentVariants { get; private set; }
 | |
|         public DbSet<PaymentMember> MemberPayments { get; private set; }
 | |
|         public DbSet<PaymentDeliveryPart> PaymentDeliveryParts { get; private set; }
 | |
|         public DbSet<PaymentCustom> CustomPayments { get; private set; }
 | |
|         public DbSet<Credit> Credits { get; private set; }
 | |
|         public DbSet<DeliveryPartBucket> DeliveryPartBuckets { get; private set; }
 | |
| 
 | |
|         public DbSet<OverUnderDeliveryRow> OverUnderDeliveryRows { get; private set; }
 | |
|         public DbSet<AreaComUnderDeliveryRowSingle> AreaComUnderDeliveryRows { get; private set; }
 | |
|         public DbSet<MemberDeliveryPerVariantRowSingle> MemberDeliveryPerVariantRows { get; private set; }
 | |
|         public DbSet<MemberAreaComsRowSingle> MemberAreaComsRows { get; private set; }
 | |
|         public DbSet<CreditNoteDeliveryRowSingle> CreditNoteDeliveryRows { get; private set; }
 | |
|         public DbSet<CreditNoteRowSingle> CreditNoteRows { get; private set; }
 | |
|         public DbSet<WeightBreakdownRow> WeightBreakDownRows { get; private set; }
 | |
|         public DbSet<PaymentVariantSummaryRow> 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<int, Dictionary<int, Dictionary<string, AreaComBucket>>> _memberAreaCommitmentBuckets = [];
 | |
|         private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = [];
 | |
|         private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBucketsStrict = [];
 | |
|         private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = [];
 | |
|         private readonly Dictionary<int, Dictionary<int, Dictionary<string, UnderDelivery>>> _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<string, string?, bool?>("REGEXP", (pattern, value) => value == null ? null : Regex.Match(value, pattern).Success, true);
 | |
|             cnx.Open();
 | |
|             return cnx;
 | |
|         }
 | |
| 
 | |
|         public static async Task<SqliteConnection> ConnectAsync(string? connectionString = null) {
 | |
|             var cnx = new SqliteConnection(connectionString ?? ConnectionString);
 | |
|             cnx.CreateFunction<string, string?, bool?>("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<object?> 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<bool> MgNrExists(int mgnr) {
 | |
|             return await Members.FindAsync(mgnr) != null;
 | |
|         }
 | |
| 
 | |
|         public async Task<bool> FbNrExists(int fbnr) {
 | |
|             return await AreaCommitments.FindAsync(fbnr) != null;
 | |
|         }
 | |
| 
 | |
|         public async Task<bool> SortIdExists(string sortId) {
 | |
|             return await WineVarieties.FindAsync(sortId) != null;
 | |
|         }
 | |
| 
 | |
|         public async Task<bool> AttrIdExists(string attrId) {
 | |
|             return await WineAttributes.FindAsync(attrId) != null;
 | |
|         }
 | |
| 
 | |
|         public async Task<bool> CultIdExists(string cultId) {
 | |
|             return await WineCultivations.FindAsync(cultId) != null;
 | |
|         }
 | |
| 
 | |
|         public async Task<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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 void UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<Modifier> oldModifiers, IEnumerable<Modifier> 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<int, Dictionary<string, AreaComBucket>>();
 | |
|             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<int, Dictionary<string, int>>();
 | |
|             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<int, Dictionary<string, int>>();
 | |
|             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<int, Dictionary<string, int>>();
 | |
|             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<int, Dictionary<string, UnderDelivery>>();
 | |
|             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<Dictionary<string, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
 | |
|             if (!_memberAreaCommitmentBuckets.ContainsKey(year))
 | |
|                 await FetchMemberAreaCommitmentBuckets(year, cnx);
 | |
|             return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, []);
 | |
|         }
 | |
| 
 | |
|         public async Task<Dictionary<string, int>> GetMemberDeliveryBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
 | |
|             if (!_memberDeliveryBuckets.ContainsKey(year))
 | |
|                 await FetchMemberDeliveryBuckets(year, cnx);
 | |
|             return _memberDeliveryBuckets[year].GetValueOrDefault(mgnr, []);
 | |
|         }
 | |
| 
 | |
|         public async Task<Dictionary<string, int>> GetMemberDeliveryBucketsStrict(int year, int mgnr, SqliteConnection? cnx = null) {
 | |
|             if (!_memberDeliveryBucketsStrict.ContainsKey(year))
 | |
|                 await FetchMemberDeliveryBucketsStrict(year, cnx);
 | |
|             return _memberDeliveryBucketsStrict[year].GetValueOrDefault(mgnr, []);
 | |
|         }
 | |
| 
 | |
|         public async Task<Dictionary<string, int>> GetMemberPaymentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
 | |
|             if (!_memberPaymentBuckets.ContainsKey(year))
 | |
|                 await FetchMemberPaymentBuckets(year, cnx);
 | |
|             return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, []);
 | |
|         }
 | |
| 
 | |
|         public async Task<Dictionary<string, UnderDelivery>> GetMemberUnderDelivery(int year, int mgnr, SqliteConnection? cnx = null) {
 | |
|             if (!_memberUnderDelivery.ContainsKey(year))
 | |
|                 await FetchMemberUnderDelivery(year, cnx);
 | |
|             return _memberUnderDelivery[year].GetValueOrDefault(mgnr, []);
 | |
|         }
 | |
| 
 | |
|         public async Task<Dictionary<string, MemberBucket>> 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<string, MemberBucket>();
 | |
|             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<List<MemberStat>> GetMemberStats(int year, int mgnr, SqliteConnection? cnx = null) {
 | |
|             var ownCnx = cnx == null;
 | |
|             cnx ??= await ConnectAsync();
 | |
|             var list = new List<MemberStat>();
 | |
|             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<List<ModifierStat>> GetModifierStats(int year, int avnr, SqliteConnection? cnx = null) {
 | |
|             var ownCnx = cnx == null;
 | |
|             cnx ??= await ConnectAsync();
 | |
|             var list = new List<ModifierStat>();
 | |
|             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;
 | |
|         }
 | |
|     }
 | |
| }
 |