DeliveryConfirmation: Add Statistics table
This commit is contained in:
		@@ -93,7 +93,60 @@
 | 
			
		||||
            </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    <!-- TODO add Gesamtlieferung tabelle -->
 | 
			
		||||
    <table class="delivery-confirmation-stats">
 | 
			
		||||
        <colgroup>
 | 
			
		||||
            <col style="width: 45mm;"/>
 | 
			
		||||
            <col style="width: 17mm;"/>
 | 
			
		||||
            <col style="width: 17mm;"/>
 | 
			
		||||
            <col style="width: 17mm;"/>
 | 
			
		||||
            <col style="width: 19mm;"/>
 | 
			
		||||
            <col style="width: 16mm;"/>
 | 
			
		||||
            <col style="width: 17mm;"/>
 | 
			
		||||
            <col style="width: 17mm;"/>
 | 
			
		||||
        </colgroup>
 | 
			
		||||
        <thead>
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th><b>Lese @Model.Year</b> per @($"{Model.Date:dd.MM.yyyy}") [kg]</th>
 | 
			
		||||
                <th>Lieferpflicht</th>
 | 
			
		||||
                <th>Lieferrecht</th>
 | 
			
		||||
                <th>Unterliefert<br/>(bzgl. Zuget.)</th>
 | 
			
		||||
                <th>Noch zu liefern<br/>(bzgl. Gelft.)</th>
 | 
			
		||||
                <th>Überliefert<br/>(bzgl. Gelft.)</th>
 | 
			
		||||
                <th>Zugeteilt</th>
 | 
			
		||||
                <th>Geliefert</th>
 | 
			
		||||
            </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
            @{
 | 
			
		||||
                string FormatRow(int obligation, int right, int sum, int? payment = null) {
 | 
			
		||||
                    var isGa = payment == null;
 | 
			
		||||
                    payment ??= sum;
 | 
			
		||||
                    return $"<td>{obligation:N0}</td>" +
 | 
			
		||||
                        $"<td>{right:N0}</td>" +
 | 
			
		||||
                        $"<td>{(payment < obligation ? $"<b>{obligation - payment:N0}\x3c/b>" : "-")}</td>" +
 | 
			
		||||
                        $"<td>{(sum >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}</td>" +
 | 
			
		||||
                        $"<td>{(obligation == 0 && right == 0 ? "-" : (sum > right ? ((isGa ? "<b>" : "") + $"{sum - right:N0}" + (isGa ? "</b>" : "")) : "-"))}</td>" +
 | 
			
		||||
                        $"<td>{(obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}</td>" +
 | 
			
		||||
                        $"<td>{sum:N0}</td>";
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>Gesamtlieferung lt. gez. GA</th>
 | 
			
		||||
                @Raw(FormatRow(Model.Member.DeliveryObligation, Model.Member.DeliveryRight, Model.Member.Deliveries.Where(d => d.Year == Model.Year).Sum(d => d.Weight)))
 | 
			
		||||
            </tr>
 | 
			
		||||
            <tr class="subheading">
 | 
			
		||||
                <th>Flächenbindungen:</th>
 | 
			
		||||
            </tr>
 | 
			
		||||
            @foreach (var (id, (name, right, obligation, sum, payment)) in Model.MemberBins.OrderBy(b => b.Key)) {
 | 
			
		||||
                if (right > 0 || obligation > 0 || sum > 0) {
 | 
			
		||||
                    <tr>
 | 
			
		||||
                        <th>@name</th>
 | 
			
		||||
                        @Raw(FormatRow(obligation, right, sum, payment))
 | 
			
		||||
                    </tr>
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    <div class="text" style="margin-top: 2em;">
 | 
			
		||||
        @if (Model.Text != null) {
 | 
			
		||||
            <p class="comment" style="white-space: pre-wrap; break-inside: avoid;">@Model.Text</p>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ namespace Elwig.Documents {
 | 
			
		||||
        public int Year;
 | 
			
		||||
        public IEnumerable<DeliveryPart> Deliveries;
 | 
			
		||||
        public string? Text = App.Client.TextDeliveryConfirmation;
 | 
			
		||||
        public Dictionary<string, (string, int, int, int, int)> 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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -76,8 +76,7 @@
 | 
			
		||||
    <p class="comment">Amerkung zur Lieferung: @Model.Delivery.Comment</p>
 | 
			
		||||
}
 | 
			
		||||
@if (Model.DisplayStats > 0) {
 | 
			
		||||
    <div id="delivery-stats">
 | 
			
		||||
        <table class="delivery-stats @(Model.DisplayStats > 2 ? "expanded" : "")">
 | 
			
		||||
    <table class="delivery-note-stats @(Model.DisplayStats > 2 ? "expanded" : "")">
 | 
			
		||||
        <colgroup>
 | 
			
		||||
            <col style="width: 45mm;"/>
 | 
			
		||||
            <col style="width: 20mm;"/>
 | 
			
		||||
@@ -109,7 +108,7 @@
 | 
			
		||||
                        $"<td>{sum:N0}</td>";
 | 
			
		||||
                }
 | 
			
		||||
                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());
 | 
			
		||||
                var bins = Model.MemberBins.GroupBy(b => b.Key[..2]).ToDictionary(g => g.Key, g => g.Count());
 | 
			
		||||
            }
 | 
			
		||||
            <tr>
 | 
			
		||||
                <th>Gesamtlieferung lt. gez. GA</th>
 | 
			
		||||
@@ -119,7 +118,7 @@
 | 
			
		||||
                <tr class="subheading">
 | 
			
		||||
                    <th>Flächenbindungen:</th>
 | 
			
		||||
                </tr>
 | 
			
		||||
                    @foreach (var (id, name, right, obligation, sum) in Model.MemberBins.OrderBy(b => b.Item1)) {
 | 
			
		||||
                @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('_'))) {
 | 
			
		||||
                        <tr class="@(sortids.Contains(id[..2]) ? "" : "optional")">
 | 
			
		||||
                            <th>@name</th>
 | 
			
		||||
@@ -130,7 +129,6 @@
 | 
			
		||||
            }
 | 
			
		||||
        </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
    </div>
 | 
			
		||||
}
 | 
			
		||||
</main>
 | 
			
		||||
@for (int i = 0; i < 2; i++) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@ namespace Elwig.Documents {
 | 
			
		||||
 | 
			
		||||
        public Delivery Delivery;
 | 
			
		||||
        public string? Text;
 | 
			
		||||
        public IEnumerable<(string, string, int, int, int)> MemberBins;
 | 
			
		||||
        public Dictionary<string, (string, int, int, int, int)> MemberBins;
 | 
			
		||||
 | 
			
		||||
        // 0 - none
 | 
			
		||||
        // 1 - GA only
 | 
			
		||||
@@ -27,7 +27,7 @@ namespace Elwig.Documents {
 | 
			
		||||
                $"</tbody></table>";
 | 
			
		||||
            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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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<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 {
 | 
			
		||||
@@ -206,26 +210,111 @@ namespace Elwig.Helpers {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IEnumerable<(string, string, int, int, int)>> 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<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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            return list;
 | 
			
		||||
        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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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<string, int>? rights = null;
 | 
			
		||||
            Dictionary<string, int>? obligations = null;
 | 
			
		||||
            Dictionary<string, (int, int)>? rightsAndObligations = null;
 | 
			
		||||
            Dictionary<string, int> 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<int, (Dictionary<string, int>, Dictionary<string, int>)>> GetMemberRightsObligations(SqliteConnection cnx, int year, int? mgnr = null) {
 | 
			
		||||
            var members = new Dictionary<int, (Dictionary<string, int>, Dictionary<string, int>)>();
 | 
			
		||||
 | 
			
		||||
            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<string, int>, Dictionary<string, int>)> 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<Dictionary<string, int>> GetMemberBinWeights(int mgnr, int year, SqliteConnection cnx) {
 | 
			
		||||
            var bins = new Dictionary<string, int>();
 | 
			
		||||
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user