Files
elwig/Elwig/Helpers/AppDbContext.cs

609 lines
33 KiB
C#

using Elwig.Models.Dtos;
using Elwig.Models.Entities;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using ScottPlot.TickGenerators.Financial;
using ScottPlot.TickGenerators.TimeUnits;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media.Converters;
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<AreaComContract> AreaCommitmentContracts { 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<MemberDeliveryPerVarietyRowSingle> 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 static readonly Func<AppDbContext, string?, bool, IAsyncEnumerable<Branch>> _compiledQueryBranches =
EF.CompileAsyncQuery<AppDbContext, string?, bool, Branch>((ctx, zwstid, includeWithoutMembers) => ctx.Branches
.Where(b => includeWithoutMembers || b.Members.Count > 0)
.Where(b => zwstid == null || b.ZwstId == zwstid)
.Include(b => b.PostalDest)
.OrderBy(b => b.Name));
private static readonly Func<AppDbContext, string?, IAsyncEnumerable<WineVar>> _compiledQueryWineVarieties =
EF.CompileAsyncQuery<AppDbContext, string?, WineVar>((ctx, sortid) => ctx.WineVarieties
.Where(v => sortid == null || v.SortId == sortid)
.OrderBy(v => v.Name));
private static readonly Func<AppDbContext, string?, bool, IAsyncEnumerable<WineAttr>> _compiledQueryWineAttributes =
EF.CompileAsyncQuery<AppDbContext, string?, bool, WineAttr>((ctx, attrid, includeNotActive) => ctx.WineAttributes
.Where(a => includeNotActive || a.IsActive)
.Where(a => attrid == null || a.AttrId == attrid)
.OrderBy(a => a.Name));
private static readonly Func<AppDbContext, string?, IAsyncEnumerable<WineCult>> _compiledQueryWineCultivations =
EF.CompileAsyncQuery<AppDbContext, string?, WineCult>((ctx, cultid) => ctx.WineCultivations
.Where(c => cultid == null || c.CultId == cultid)
.OrderBy(v => v.Name));
private static readonly Func<AppDbContext, bool, IAsyncEnumerable<WineQualLevel>> _compiledQueryWineQualityLevels =
EF.CompileAsyncQuery<AppDbContext, bool, WineQualLevel>((ctx, includePredicate) => ctx.WineQualityLevels
.Where(l => includePredicate || !l.IsPredicate)
.OrderBy(l => l.MinKmw));
private static readonly Func<AppDbContext, int?, bool, IAsyncEnumerable<Modifier>> _compiledQueryModifiers =
EF.CompileAsyncQuery<AppDbContext, int?, bool, Modifier>((ctx, year, incudeNotActive) => ctx.Modifiers
.Where(m => (year == null || m.Year == year) && (incudeNotActive || m.IsActive))
.OrderBy(m => m.Year).ThenBy(m => m.Ordering).ThenBy(m => m.Name));
private static readonly Func<AppDbContext, int?, bool, IAsyncEnumerable<Member>> _compiledQueryMembers =
EF.CompileAsyncQuery<AppDbContext, int?, bool, Member>((ctx, mgnr, includeNotActive) => ctx.Members
.Where(m => includeNotActive || m.IsActive)
.Where(m => mgnr == null || m.MgNr == mgnr)
.OrderBy(m => m.Name).ThenBy(m => m.GivenName).ThenBy(m => m.MgNr));
private static readonly Func<AppDbContext, int?, bool, IAsyncEnumerable<Member>> _compiledQueryMembersContactInfo =
EF.CompileAsyncQuery<AppDbContext, int?, bool, Member>((ctx, mgnr, includeNotActive) => ctx.Members
.Where(m => includeNotActive || m.IsActive)
.Where(m => mgnr == null || m.MgNr == mgnr)
.Include(m => m.EmailAddresses)
.Include(m => m.TelephoneNumbers)
.OrderBy(m => m.Name).ThenBy(m => m.GivenName).ThenBy(m => m.MgNr)
.AsSplitQuery());
private static readonly Func<AppDbContext, int?, IAsyncEnumerable<AreaCom>> _compiledQueryAreaCommitments =
EF.CompileAsyncQuery<AppDbContext, int?, AreaCom>((ctx, fbnr) => ctx.AreaCommitments
.Where(c => fbnr == null || c.FbNr == fbnr)
.OrderBy(c => c.FbNr).ThenBy(c => c.RevNr));
private static readonly Func<AppDbContext, int?, IAsyncEnumerable<Season>> _compiledQuerySeasons =
EF.CompileAsyncQuery<AppDbContext, int?, Season>((ctx, year) => ctx.Seasons
.Where(s => year == null || s.Year == year)
.OrderByDescending(s => s.Year));
private static readonly Func<AppDbContext, int?, IAsyncEnumerable<Season>> _compiledQuerySeasonsModifiers =
EF.CompileAsyncQuery<AppDbContext, int?, Season>((ctx, year) => ctx.Seasons
.Where(s => year == null || s.Year == year)
.Include(s => s.Modifiers)
.OrderByDescending(s => s.Year));
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;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseSqlite(ConnectionString);
optionsBuilder.LogTo(Log, LogLevel.Information);
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<WbKg>().Navigation(k => k.AtKg).AutoInclude();
modelBuilder.Entity<WbKg>().Navigation(k => k.Gl).AutoInclude();
modelBuilder.Entity<AT_Kg>().Navigation(k => k.Gem).AutoInclude();
modelBuilder.Entity<PostalDest>().Navigation(p => p.Country).AutoInclude();
modelBuilder.Entity<PostalDest>().Navigation(p => p.AtPlz).AutoInclude();
modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.AtPlz).AutoInclude();
modelBuilder.Entity<AT_PlzDest>().Navigation(p => p.Ort).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.DefaultWbKg).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.Country).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.PostalDest).AutoInclude();
modelBuilder.Entity<Member>().Navigation(m => m.BillingAddress).AutoInclude();
modelBuilder.Entity<BillingAddr>().Navigation(a => a.Country).AutoInclude();
modelBuilder.Entity<BillingAddr>().Navigation(a => a.PostalDest).AutoInclude();
modelBuilder.Entity<Modifier>().Navigation(m => m.Season).AutoInclude();
modelBuilder.Entity<Season>().Navigation(s => s.Currency).AutoInclude();
modelBuilder.Entity<PaymentVar>().Navigation(v => v.Season).AutoInclude();
modelBuilder.Entity<PaymentDeliveryPart>().Navigation(p => p.Variant).AutoInclude();
modelBuilder.Entity<Credit>().Navigation(c => c.Payment).AutoInclude();
modelBuilder.Entity<Delivery>().Navigation(d => d.Member).AutoInclude();
modelBuilder.Entity<Delivery>().Navigation(d => d.Season).AutoInclude();
modelBuilder.Entity<Delivery>().Navigation(d => d.Branch).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Quality).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Variety).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Attribute).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Cultivation).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Kg).AutoInclude();
modelBuilder.Entity<DeliveryPart>().Navigation(p => p.Rd).AutoInclude();
modelBuilder.Entity<DeliveryPartModifier>().Navigation(m => m.Modifier).AutoInclude();
modelBuilder.Entity<AreaComContract>().Navigation(c => c.Kg).AutoInclude();
modelBuilder.Entity<AreaComContract>().Navigation(c => c.Rd).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.Contract).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.WineCult).AutoInclude();
modelBuilder.Entity<AreaCom>().Navigation(c => c.AreaComType).AutoInclude();
modelBuilder.Entity<AreaComType>().Navigation(c => c.WineVar).AutoInclude();
modelBuilder.Entity<AreaComType>().Navigation(c => c.WineAttr).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Credit).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Member).AutoInclude();
modelBuilder.Entity<PaymentMember>().Navigation(c => c.Variant).AutoInclude();
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Member).AutoInclude();
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Schedule).AutoInclude();
modelBuilder.Entity<DeliveryAncmt>().Navigation(a => a.Variety).AutoInclude();
modelBuilder.Entity<DeliverySchedule>().Navigation(s => s.Branch).AutoInclude();
}
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 _compiledQueryMembers.Invoke(this, mgnr, true).AnyAsync();
}
public async Task<bool> FbNrExists(int fbnr) {
return await _compiledQueryAreaCommitments.Invoke(this, fbnr).AnyAsync();
}
public async Task<bool> SortIdExists(string sortId) {
return await _compiledQueryWineVarieties.Invoke(this, sortId).AnyAsync();
}
public async Task<bool> AttrIdExists(string attrId) {
return await _compiledQueryWineAttributes.Invoke(this, attrId, true).AnyAsync();
}
public async Task<bool> CultIdExists(string cultId) {
return await _compiledQueryWineCultivations.Invoke(this, cultId).AnyAsync();
}
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 AreaCommitmentContracts.OrderBy(ac => ac.FbNr).Select(ac => ac.FbNr).ToListAsync())
.ForEach(a => { if (a <= c + 10000) c = a; });
return c + 1;
}
public async Task<int> NextRevNr(int fbnr) {
return (await AreaCommitments.Where(c => c.FbNr == fbnr).Select(c => (int?)c.RevNr).MaxAsync() ?? 0) + 1;
}
public async Task<int> NextLNr(DateOnly date, string zwstid) {
var dateStr = date.ToString("yyyy-MM-dd");
return (await Deliveries.Where(d => d.DateString == dateStr && d.ZwstId == zwstid).Select(d => (int?)d.LNr).MaxAsync() ?? 0) + 1;
}
public async Task<int> NextDId(int year) {
return (await Deliveries.Where(d => d.Year == year).Select(d => (int?)d.DId).MaxAsync() ?? 0) + 1;
}
public async Task<int> NextDPNr(int year, int did) {
return (await DeliveryParts.Where(p => p.Year == year && p.DId == did).Select(p => (int?)p.DPNr).MaxAsync() ?? 0) + 1;
}
public async Task<int> NextRdNr(int kgnr) {
return (await WbRde.Where(r => r.KgNr == kgnr).Select(r => (int?)r.RdNr).MaxAsync() ?? 0) + 1;
}
public async Task<int> NextAvNr(int year) {
return (await PaymentVariants.Where(v => v.Year == year).Select(v => (int?)v.AvNr).MaxAsync() ?? 0) + 1;
}
public async Task<int> NextDsNr(int year) {
return (await DeliverySchedules.Where(s => s.Year == year).Select(v => (int?)v.DsNr).MaxAsync() ?? 0) + 1;
}
public IAsyncEnumerable<Branch> FetchBranches(string? zwstid = null, bool includeWithoutMembers = true) {
return _compiledQueryBranches.Invoke(this, zwstid, includeWithoutMembers);
}
public IAsyncEnumerable<WineVar> FetchWineVarieties() {
return _compiledQueryWineVarieties.Invoke(this, null);
}
public IAsyncEnumerable<WineAttr> FetchWineAttributes(bool incudeNotActive = true) {
return _compiledQueryWineAttributes.Invoke(this, null, incudeNotActive);
}
public IAsyncEnumerable<WineCult> FetchWineCultivations() {
return _compiledQueryWineCultivations.Invoke(this, null);
}
public IAsyncEnumerable<WineQualLevel> FetchWineQualityLevels(bool includePredicate = true) {
return _compiledQueryWineQualityLevels.Invoke(this, includePredicate);
}
public IAsyncEnumerable<Modifier> FetchModifiers(int? year, bool incudeNotActive = true) {
return _compiledQueryModifiers.Invoke(this, year, incudeNotActive);
}
public IAsyncEnumerable<Member> FetchMembers(int? mgnr = null, bool includeNotActive = false, bool includeContactInfo = false) {
if (includeContactInfo) {
return _compiledQueryMembersContactInfo.Invoke(this, mgnr, includeNotActive);
} else {
return _compiledQueryMembers.Invoke(this, mgnr, includeNotActive);
}
}
public IAsyncEnumerable<Season> FetchSeasons(int? year = null, bool includeModifiers = false) {
if (includeModifiers) {
return _compiledQuerySeasonsModifiers.Invoke(this, year);
} else {
return _compiledQuerySeasons.Invoke(this, year);
}
}
public async Task UpdateDeliveryPartModifiers(DeliveryPart part, IEnumerable<string> oldModIds, IEnumerable<string> newModIds) {
foreach (var m in await FetchModifiers(part.Year).ToListAsync()) {
var mod = new DeliveryPartModifier {
Year = part.Year,
DId = part.DId,
DPNr = part.DPNr,
ModId = m.ModId,
};
var old = oldModIds.Contains(m.ModId);
if (newModIds.Contains(m.ModId)) {
if (!old) {
Add(mod);
} else {
Update(mod);
}
} else {
if (old) {
Remove(mod);
}
}
}
}
public async Task UpdateDeliveryScheduleWineVarieties(DeliverySchedule schedule, IEnumerable<(string, int)> oldVarieties, IEnumerable<(string, int)> newVarieties) {
foreach (var v in await FetchWineVarieties().ToArrayAsync()) {
var e = new DeliveryScheduleWineVar {
Year = schedule.Year,
DsNr = schedule.DsNr,
SortId = v.SortId,
Priority = 1,
};
var o = oldVarieties.Where(x => x.Item1 == e.SortId).Select(x => x.Item2).FirstOrDefault(-1);
var n = newVarieties.Where(x => x.Item1 == 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 varieties = await WineVarieties.ToDictionaryAsync(v => v.SortId);
var attributes = await WineAttributes.ToDictionaryAsync(a => a.AttrId);
var buckets = new Dictionary<string, MemberBucket>();
foreach (var id in rightsAndObligations.Keys.Union(deliveryBuckets.Keys).Union(paymentBuckets.Keys)) {
var variety = varieties.GetValueOrDefault(id[..2]);
var attribute = attributes.GetValueOrDefault(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;
}
}
}