AppDbContext: Use records instead of unnamed tuples for buckets

This commit is contained in:
2023-11-30 17:29:01 +01:00
parent 7efd34bc4d
commit 3576a066fe
9 changed files with 81 additions and 85 deletions

View File

@ -10,7 +10,7 @@ namespace Elwig.Documents {
public Season Season;
public DeliveryConfirmationData Data;
public string? Text = App.Client.TextDeliveryConfirmation;
public Dictionary<string, (string, int, int, int, int)> MemberBuckets;
public Dictionary<string, MemberBucket> MemberBuckets;
public DeliveryConfirmation(AppDbContext ctx, int year, Member m, DeliveryConfirmationData data) :
base($"Anlieferungsbestätigung {year}", m) {

View File

@ -125,11 +125,11 @@
$"<td>{(mode != 2 ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}</td>" +
$"<td>{sum:N0}</td>";
}
var mBuckets = Model.MemberBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0 || b.Value.Item4 > 0).ToList();
var fbVars = mBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray();
var fbs = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2).OrderBy(b => b.Value.Item1);
var vtr = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2).OrderBy(b => b.Value.Item1);
var rem = mBuckets.Where(b => !fbVars.Contains(b.Key)).OrderBy(b => b.Value.Item1);
var mBuckets = Model.MemberBuckets.Where(b => b.Value.Right > 0 || b.Value.Obligation > 0 || b.Value.Delivery > 0).ToList();
var fbVars = mBuckets.Where(b => b.Value.Right > 0 || b.Value.Obligation > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray();
var fbs = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2).OrderBy(b => b.Value.Name);
var vtr = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2).OrderBy(b => b.Value.Name);
var rem = mBuckets.Where(b => !fbVars.Contains(b.Key)).OrderBy(b => b.Value.Name);
}
<tr>
<th>Gesamtlieferung lt. gez. GA</th>
@ -143,28 +143,28 @@
@if (rem.Any()) {
<tr class="subheading"><th colspan="8">Sortenaufteilung:</th></tr>
}
@foreach (var (id, (name, right, obligation, sum, payment)) in rem) {
@foreach (var (id, b) in rem) {
<tr>
<th>@name</th>
@Raw(FormatRow(1, obligation, right, sum, payment))
<th>@b.Name</th>
@Raw(FormatRow(1, b.Obligation, b.Right, b.Delivery, b.Payment))
</tr>
}
@if (fbs.Any()){
<tr class="subheading"><th colspan="8">Flächenbindungen:</th></tr>
}
@foreach (var (id, (name, right, obligation, sum, payment)) in fbs) {
@foreach (var (id, b) in fbs) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, obligation, right, sum, payment))
<th>@b.Name</th>
@Raw(FormatRow(2, b.Obligation, b.Right, b.Delivery, b.Payment))
</tr>
}
@if (vtr.Any()) {
<tr class="subheading"><th colspan="8">Verträge:</th></tr>
}
@foreach (var (id, (name, right, obligation, sum, payment)) in vtr) {
@foreach (var (id, b) in vtr) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, obligation, right, sum, payment))
<th>@b.Name</th>
@Raw(FormatRow(2, b.Obligation, b.Right, b.Delivery, b.Payment))
</tr>
}
</tbody>

View File

