diff --git a/Elwig/Documents/DeliveryConfirmation.cshtml b/Elwig/Documents/DeliveryConfirmation.cshtml index b2e2f63..e6bd18d 100644 --- a/Elwig/Documents/DeliveryConfirmation.cshtml +++ b/Elwig/Documents/DeliveryConfirmation.cshtml @@ -93,7 +93,60 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + @{ + string FormatRow(int obligation, int right, int sum, int? payment = null) { + var isGa = payment == null; + payment ??= sum; + return $"" + + $"" + + $"" + + $"" + + $"" + + $"" + + $""; + } + } + + + @Raw(FormatRow(Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Year).Sum(d => d.Weight))) + + + + + @foreach (var (id, (name, right, obligation, sum, payment)) in Model.MemberBins.OrderBy(b => b.Key)) { + if (right > 0 || obligation > 0 || sum > 0) { + + + @Raw(FormatRow(obligation, right, sum, payment)) + + } + } + +
Lese @Model.Year per @($"{Model.Date:dd.MM.yyyy}") [kg]LieferpflichtLieferrechtUnterliefert
(bzgl. Zuget.)
Noch zu liefern
(bzgl. Gelft.)
Überliefert
(bzgl. Gelft.)
ZugeteiltGeliefert
{obligation:N0}{right:N0}{(payment < obligation ? $"{obligation - payment:N0}\x3c/b>" : "-")}{(sum >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}{(obligation == 0 && right == 0 ? "-" : (sum > right ? ((isGa ? "" : "") + $"{sum - right:N0}" + (isGa ? "" : "")) : "-"))}{(obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}{sum:N0}
Gesamtlieferung lt. gez. GA
Flächenbindungen:
@name
@if (Model.Text != null) {

@Model.Text

diff --git a/Elwig/Documents/DeliveryConfirmation.cshtml.cs b/Elwig/Documents/DeliveryConfirmation.cshtml.cs index 19f9aaf..7e4fb3d 100644 --- a/Elwig/Documents/DeliveryConfirmation.cshtml.cs +++ b/Elwig/Documents/DeliveryConfirmation.cshtml.cs @@ -10,6 +10,7 @@ namespace Elwig.Documents { public int Year; public IEnumerable Deliveries; public string? Text = App.Client.TextDeliveryConfirmation; + public Dictionary MemberBins; public DeliveryConfirmation(AppDbContext ctx, int year, Member m) : base($"Anlieferungsbestätigung {year} – {((IAddress?)m.BillingAddress ?? m).Name}", m) { @@ -29,6 +30,7 @@ namespace Elwig.Documents { v.kmw DESC, v.lsnr, v.dpnr """) .ToList(); + MemberBins = ctx.GetMemberBins(Year, m.MgNr).GetAwaiter().GetResult(); } } } diff --git a/Elwig/Documents/DeliveryNote.cshtml b/Elwig/Documents/DeliveryNote.cshtml index bd73689..f44af88 100644 --- a/Elwig/Documents/DeliveryNote.cshtml +++ b/Elwig/Documents/DeliveryNote.cshtml @@ -76,61 +76,59 @@

Amerkung zur Lieferung: @Model.Delivery.Comment

} @if (Model.DisplayStats > 0) { -
- - - - - - - - - - - - - - - - - - - - - - - @{ - string FormatRow(int obligation, int right, int sum) { - return $"" + - $"" + - $"" + - $"" + - $"" + - $""; - } - var sortids = Model.Delivery.Parts.Select(p => p.SortId).ToList(); - var bins = Model.MemberBins.GroupBy(b => b.Item1[..2]).ToDictionary(g => g.Key, g => g.Count()); +
Lese @Model.Delivery.Year per @($"{Model.Date:dd.MM.yyyy}") [kg]LieferpflichtLieferrechtUnterliefertNoch zu liefernÜberliefertGeliefert
{obligation:N0}{right:N0}{(sum < obligation ? $"{obligation - sum:N0}" : "-")}{(sum >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}{(sum > right ? $"{sum - right:N0}" : "-")}{sum:N0}
+ + + + + + + + + + + + + + + + + + + + + + @{ + string FormatRow(int obligation, int right, int sum) { + return $"" + + $"" + + $"" + + $"" + + $"" + + $""; } - - - @Raw(FormatRow(Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Delivery.Year).Sum(d => d.Weight))) + var sortids = Model.Delivery.Parts.Select(p => p.SortId).ToList(); + var bins = Model.MemberBins.GroupBy(b => b.Key[..2]).ToDictionary(g => g.Key, g => g.Count()); + } + + + @Raw(FormatRow(Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Delivery.Year).Sum(d => d.Weight))) + + @if (Model.DisplayStats > 1) { + + - @if (Model.DisplayStats > 1) { - - - - @foreach (var (id, name, right, obligation, sum) in Model.MemberBins.OrderBy(b => b.Item1)) { - if (right > 0 || obligation > 0 || (sum > 0 && bins[id[..2]] > 1 && !id.EndsWith('_'))) { - - - @Raw(FormatRow(obligation, right, sum)) - - } + @foreach (var (id, (name, right, obligation, sum, _)) in Model.MemberBins.OrderBy(b => b.Key)) { + if (right > 0 || obligation > 0 || (sum > 0 && bins[id[..2]] > 1 && !id.EndsWith('_'))) { + + + @Raw(FormatRow(obligation, right, sum)) + } } - -
Lese @Model.Delivery.Year per @($"{Model.Date:dd.MM.yyyy}") [kg]LieferpflichtLieferrechtUnterliefertNoch zu liefernÜberliefertGeliefert
{obligation:N0}{right:N0}{(sum < obligation ? $"{obligation - sum:N0}" : "-")}{(sum >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}{(sum > right ? $"{sum - right:N0}" : "-")}{sum:N0}
Gesamtlieferung lt. gez. GA
Gesamtlieferung lt. gez. GA
Flächenbindungen:
Flächenbindungen:
@name
@name
-
+ } + + } @for (int i = 0; i < 2; i++) { diff --git a/Elwig/Documents/DeliveryNote.cshtml.cs b/Elwig/Documents/DeliveryNote.cshtml.cs index cd9df60..43c67d0 100644 --- a/Elwig/Documents/DeliveryNote.cshtml.cs +++ b/Elwig/Documents/DeliveryNote.cshtml.cs @@ -7,7 +7,7 @@ namespace Elwig.Documents { public Delivery Delivery; public string? Text; - public IEnumerable<(string, string, int, int, int)> MemberBins; + public Dictionary MemberBins; // 0 - none // 1 - GA only @@ -27,7 +27,7 @@ namespace Elwig.Documents { $""; Text = App.Client.TextDeliveryNote; DocumentId = d.LsNr; - MemberBins = ctx.GetMemberBins(d.Member, d.Year).GetAwaiter().GetResult(); + MemberBins = ctx.GetMemberBins(d.Year, d.Member.MgNr).GetAwaiter().GetResult(); } } } diff --git a/Elwig/Documents/style-deliveryconfirmation.css b/Elwig/Documents/style-deliveryconfirmation.css index ff7fb13..fbb26f3 100644 --- a/Elwig/Documents/style-deliveryconfirmation.css +++ b/Elwig/Documents/style-deliveryconfirmation.css @@ -1,6 +1,7 @@ table.delivery-confirmation { font-size: 10pt; + margin-bottom: 5mm; } table.delivery-confirmation thead { @@ -68,3 +69,38 @@ table.delivery-confirmation tr.sum { table.delivery-confirmation tr.sum td { padding-top: 1mm; } + +table.delivery-confirmation-stats { + font-size: 10pt; + break-inside: avoid; +} + +table.delivery-confirmation-stats th, +table.delivery-confirmation-stats td { + padding: 0.125mm 0; +} + +table.delivery-confirmation-stats tr.subheading th { + text-align: left; +} + +table.delivery-confirmation-stats thead th { + font-weight: normal; + font-style: italic; + text-align: right; + font-size: 8pt; +} + +table.delivery-confirmation-stats thead th:first-child { + text-align: left; +} + +table.delivery-confirmation-stats td { + text-align: right; +} + +table.delivery-confirmation-stats tbody th { + font-weight: normal; + font-style: italic; + text-align: left; +} diff --git a/Elwig/Documents/style-deliverynote.css b/Elwig/Documents/style-deliverynote.css index 44543ff..190ded3 100644 --- a/Elwig/Documents/style-deliverynote.css +++ b/Elwig/Documents/style-deliverynote.css @@ -52,45 +52,45 @@ table.delivery tr.sum td { padding-top: 1mm; } -table.delivery-stats { +table.delivery-note-stats { font-size: 8pt; break-inside: avoid; break-after: avoid; } -table.delivery-stats th, -table.delivery-stats td { +table.delivery-note-stats th, +table.delivery-note-stats td { padding: 0.125mm 0; } -table.delivery-stats:not(.expanded) tr.optional { +table.delivery-note-stats:not(.expanded) tr.optional { display: none; } -table.delivery-stats tr.subheading th { +table.delivery-note-stats tr.subheading th { text-align: left; } -table.delivery-stats.expanded tr.subheading:not(:has(~ tr)), -table.delivery-stats tr.subheading:not(:has(~ tr:not(.optional))) { +table.delivery-note-stats.expanded tr.subheading:not(:has(~ tr)), +table.delivery-note-stats tr.subheading:not(:has(~ tr:not(.optional))) { display: none; } -table.delivery-stats thead th { +table.delivery-note-stats thead th { font-weight: normal; font-style: italic; text-align: right; } -table.delivery-stats thead th:first-child { +table.delivery-note-stats thead th:first-child { text-align: left; } -table.delivery-stats td { +table.delivery-note-stats td { text-align: right; } -table.delivery-stats tbody th { +table.delivery-note-stats tbody th { font-weight: normal; font-style: italic; text-align: left; diff --git a/Elwig/Helpers/AppDbContext.cs b/Elwig/Helpers/AppDbContext.cs index de2ccd4..c2b0e96 100644 --- a/Elwig/Helpers/AppDbContext.cs +++ b/Elwig/Helpers/AppDbContext.cs @@ -53,6 +53,10 @@ namespace Elwig.Helpers { 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 { @@ -206,26 +210,111 @@ namespace Elwig.Helpers { } } - public async Task> GetMemberBins(Member m, int year) { - using var cnx = await ConnectAsync(); - var (rights, obligations) = await Billing.Billing.GetMemberRightsObligations(cnx, year, m.MgNr); - var bins = await Billing.Billing.GetMemberBinWeights(m.MgNr, year, cnx); - - var list = new List<(string, string, int, int, int)>(); - foreach (var id in rights.Keys.Union(obligations.Keys).Union(bins.Keys)) { - var s = await WineVarieties.FindAsync(id[..2]); - var attrIds = id[2..]; - var a = await WineAttributes.Where(a => attrIds.Contains(a.AttrId)).ToListAsync(); - var name = (s?.Name ?? "") + (a.Count > 0 ? $" ({string.Join(" / ", a.Select(a => a.Name))})" : ""); - list.Add(( - id, name, - rights.TryGetValue(id, out var v1) ? v1 : 0, - obligations.TryGetValue(id, out var v2) ? v2 : 0, - bins.TryGetValue(id, out var v3) ? v3 : 0 - )); + 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; + } - return list; + 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; } } } diff --git a/Elwig/Helpers/Billing/Billing.cs b/Elwig/Helpers/Billing/Billing.cs index e006d81..171cb76 100644 --- a/Elwig/Helpers/Billing/Billing.cs +++ b/Elwig/Helpers/Billing/Billing.cs @@ -42,7 +42,7 @@ namespace Elwig.Helpers.Billing { var attrVals = Context.WineAttributes.ToDictionary(a => a.AttrId, a => a.FillLowerBins); var attrForced = attrVals.Where(a => a.Value == 0).Select(a => a.Key).ToArray(); using var cnx = await AppDbContext.ConnectAsync(); - var memberOblRig = await GetMemberRightsObligations(cnx, Year); + await Context.GetMemberRightsAndObligations(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?)>(); @@ -68,19 +68,15 @@ namespace Elwig.Helpers.Billing { } int lastMgNr = 0; - Dictionary? rights = null; - Dictionary? obligations = null; + Dictionary? rightsAndObligations = null; Dictionary used = new(); foreach (var (mgnr, did, dpnr, sortid, weight, kmw, qualid, attributes, modifiers, gebunden) in deliveries) { if (lastMgNr != mgnr) { - var or = memberOblRig.GetValueOrDefault(mgnr, (new(), new())); - rights = or.Item2; - obligations = or.Item1; + rightsAndObligations = await Context.GetMemberRightsAndObligations(Year, mgnr); used = new(); } if ((honorGebunden && gebunden == false) || - obligations == null || rights == null || - obligations.Count == 0 || rights.Count == 0 || + rightsAndObligations == null || rightsAndObligations.Count == 0 || qualid == "WEI" || qualid == "RSW" || qualid == "LDW") { // Explizit als ungebunden markiert, @@ -98,7 +94,7 @@ namespace Elwig.Helpers.Billing { foreach (var p in Utils.Permutate(attributes, attributes.Intersect(attrForced))) { var c = p.Count(); var key = sortid + string.Join("", p); - if (rights.ContainsKey(key) && obligations.ContainsKey(key)) { + if (rightsAndObligations.ContainsKey(key)) { int i = 4; if (c == 1) { i = (p.ElementAt(0) == attributes[0]) ? 2 : 3; @@ -106,8 +102,8 @@ namespace Elwig.Helpers.Billing { i = 1; } var u = used.GetValueOrDefault(key, 0); - var vr = Math.Max(0, Math.Min(rights[key] - u, w)); - var vo = Math.Max(0, Math.Min(obligations[key] - u, w)); + 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 v = (c == 0 || p.Select(a => attrVals[a]).Min() == 2) ? vr : vo; used[key] = u + v; inserts.Add((did, dpnr, i, key[2..], v)); @@ -131,58 +127,5 @@ namespace Elwig.Helpers.Billing { // TODO add second round to avoid under deliveries } - - public static async Task, Dictionary)>> GetMemberRightsObligations(SqliteConnection cnx, int year, int? mgnr = null) { - var members = new Dictionary, Dictionary)>(); - - using var cmd = cnx.CreateCommand(); - cmd.CommandText = $""" - SELECT mgnr, t.vtrgid, - SUM(COALESCE(area * min_kg_per_ha, 0)) / 10000 AS min_kg, - SUM(COALESCE(area * max_kg_per_ha, 0)) / 10000 AS max_kg - FROM area_commitment c - JOIN area_commitment_type t ON t.vtrgid = c.vtrgid - WHERE ({(mgnr == null ? "NULL" : mgnr)} IS NULL OR mgnr = {(mgnr == null ? "NULL" : mgnr)}) AND - (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 - """; - - var reader = await cmd.ExecuteReaderAsync(); - while (await reader.ReadAsync()) { - var m = reader.GetInt32(0); - var vtrgid = reader.GetString(1); - if (!members.ContainsKey(m)) members[m] = (new(), new()); - members[m].Item1[vtrgid] = reader.GetInt32(2); - members[m].Item2[vtrgid] = reader.GetInt32(3); - } - - return members; - } - - public static async Task<(Dictionary, Dictionary)> GetMemberRightsObligations(SqliteConnection cnx, int year, int mgnr) { - var members = await GetMemberRightsObligations(cnx, year, (int?)mgnr); - return members.GetValueOrDefault(mgnr, (new(), new())); - } - - public static async Task> GetMemberBinWeights(int mgnr, int year, SqliteConnection cnx) { - var bins = new Dictionary(); - - using var cmd = cnx.CreateCommand(); - cmd.CommandText = $""" - SELECT bin, weight - FROM v_delivery_bin - WHERE (year, mgnr) = ({year}, {mgnr}) - """; - - var reader = await cmd.ExecuteReaderAsync(); - while (await reader.ReadAsync()) { - var bin = reader.GetString(0); - bins[bin] = reader.GetInt32(1); - } - - return bins; - } } }