321 lines
15 KiB
C#
321 lines
15 KiB
C#
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<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<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<AreaCom> AreaCommitments { get; private set; }
|
|
public DbSet<Season> Seasons { 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<DeliveryPartAttr> DeliveryPartAttributes { 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<Credit> 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<int, Dictionary<int, Dictionary<string, (int, int)>>> _memberRightsAndObligations = new();
|
|
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBins = new();
|
|
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _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<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() {
|
|
var cnx = new SqliteConnection(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.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<int> 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<int> 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<int> 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<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<WineQualLevel> 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<WineAttr> 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<DeliveryPartAttr>();
|
|
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<Modifier> 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<DeliveryPartModifier>();
|
|
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<int, Dictionary<string, (int, int)>>();
|
|
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<int, Dictionary<string, int>>();
|
|
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<int, Dictionary<string, int>>();
|
|
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<Dictionary<string, (int, int)>> 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<Dictionary<string, int>> 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<Dictionary<string, int>> 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<Dictionary<string, (string, int, int, int, int)>> 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<string, (string, int, int, int, int)>();
|
|
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;
|
|
}
|
|
}
|
|
}
|