@ -7,7 +7,7 @@ namespace Elwig.Documents {
public Delivery Delivery;
public string? Text;
public Dictionary<string, (string, int, int, int, int)> MemberBuckets;
public Dictionary<string, MemberBucket> MemberBuckets;
// 0 - none
// 1 - GA only

View File

@ -122,11 +122,11 @@
<tr class="subheading">
<th>Flächenbindungen:</th>
</tr>
@foreach (var (id, (name, right, obligation, sum, _)) in Model.MemberBuckets.OrderBy(b => b.Key)) {
if (right > 0 || obligation > 0 || (sum > 0 && buckets[id[..2]] > 1 && !id.EndsWith('_'))) {
@foreach (var (id, b) in Model.MemberBuckets.OrderBy(b => b.Key)) {
if (b.Right > 0 || b.Obligation > 0 || (b.Delivery > 0 && buckets[id[..2]] > 1 && !id.EndsWith('_'))) {
<tr class="@(sortids.Contains(id[..2]) ? "" : "optional")">
<th>@name</th>
@Raw(FormatRow(obligation, right, sum))
<th>@b.Name</th>
@Raw(FormatRow(b.Obligation, b.Right, b.Delivery))
</tr>
}
}

View File

@ -8,14 +8,12 @@ namespace Elwig.Documents {
public Season Season;
public int Year = Utils.CurrentYear;
public Dictionary<string, (string, int, int, int, int)> MemberBuckets;
public Dictionary<string, int> BucketAreas;
public Dictionary<string, MemberBucket> MemberBuckets;
public MemberDataSheet(Member m, AppDbContext ctx) : base($"Stammdatenblatt {m.AdministrativeName}", m) {
DocumentId = $"Stammdatenblatt {m.MgNr}";
Season = ctx.Seasons.Find(Year) ?? throw new ArgumentException("invalid season");
MemberBuckets = ctx.GetMemberBuckets(Year, m.MgNr).GetAwaiter().GetResult();
BucketAreas = ctx.GetMemberBucketAreas(Year, m.MgNr).GetAwaiter().GetResult();
}
}
}

View File

@ -259,10 +259,10 @@
$"<td>{(mode == 1 ? "" : obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
$"<td>{(mode == 1 ? "" : right == 0 ? "-" : $"{right:N0}")}</td>";
}
var mBuckets = Model.MemberBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0 || b.Value.Item4 > 0).ToList();
var fbVars = mBuckets.Where(b => b.Value.Item2 > 0 || b.Value.Item3 > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray();
var fbs = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2).OrderBy(b => b.Value.Item1);
var vtr = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2).OrderBy(b => b.Value.Item1);
var mBuckets = Model.MemberBuckets.Where(b => b.Value.Right > 0 || b.Value.Obligation > 0 || b.Value.Delivery > 0).ToList();
var fbVars = mBuckets.Where(b => b.Value.Right > 0 || b.Value.Obligation > 0).Select(b => b.Key.Replace("_", "")).Order().ToArray();
var fbs = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length == 2).OrderBy(b => b.Value.Name);
var vtr = mBuckets.Where(b => fbVars.Contains(b.Key) && b.Key.Length > 2).OrderBy(b => b.Value.Name);
}
<tr>
<th>Laut gezeichneten GA</th>
@ -271,24 +271,24 @@
0,
Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare,
Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare
))
))
</tr>
@if (fbs.Any()) {
<tr class="subheading"><th colspan="8">Flächenbindungen:</th></tr>
}
@foreach (var (id, (name, right, obligation, _, _)) in fbs) {
@foreach (var (id, b) in fbs) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, Model.BucketAreas[id], obligation, right))
<th>@b.Name</th>
@Raw(FormatRow(2, b.Area, b.Obligation, b.Right))
</tr>
}
@if (vtr.Any()) {
<tr class="subheading"><th colspan="8">Verträge:</th></tr>
}
@foreach (var (id, (name, right, obligation, _, _)) in vtr) {
@foreach (var (id, b) in vtr) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, Model.BucketAreas[id], obligation, right))
<th>@b.Name</th>
@Raw(FormatRow(2, b.Area, b.Obligation, b.Right))
</tr>
}
</tbody>

View File

@ -12,6 +12,10 @@ using System.Collections.Generic;
using Elwig.Models.Dtos;
namespace Elwig.Helpers {
public record struct AreaComBucket(int Area, int Obligation, int Right);
public record struct MemberBucket(string Name, int Area, int Obligation, int Right, int Delivery, int Payment);
public class AppDbContext : DbContext {
public DbSet<Country> Countries { get; private set; }
@ -60,10 +64,9 @@ namespace Elwig.Helpers {
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, AreaComBucket>>> _memberAreaCommitmentBuckets = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberPaymentBuckets = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberBucketAreas = new();
public AppDbContext() {
if (App.Config.DatabaseLog != null) {
@ -203,22 +206,22 @@ namespace Elwig.Helpers {
}
}
private async Task FetchMemberRightsAndObligations(int year, SqliteConnection? cnx = null) {
private async Task FetchMemberAreaCommitmentBuckets(int year, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();
var buckets = new Dictionary<int, Dictionary<string, (int, int)>>();
var buckets = new Dictionary<int, Dictionary<string, AreaComBucket>>();
using (var cmd = cnx.CreateCommand()) {
cmd.CommandText = $"SELECT mgnr, bucket, min_kg, max_kg FROM v_area_commitment_bucket WHERE year = {year}";
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] = new();
buckets[mgnr][vtrgid] = (reader.GetInt32(3), reader.GetInt32(2));
buckets[mgnr][vtrgid] = new(reader.GetInt32(2), reader.GetInt32(3), reader.GetInt32(4));
}
}
if (ownCnx) await cnx.DisposeAsync();
_memberRightsAndObligations[year] = buckets;
_memberAreaCommitmentBuckets[year] = buckets;
}
private async Task FetchMemberDeliveryBuckets(int year, SqliteConnection? cnx = null) {
@ -257,32 +260,10 @@ namespace Elwig.Helpers {
_memberPaymentBuckets[year] = buckets;
}
private async Task FetchMemberBucketAreas(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, area FROM v_area_commitment_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);
var v = reader.GetInt32(2);
if (!buckets.ContainsKey(mgnr)) buckets[mgnr] = new();
buckets[mgnr][bucket] = v;
if (bucket.Length > 2) {
buckets[mgnr][bucket[..2]] = buckets[mgnr].GetValueOrDefault(bucket[..2], 0) + v;
}
}
}
if (ownCnx) await cnx.DisposeAsync();
_memberBucketAreas[year] = buckets;
}
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, AreaComBucket>> GetMemberAreaCommitmentBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberAreaCommitmentBuckets.ContainsKey(year))
await FetchMemberAreaCommitmentBuckets(year, cnx);
return _memberAreaCommitmentBuckets[year].GetValueOrDefault(mgnr, new());
}
public async Task<Dictionary<string, int>> GetMemberDeliveryBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
@ -297,30 +278,25 @@ namespace Elwig.Helpers {
return _memberPaymentBuckets[year].GetValueOrDefault(mgnr, new());
}
public async Task<Dictionary<string, int>> GetMemberBucketAreas(int year, int mgnr, SqliteConnection? cnx = null) {
if (!_memberBucketAreas.ContainsKey(year))
await FetchMemberBucketAreas(year, cnx);
return _memberBucketAreas[year].GetValueOrDefault(mgnr, new());
}
public async Task<Dictionary<string, (string, int, int, int, int)>> GetMemberBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
public async Task<Dictionary<string, MemberBucket>> GetMemberBuckets(int year, int mgnr, SqliteConnection? cnx = null) {
var ownCnx = cnx == null;
cnx ??= await ConnectAsync();
var rightsAndObligations = await GetMemberRightsAndObligations(year, mgnr, cnx);
var rightsAndObligations = await GetMemberAreaCommitmentBuckets(year, mgnr, cnx);
var deliveryBuckets = await GetMemberDeliveryBuckets(year, mgnr, cnx);
var paymentBuckets = await GetMemberPaymentBuckets(year, mgnr, cnx);
if (ownCnx) await cnx.DisposeAsync();
var buckets = new Dictionary<string, (string, int, int, int, int)>();
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 attrIds = id[2..];
var attrs = await WineAttributes.Where(a => attrIds.Contains(a.AttrId)).ToListAsync();
var name = (variety?.Name ?? "") + (attrIds == "_" ? " (kein Qual.Wein)" : attrs.Count > 0 ? $" ({string.Join(" / ", attrs.Select(a => a.Name))})" : "");
buckets[id] = (
buckets[id] = new(
name,
rightsAndObligations.GetValueOrDefault(id).Item1,
rightsAndObligations.GetValueOrDefault(id).Item2,
rightsAndObligations.GetValueOrDefault(id).Area,
rightsAndObligations.GetValueOrDefault(id).Obligation,
rightsAndObligations.GetValueOrDefault(id).Right,
deliveryBuckets.GetValueOrDefault(id),
paymentBuckets.GetValueOrDefault(id)
);

View File

@ -5,12 +5,13 @@ using System.Windows;
namespace Elwig.Helpers {
public static class AppDbUpdater {
public static readonly int RequiredSchemaVersion = 9;
public static readonly int RequiredSchemaVersion = 10;
private static int _versionOffset = 0;
private static readonly Action<SqliteConnection>[] _updaters = new[] {
UpdateDbSchema_1_To_2, UpdateDbSchema_2_To_3, UpdateDbSchema_3_To_4, UpdateDbSchema_4_To_5,
UpdateDbSchema_5_To_6, UpdateDBSchema_6_To_7, UpdateDbSchema_7_To_8, UpdateDbSchema_8_To_9,
UpdateDbSchema_9_To_10,
};
private static void ExecuteNonQuery(SqliteConnection cnx, string sql) {
@ -714,5 +715,26 @@ namespace Elwig.Helpers {
END;
""");
}
private static void UpdateDbSchema_9_To_10(SqliteConnection cnx) {
ExecuteNonQuery(cnx, "UPDATE wine_quality_level SET min_kmw = 10.6 WHERE qualid = 'RSW'");
ExecuteNonQuery(cnx, "DROP VIEW v_area_commitment_bucket");
ExecuteNonQuery(cnx, """
CREATE VIEW v_area_commitment_bucket AS
SELECT year, mgnr, bucket, area, min_kg, max_kg
FROM v_area_commitment_bucket_strict
WHERE attrid IS NOT NULL
UNION ALL
SELECT b.year, b.mgnr, b.sortid,
SUM(b.area) AS area,
SUM(b.min_kg) AS min_kg,
SUM(b.upper_max_kg) AS max_kg
FROM v_area_commitment_bucket_strict b
LEFT JOIN wine_attribute a ON a.attrid = b.attrid
WHERE a.strict IS NULL OR a.strict = FALSE
GROUP BY b.year, b.mgnr, b.sortid
ORDER BY year, mgnr, bucket;
""");
}
}
}

View File

@ -41,7 +41,7 @@ namespace Elwig.Helpers.Billing {
var attrVals = Context.WineAttributes.ToDictionary(a => a.AttrId, a => (a.IsStrict, a.FillLower));
var attrForced = attrVals.Where(a => a.Value.IsStrict && a.Value.FillLower == 0).Select(a => a.Key).ToArray();
using var cnx = await AppDbContext.ConnectAsync();
await Context.GetMemberRightsAndObligations(Year, 0, cnx);
await Context.GetMemberAreaCommitmentBuckets(Year, 0, cnx);
var inserts = new List<(int, int, int, string, int)>();
var deliveries = new List<(int, int, int, string, int, double, string, string?, string[], bool?)>();
@ -66,11 +66,11 @@ namespace Elwig.Helpers.Billing {
}
int lastMgNr = 0;
Dictionary<string, (int, int)>? rightsAndObligations = null;
Dictionary<string, AreaComBucket>? rightsAndObligations = null;
Dictionary<string, int> used = new();
foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attrid, modifiers, gebunden) in deliveries) {
if (lastMgNr != mgnr) {
rightsAndObligations = await Context.GetMemberRightsAndObligations(Year, mgnr);
rightsAndObligations = await Context.GetMemberAreaCommitmentBuckets(Year, mgnr);
used = new();
}
if ((honorGebunden && gebunden == false) ||
@ -94,8 +94,8 @@ namespace Elwig.Helpers.Billing {
if (rightsAndObligations.ContainsKey(key)) {
int i = (c == 0) ? 1 : 2;
var u = used.GetValueOrDefault(key, 0);
var vr = Math.Max(0, Math.Min(rightsAndObligations[key].Item1 - u, w));
var vo = Math.Max(0, Math.Min(rightsAndObligations[key].Item2 - u, w));
var vr = Math.Max(0, Math.Min(rightsAndObligations[key].Right - u, w));
var vo = Math.Max(0, Math.Min(rightsAndObligations[key].Obligation - u, w));
var v = (attributes.Length == c || attributes.Select(a => !attrVals[a].IsStrict ? 2 : attrVals[a].FillLower).Min() == 2) ? vr : vo;
used[key] = u + v;
if (key.Length > 2 && !isStrict) used[key[..2]] = used.GetValueOrDefault(key[..2], 0) + v;