Compare commits

..

14 Commits

Author SHA1 Message Date
lorenz.stechauner 781077e5e3 [#25] Documents: Fix page breaking 2023-12-20 01:39:26 +01:00
lorenz.stechauner 16d429e9e4 DeliveryConfirmation: Keep last two tables together on page 2023-12-19 22:04:34 +01:00
lorenz.stechauner 18600a44da Document: Add mailto: and http: hrefs to footer 2023-12-19 20:48:01 +01:00
lorenz.stechauner 9c46974bd7 [#23] DeliveryConfirmation: Add Sortenaufteilung table 2023-12-19 20:48:01 +01:00
lorenz.stechauner 480f99234c AppDbContext: Remove support for multiple attributes in buckets 2023-12-19 19:31:35 +01:00
lorenz.stechauner 8811ca25ce [#17][#23] BusinessDocument: Remove enum RowMode 2023-12-19 18:11:02 +01:00
lorenz.stechauner 161bf31a62 [#17][#23] Documents: Unify table stylings 2023-12-19 18:10:31 +01:00
lorenz.stechauner ae00fd2c31 [#22] DeliveryConfirmation: Fix wrong border 2023-12-18 21:01:15 +01:00
lorenz.stechauner 2c48c89cfa Installer/config.ini: Comment out branch 2023-12-18 12:20:05 +01:00
lorenz.stechauner de5e62de50 MemberAdminWindow: Rename ...Memberdata... to ...MemberDataSheet... 2023-12-02 17:30:04 +01:00
lorenz.stechauner 0eed426559 [#21] AdministrationWindow: Temporarily fix UnitTextBox/TextBox casting error 2023-12-02 14:00:22 +01:00
lorenz.stechauner 03a9a3793a Config: Use Microsoft's INI implementation instead of a 3rd party's 2023-12-02 13:04:50 +01:00
lorenz.stechauner 7528764ff3 Update project to .NET 8 2023-12-02 12:28:16 +01:00
lorenz.stechauner 3576a066fe AppDbContext: Use records instead of unnamed tuples for buckets 2023-11-30 17:29:01 +01:00
28 changed files with 460 additions and 561 deletions
+124
View File
@@ -1,5 +1,7 @@
using Elwig.Helpers;
using Elwig.Models.Entities;
using System.Collections.Generic;
using System.Linq;
namespace Elwig.Documents {
public abstract class BusinessDocument : Document {
@@ -32,5 +34,127 @@ namespace Elwig.Documents {
return (addr is BillingAddr ? $"{addr.Name}\n" : "") + $"{Member.AdministrativeName}\n{addr.Address}\n{plz?.Plz} {plz?.Ort.Name.Split(",")[0]}\n{addr.PostalDest.Country.Name}";
}
}
private static string GetColGroup(IEnumerable<double> cols) {
return "<colgroup>\n" + string.Join("\n", cols.Select(g => $"<col style=\"width: {g}mm;\"/>")) + "\n</colgroup>\n";
}
public static string PrintSortenaufteilung(Dictionary<string, MemberBucket> buckets) {
List<string> attributes = ["_", ""];
List<string> names = ["kein Qual.Wein", "ohne Attribut"];
List<(string, string)> bucketAttrs = [
.. buckets
.Where(b => b.Key.Length > 2 && b.Key[2] != '_' && b.Value.DeliveryStrict > 0)
.Select(b => (b.Key[2..], b.Value.Name.Split("(")[1][..^1]))
.Distinct()
.OrderBy(v => v.Item1)
];
names.AddRange(bucketAttrs.Select(b => b.Item2));
names.Add("Gesamt");
attributes.AddRange(bucketAttrs.Select(b => b.Item1));
List<double> cols = [40];
cols.AddRange(names.Select(_ => 125.0 / names.Count));
string tbl = GetColGroup(cols);
tbl += "<thead><tr>" +
$"<th><b>Sortenaufteilung</b> [kg]</th>" +
string.Join("", names.Select(c => $"<th>{c}</th>")) +
"</tr></thead>";
tbl += string.Join("\n", buckets
.GroupBy(b => (b.Key[..2], b.Value.Name.Split("(")[0].Trim()))
.Where(g => g.Sum(a => a.Value.DeliveryStrict) > 0)
.OrderBy(g => g.Key.Item1)
.Select(g => {
var dict = g.ToDictionary(a => a.Key[2..], a => a.Value);
var vals = attributes.Select(a => dict.TryGetValue(a, out MemberBucket value) ? value.DeliveryStrict : 0).ToList();
return $"<tr><th>{g.Key.Item2}</th>" + string.Join("", vals.Select(v => "<td class=\"number\">" + (v == 0 ? "-" : $"{v:N0}") + "</td>")) +
$"<td class=\"number\">{dict.Values.Select(v => v.DeliveryStrict).Sum():N0}</td></tr>";
})
);
var totalDict = buckets.GroupBy(b => b.Key[2..]).ToDictionary(g => g.Key, g => g.Sum(a => a.Value.DeliveryStrict));
var totals = attributes.Select(a => totalDict.TryGetValue(a, out int value) ? value : 0);
tbl += "<tr class=\"sum\"><td></td>" + string.Join("", totals.Select(v => $"<td class=\"number\">{v:N0}</td>")) +
$"<td class=\"number\">{totalDict.Values.Sum():N0}</td></tr>";
return "<table class=\"sortenaufteilung small number cohere\">" + tbl + "</table>";
}
private static string FormatRow(int obligation, int right, int delivery, int? payment = null, bool isGa = false, bool showPayment = false) {
payment ??= delivery;
var baseline = showPayment ? payment : delivery;
return $"<td>{(obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
$"<td>{(right == 0 ? "-" : $"{right:N0}")}</td>" +
$"<td>{(baseline < obligation ? $"<b>{obligation - baseline:N0}</b>" : "-")}</td>" +
$"<td>{(baseline >= obligation && delivery <= right ? $"{right - delivery:N0}" : "-")}</td>" +
$"<td>{(obligation == 0 && right == 0 ? "-" : (delivery > right ? ((isGa ? "<b>" : "") + $"{delivery - right:N0}" + (isGa ? "</b>" : "")) : "-"))}</td>" +
(showPayment ? $"<td>{(isGa ? "" : obligation == 0 && right == 0 ? "-" : $"{payment:N0}")}</td>" : "") +
$"<td>{delivery:N0}</td>";
}
private static string FormatRow(MemberBucket bucket, bool isGa = false, bool showPayment = false) {
return FormatRow(bucket.Obligation, bucket.Right, bucket.Delivery, bucket.Payment, isGa, showPayment);
}
public string PrintBucketTable(Season season, Dictionary<string, MemberBucket> buckets, bool includePayment = false, bool isTiny = false, IEnumerable<string>? filter = null) {
string tbl = GetColGroup(includePayment ? [45, 17, 17, 17, 19, 16, 17, 17] : [45, 20, 20, 20, 20, 20, 20]);
tbl += $"""
<thead>
<tr>
<th><b>Lese {season.Year}</b> per {Date:dd.MM.yyyy} [kg]</th>
<th>Lieferpflicht</th>
<th>Lieferrecht</th>
<th>Unterliefert</th>
<th>Noch lieferbar</th>
<th>Überliefert</th>
{(includePayment ? "<th>Zugeteilt</th>" : "")}
<th>Geliefert</th>
</tr>
</thead>
""";
var mBuckets = buckets
.Where(b => (b.Value.Right > 0 || b.Value.Obligation > 0 || b.Value.Delivery > 0) && (filter == null || filter.Contains(b.Key[..2])))
.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);
tbl += "\n<tbody>\n";
tbl += $"<tr><th>Gesamtlieferung lt. gez. GA</th>{FormatRow(
Member.BusinessShares * season.MinKgPerBusinessShare,
Member.BusinessShares * season.MaxKgPerBusinessShare,
Member.Deliveries.Where(d => d.Year == season.Year).Sum(d => d.Weight),
isGa: true, showPayment: includePayment
)}</tr>";
if (fbs.Any()) {
tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
$"Flächenbindungen{(vtr.Any() ? " (inkl. Verträge)" : "")}:</th></tr>";
foreach (var (id, b) in fbs) {
tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment)}</tr>";
}
}
if (vtr.Any()) {
tbl += $"<tr class=\"subheading{(filter == null ? " border" : "")}\"><th colspan=\"{(includePayment ? 8 : 7)}\">" +
"Verträge:</th></tr>";
foreach (var (id, b) in vtr) {
tbl += $"<tr><th>{b.Name}</th>{FormatRow(b, showPayment: includePayment)}</tr>";
}
}
tbl += "\n</tbody>\n";
return $"<table class=\"buckets {(isTiny ? "tiny" : "small")} number cohere\">\n" + tbl + "\n</table>";
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
.address-wrapper, aside, main {
.address-wrapper, aside {
overflow: hidden;
}
-52
View File
@@ -1,49 +1,5 @@
table.credit {
font-size: 10pt;
}
table.credit th,
table.credit td {
padding: 0 0.25mm;
}
table.credit thead {
font-size: 8pt
}
table.credit thead th {
font-weight: normal;
font-style: italic;
vertical-align: bottom;
}
table.credit td {
vertical-align: top;
}
table.credit .oe,
table.credit .kmw {
text-align: center;
}
table.credit .dpnr {
text-align: center;
}
table.credit .amount,
table.credit .weight,
table.credit .price {
text-align: right;
}
table.credit .rel,
table.credit .abs {
text-align: center;
}
table.credit .amount.sum {
vertical-align: bottom;
padding-bottom: 1mm;
}
@@ -59,11 +15,3 @@ table.credit tbody tr.first td {
table.credit tbody tr.last td {
padding-bottom: 1mm;
}
table.credit tbody tr.new {
border-top: 0.5pt solid black;
}
table.credit .small {
font-size: 8pt;
}
+1 -1
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) {
+18 -95
View File
@@ -1,3 +1,4 @@
@using Elwig.Documents
@using RazorLight
@inherits TemplatePage<Elwig.Documents.DeliveryConfirmation>
@model Elwig.Documents.DeliveryConfirmation
@@ -22,7 +23,7 @@
<thead>
<tr>
<th rowspan="2" style="text-align: left;">Lieferschein-Nr.</th>
<th rowspan="2">Pos.</th>
<th rowspan="2" class="narrow">Pos.</th>
<th rowspan="2" style="text-align: left;">Sorte</th>
<th rowspan="2" style="text-align: left;">Attribut</th>
<th rowspan="2" style="text-align: left;">Qualitätsstufe</th>
@@ -32,11 +33,11 @@
<th>Davon<br/>abzuwerten</th>
</tr>
<tr>
<th>[°Oe]</th>
<th>[°KMW]</th>
<th colspan="2">[kg]</th>
<th>[kg]</th>
<th>[kg]</th>
<th class="unit">[°Oe]</th>
<th class="unit narrow">[°KMW]</th>
<th class="unit"colspan="2">[kg]</th>
<th class="unit">[kg]</th>
<th class="unit">[kg]</th>
</tr>
</thead>
<tbody>
@@ -54,121 +55,43 @@
<td class="small">@p.Variant</td>
<td class="small">@p.Attribute</td>
<td class="small">@p.QualityLevel</td>
<td rowspan="@rows" class="grad">@($"{p.Gradation.Oe:N0}")</td>
<td rowspan="@rows" class="grad">@($"{p.Gradation.Kmw:N1}")</td>
<td rowspan="@rows" class="center">@($"{p.Gradation.Oe:N0}")</td>
<td rowspan="@rows" class="center">@($"{p.Gradation.Kmw:N1}")</td>
}
@if (i > 0 && i <= p.Modifiers.Length) {
<td colspan="3" class="mod">@(p.Modifiers[i - 1])</td>
<td colspan="3" class="small mod">@(p.Modifiers[i - 1])</td>
} else if (i > 0) {
<td colspan="3"></td>
}
@if (i < p.Buckets.Length) {
var bucket = p.Buckets[i];
<td class="geb">@bucket.Name:</td>
<td class="weight">@($"{bucket.Value:N0}")</td>
<td class="small">@bucket.Name:</td>
<td class="number">@($"{bucket.Value:N0}")</td>
} else {
<td colspan="2"></td>
}
@if (i == p.Buckets.Length - 1) {
<td class="weight">@($"{p.Weight:N0}")</td>
<td class="number">@($"{p.Weight:N0}")</td>
} else {
<td></td>
}
@if (first) {
<td rowspan="@rows" class="weight"></td>
<td rowspan="@rows" class="number"></td>
first = false;
}
</tr>
lastVariant = p.Variant;
}
lastVariant = p.Variant;
}
<tr class="sum">
<td colspan="8">Gesamt:</td>
<td colspan="2" class="weight">@($"{Model.Data.Rows.Sum(p => p.Weight):N0}")</td>
<td colspan="2" class="number">@($"{Model.Data.Rows.Sum(p => p.Weight):N0}")</td>
<td></td>
</tr>
</tbody>
</table>
<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.Season.Year</b> per @($"{Model.Date:dd.MM.yyyy}") [kg]</th>
<th>Lieferpflicht</th>
<th>Lieferrecht</th>
<th>Unterliefert</th>
<th>Noch lieferbar</th>
<th>Überliefert</th>
<th>Zugeteilt</th>
<th>Geliefert</th>
</tr>
</thead>
<tbody>
@{
string FormatRow(int mode, int obligation, int right, int sum, int? payment = null) {
var isGa = mode == 0;
payment ??= sum;
return $"<td>{(mode == 1 ? "" : obligation == 0 ? "-" : $"{obligation:N0}")}</td>" +
$"<td>{(mode == 1 ? "" : right == 0 ? "-" : $"{right:N0}")}</td>" +
$"<td>{(mode == 1 ? "" : payment < obligation ? $"<b>{obligation - payment:N0}\x3c/b>" : "-")}</td>" +
$"<td>{(mode == 1 ? "" : payment >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}</td>" +
$"<td>{(mode == 1 ? "" : obligation == 0 && right == 0 ? "-" : (sum > right ? ((isGa ? "<b>" : "") + $"{sum - right:N0}" + (isGa ? "</b>" : "")) : "-"))}</td>" +
$"<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);
}
<tr>
<th>Gesamtlieferung lt. gez. GA</th>
@Raw(FormatRow(
0,
Model.Member.BusinessShares * Model.Season.MinKgPerBusinessShare,
Model.Member.BusinessShares * Model.Season.MaxKgPerBusinessShare,
Model.Member.Deliveries.Where(d => d.Year == Model.Season.Year).Sum(d => d.Weight)
))
</tr>
@if (rem.Any()) {
<tr class="subheading"><th colspan="8">Sortenaufteilung:</th></tr>
}
@foreach (var (id, (name, right, obligation, sum, payment)) in rem) {
<tr>
<th>@name</th>
@Raw(FormatRow(1, obligation, right, sum, 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) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, obligation, right, sum, 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) {
<tr>
<th>@name</th>
@Raw(FormatRow(2, obligation, right, sum, payment))
</tr>
}
</tbody>
</table>
@Raw(BusinessDocument.PrintSortenaufteilung(Model.MemberBuckets))
@Raw(Model.PrintBucketTable(Model.Season, Model.MemberBuckets, includePayment: true))
<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>
+4 -85
View File
@@ -1,52 +1,8 @@
table.delivery-confirmation {
font-size: 10pt;
margin-bottom: 5mm;
}
table.delivery-confirmation thead {
font-size: 8pt;
}
table.delivery-confirmation thead th {
font-weight: normal;
font-style: italic;
}
table.delivery-confirmation td {
overflow: hidden;
white-space: nowrap;
}
table.delivery-confirmation td[rowspan] {
vertical-align: top;
}
table.delivery-confirmation .weight {
text-align: right;
}
table.delivery-confirmation .grad {
text-align: center;
}
table.delivery-confirmation .geb {
font-size: 8pt;
}
table.delivery-confirmation .mod {
font-size: 8pt;
padding-left: 5mm;
}
table.delivery-confirmation .small {
font-size: 8pt;
}
table.delivery-confirmation tr.new td {
border-top: 0.5pt solid black;
}
table.delivery-confirmation tr:not(.first) {
break-before: avoid;
}
@@ -60,9 +16,6 @@ table.delivery-confirmation tr.trailing td {
}
table.delivery-confirmation tr.sum {
border-top: 0.5pt solid black;
break-before: avoid;
font-weight: bold;
font-size: 12pt;
}
@@ -70,44 +23,10 @@ table.delivery-confirmation tr.sum td {
padding-top: 1mm;
}
table.delivery-confirmation-stats {
font-size: 10pt;
break-inside: avoid;
table.sortenaufteilung {
break-after: avoid;
}
table.delivery-confirmation-stats th,
table.delivery-confirmation-stats td {
padding: 0.125mm 0;
overflow: hidden;
white-space: nowrap;
}
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;
}
table.delivery-confirmation-stats tr.subheading th {
font-weight: bold;
border-top: 0.5pt solid black;
table.buckets {
break-before: avoid;
}
+17 -17
View File
@@ -10,9 +10,9 @@
<colgroup>
<col style="width: 25mm;"/>
<col style="width: 5mm;"/>
<col style="width: 17mm;"/>
<col style="width: 10mm;"/>
<col style="width: 15mm;"/>
<col style="width: 8mm;"/>
<col style="width: 12mm;"/>
<col style="width: 38mm;"/>
<col style="width: 28mm;"/>
<col style="width: 10mm;"/>
@@ -22,7 +22,7 @@
<thead>
<tr>
<th rowspan="2" style="text-align: left;">Lieferschein-Nr.</th>
<th rowspan="2">Pos.</th>
<th rowspan="2" class="narrow">Pos.</th>
<th rowspan="2">Datum</th>
<th rowspan="2">Zeit</th>
<th colspan="2" rowspan="2" style="text-align: left;">Mitglied</th>
@@ -31,9 +31,9 @@
<th>Gewicht</th>
</tr>
<tr>
<th>[°Oe]</th>
<th>[°KMW]</th>
<th>[kg]</th>
<th class="unit">[°Oe]</th>
<th class="unit narrow">[°KMW]</th>
<th class="unit">[kg]</th>
</tr>
</thead>
<tbody>
@@ -41,14 +41,14 @@
<tr>
<td>@p.Delivery.LsNr</td>
<td>@p.DPNr</td>
<td>@($"{p.Delivery.Date:dd.MM.yyyy}")</td>
<td>@($"{p.Delivery.Time:HH:mm}")</td>
<td class="mgnr">@p.Delivery.Member.MgNr</td>
<td>@p.Delivery.Member.AdministrativeName</td>
<td>@p.Variant.Name</td>
<td class="grad">@($"{p.Oe:N0}")</td>
<td class="grad">@($"{p.Kmw:N1}")</td>
<td class="weight">@($"{p.Weight:N0}")</td>
<td class="small">@($"{p.Delivery.Date:dd.MM.yyyy}")</td>
<td class="small">@($"{p.Delivery.Time:HH:mm}")</td>
<td class="number">@p.Delivery.Member.MgNr</td>
<td class="small">@p.Delivery.Member.AdministrativeName</td>
<td class="small">@p.Variant.Name</td>
<td class="center">@($"{p.Oe:N0}")</td>
<td class="center">@($"{p.Kmw:N1}")</td>
<td class="number">@($"{p.Weight:N0}")</td>
</tr>
}
<tr class="sum">
@@ -58,9 +58,9 @@
}
<td colspan="2">Gesamt:</td>
<td colspan="5">(Teil-)Lieferungen: @($"{Model.Deliveries.DistinctBy(p => p.Delivery).Count():N0}") (@($"{Model.Deliveries.Count():N0}"))</td>
<td class="grad">@($"{oe:N0}")</td>
<td class="grad">@($"{kmw:N1}")</td>
<td class="weight">@($"{Model.Deliveries.Sum(p => p.Weight):N0}")</td>
<td class="center">@($"{oe:N0}")</td>
<td class="center">@($"{kmw:N1}")</td>
<td class="number">@($"{Model.Deliveries.Sum(p => p.Weight):N0}")</td>
</tr>
</tbody>
</table>
-35
View File
@@ -10,38 +10,3 @@ h2 {
font-size: 14pt;
margin-top: 2mm;
}
table.journal {
font-size: 10pt;
}
table.journal thead {
font-size: 8pt;
}
table.journal th {
font-weight: normal;
font-style: italic;
}
table.journal td {
overflow: hidden;
white-space: nowrap;
}
table.journal .mgnr,
table.journal .weight {
text-align: right;
}
table.journal .grad {
text-align: center;
}
table.journal tr.sum {
font-weight: bold;
}
table.journal tr.sum td {
border-top: 0.5pt solid black;
}
+1 -1
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
+18 -69
View File
@@ -5,7 +5,7 @@
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\DeliveryNote.css" />
<main>
<h1>@Model.Title</h1>
<table class="delivery">
<table class="delivery large">
<colgroup>
<col style="width: 10.00mm;"/>
<col style="width: 21.25mm;"/>
@@ -19,7 +19,7 @@
</colgroup>
<thead>
<tr>
<th class="main" rowspan="2" style="text-align: center;">Pos.</th>
<th class="main center narrow" rowspan="2">Pos.</th>
<th class="main" rowspan="2" colspan="2">Sorte</th>
<th class="main" rowspan="2" colspan="2">Attribut</th>
<th class="main" rowspan="2">Qualitätsstufe</th>
@@ -27,21 +27,21 @@
<th>Gewicht</th>
</tr>
<tr>
<th style="font-size: 8pt;">[°Oe]</th>
<th style="font-size: 8pt;">[°KMW]</th>
<th style="font-size: 8pt;">[kg]</th>
<th class="unit">[°Oe]</th>
<th class="unit narrow">[°KMW]</th>
<th class="unit">[kg]</th>
</tr>
</thead>
<tbody>
@foreach (var part in Model.Delivery.Parts.OrderBy(p => p.DPNr)) {
<tr class="main">
<td style="text-align: center;">@part.DPNr</td>
<td class="center">@part.DPNr</td>
<td colspan="2">@part.Variant.Name</td>
<td colspan="2">@part.Attribute?.Name</td>
<td>@part.Quality.Name</td>
<td class="narrow" style="text-align: center;">@($"{part.Oe:N0}")</td>
<td class="narrow" style="text-align: center;">@($"{part.Kmw:N1}")</td>
<td class="narrow" style="text-align: right;">@($"{part.Weight:N0}")</td>
<td class="center">@($"{part.Oe:N0}")</td>
<td class="center">@($"{part.Kmw:N1}")</td>
<td class="number">@($"{part.Weight:N0}")</td>
</tr>
<tr><td></td><td colspan="5" style="white-space: pre;"><i>Herkunft:</i> @part.OriginString</td></tr>
@if (part.Modifiers.Count() > 0) {
@@ -65,9 +65,9 @@
@if (Model.Delivery.Parts.Count() > 1) {
<tr class="main sum">
<td colspan="6">Gesamt:</td>
<td style="text-align: center;">@($"{Model.Delivery.Oe:N0}")</td>
<td style="text-align: center;">@($"{Model.Delivery.Kmw:N1}")</td>
<td style="text-align: right;">@($"{Model.Delivery.Weight:N0}")</td>
<td class="center">@($"{Model.Delivery.Oe:N0}")</td>
<td class="center">@($"{Model.Delivery.Kmw:N1}")</td>
<td class="number">@($"{Model.Delivery.Weight:N0}")</td>
</tr>
}
</tbody>
@@ -76,63 +76,12 @@
<p class="comment">Amerkung zur Lieferung: @Model.Delivery.Comment</p>
}
@if (Model.DisplayStats > 0) {
<table class="delivery-note-stats @(Model.DisplayStats > 2 ? "expanded" : "")">
<colgroup>
<col style="width: 45mm;"/>
<col style="width: 20mm;"/>
<col style="width: 20mm;"/>
<col style="width: 20mm;"/>
<col style="width: 20mm;"/>
<col style="width: 20mm;"/>
<col style="width: 20mm;"/>
</colgroup>
<thead>
<tr>
<th><b>Lese @Model.Delivery.Year</b> per @($"{Model.Date:dd.MM.yyyy}") [kg]</th>
<th>Lieferpflicht</th>
<th>Lieferrecht</th>
<th>Unterliefert</th>
<th>Noch lieferbar</th>
<th>Überliefert</th>
<th>Geliefert</th>
</tr>
</thead>
<tbody>
@{
string FormatRow(int obligation, int right, int sum) {
return $"<td>{obligation:N0}</td>" +
$"<td>{right:N0}</td>" +
$"<td>{(sum < obligation ? $"{obligation - sum:N0}" : "-")}</td>" +
$"<td>{(sum >= obligation && sum <= right ? $"{right - sum:N0}" : "-")}</td>" +
$"<td>{(sum > right ? $"{sum - right:N0}" : "-")}</td>" +
$"<td>{sum:N0}</td>";
}
var sortids = Model.Delivery.Parts.Select(p => p.SortId).ToList();
var buckets = Model.MemberBuckets.GroupBy(b => b.Key[..2]).ToDictionary(g => g.Key, g => g.Count());
}
<tr>
<th>Gesamtlieferung lt. gez. GA</th>
@Raw(FormatRow(
Model.Member.BusinessShares * Model.Delivery.Season.MinKgPerBusinessShare,
Model.Member.BusinessShares * Model.Delivery.Season.MaxKgPerBusinessShare,
Model.Member.Deliveries.Where(d => d.Year == Model.Delivery.Year).Sum(d => d.Weight)
))
</tr>
@if (Model.DisplayStats > 1) {
<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('_'))) {
<tr class="@(sortids.Contains(id[..2]) ? "" : "optional")">
<th>@name</th>
@Raw(FormatRow(obligation, right, sum))
</tr>
}
}
}
</tbody>
</table>
@Raw(Model.PrintBucketTable(
Model.Delivery.Season, Model.MemberBuckets, isTiny: true,
filter: Model.DisplayStats > 2 ? null :
Model.DisplayStats == 1 ? new List<string>() :
Model.Delivery.Parts.Select(p => p.SortId).Distinct().ToList()
))
}
</main>
@for (int i = 0; i < 2; i++) {
-51
View File
@@ -11,12 +11,6 @@ table.delivery tr:not(.main) {
break-before: avoid;
}
table.delivery th {
font-weight: normal;
font-style: italic;
font-size: 10pt;
}
table.delivery th.main {
text-align: left;
}
@@ -43,55 +37,10 @@ table.delivery tr.tight:has(+ tr:not(.tight)) td {
padding-bottom: 0.5mm !important;
}
table.delivery tr.sum {
border-top: 0.5pt solid black;
break-before: avoid;
}
table.delivery tr.sum td {
padding-top: 1mm;
}
table.delivery-note-stats {
font-size: 8pt;
break-inside: avoid;
break-after: avoid;
}
table.delivery-note-stats th,
table.delivery-note-stats td {
padding: 0.125mm 0;
}
table.delivery-note-stats:not(.expanded) tr.optional {
display: none;
}
table.delivery-note-stats tr.subheading th {
text-align: left;
}
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-note-stats thead th {
font-weight: normal;
font-style: italic;
text-align: right;
}
table.delivery-note-stats thead th:first-child {
text-align: left;
}
table.delivery-note-stats td {
text-align: right;
}
table.delivery-note-stats tbody th {
font-weight: normal;
font-style: italic;
text-align: left;
}
+5
View File
@@ -68,4 +68,9 @@ hr.page-break {
.page::after {
content: "Seite " counter(page) " von " counter(pages) !important;
}
a {
text-decoration: inherit;
color: inherit;
}
}
+113
View File
@@ -0,0 +1,113 @@
main table {
border-collapse: collapse;
margin-bottom: 10mm;
font-size: 10pt;
}
main table.large {font-size: 12pt;}
main table.tiny {
font-size: 8pt;
margin-bottom: 5mm;
}
main table tr {
break-inside: avoid;
}
main table th,
main table td {
overflow: hidden;
white-space: nowrap;
vertical-align: middle;
padding: 0.5mm 1mm;
font-weight: normal;
}
main table.small th,
main table.small td,
main table.tiny th,
main table.tiny td {
padding: 0.125mm 0.125mm;
}
main table td[rowspan] {
vertical-align: top;
}
main table thead {
font-size: 8pt;
}
main table.large thead {
font-size: 10pt;
}
main table th {
font-style: italic;
}
main table tbody {
}
main table tbody .small {
font-size: 8pt;
}
main table.number td,
main table.number th {
padding-left: 0;
padding-right: 0;
}
main table.number thead th,
main table.number td,
main table tbody td.number {
text-align: right;
}
main table.center tbody td,
main table tbody td.center {
text-align: center;
}
main table tbody th {
text-align: left;
}
main table.cohere {
break-inside: avoid;
}
main table tr.subheading th {
text-align: left;
font-weight: bold;
font-size: 10pt;
}
main table.small tr.subheading th,
main table.tiny tr.subheading th {
font-size: 8pt;
}
main table.number thead tr:first-child th:first-child {
text-align: left;
}
main table tr.sum td {
font-weight: bold;
}
main table tr.sum,
main table tr.new,
main table tr.border {
border-top: 0.5pt solid black;
}
main table tr.sum {
break-before: avoid;
}
main table th.unit {
font-size: 8pt;
}
main table th.narrow {
padding-left: 0;
padding-right: 0;
}
+3 -1
View File
@@ -33,7 +33,9 @@ namespace Elwig.Documents {
Footer = Utils.GenerateFooter("<br/>", " \u00b7 ")
.Item(c.NameFull).NextLine()
.Item(c.Address).Item($"{c.Plz} {c.Ort}").Item("Österreich").Item("Tel.", c.PhoneNr).Item("Fax", c.FaxNr).NextLine()
.Item(c.EmailAddress).Item(c.Website).Item("Betriebs-Nr.", c.LfbisNr).Item("UID", c.UstIdNr).NextLine()
.Item(c.EmailAddress != null ? $"<a href=\"mailto:{c.Name} {c.NameSuffix} <{c.EmailAddress}>\">{c.EmailAddress}</a>" : null)
.Item(c.Website != null ? $"<a href=\"http://{c.Website}/\">{c.Website}</a>" : null)
.Item("Betriebs-Nr.", c.LfbisNr).Item("UID", c.UstIdNr).NextLine()
.Item("BIC", c.Bic).Item("IBAN", c.Iban)
.ToString();
Date = DateTime.Today;
+1
View File
@@ -9,6 +9,7 @@
<meta charset="UTF-8"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.Page.css"/>
<link rel="stylesheet" href="file:///@Raw(Model.DataPath)\resources\Document.Table.css"/>
@if (Model.DoubleSided) {
<style>
@@page :left {
+1 -3
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();
}
}
}
+11 -11
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>
+9 -9
View File
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<PreserveCompilationContext>true</PreserveCompilationContext>
@@ -22,16 +22,16 @@
<ItemGroup>
<PackageReference Include="Extended.Wpf.Toolkit" Version="4.5.1" />
<PackageReference Include="ini-parser" Version="2.5.2" />
<PackageReference Include="LinqKit" Version="1.2.4" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.24" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="7.0.13" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.13" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2088.41" />
<PackageReference Include="LinqKit" Version="1.2.5" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Language" Version="6.0.25" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Proxies" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" Version="8.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2151.40" />
<PackageReference Include="RazorLight" Version="2.3.1" />
<PackageReference Include="ScottPlot.WPF" Version="4.1.68" />
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="7.0.0" />
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="8.0.0" />
</ItemGroup>
</Project>
+50 -48
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 DeliveryStrict, int Payment);
public class AppDbContext : DbContext {
public DbSet<Country> Countries { get; private set; }
@@ -60,10 +64,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, AreaComBucket>>> _memberAreaCommitmentBuckets = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBuckets = new();
private readonly Dictionary<int, Dictionary<int, Dictionary<string, int>>> _memberDeliveryBucketsStrict = 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 +207,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) {
@@ -239,6 +243,24 @@ namespace Elwig.Helpers {
_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] = new();
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();
@@ -257,32 +279,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) {
@@ -291,37 +291,39 @@ namespace Elwig.Helpers {
return _memberDeliveryBuckets[year].GetValueOrDefault(mgnr, new());
}
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, new());
}
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, 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 deliveryBucketsStrict = await GetMemberDeliveryBucketsStrict(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] = (
var attribute = await WineAttributes.FindAsync(id[2..]);
var name = (variety?.Name ?? "") + (id[2..] == "_" ? " (kein Qual.Wein)" : attribute != null ? $" ({attribute})" : "");
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),
deliveryBucketsStrict.GetValueOrDefault(id),
paymentBuckets.GetValueOrDefault(id)
);
}
+23 -1
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;
""");
}
}
}
+5 -5
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;
+17 -50
View File
@@ -1,8 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using IniParser;
using IniParser.Model;
using Microsoft.Extensions.Configuration;
namespace Elwig.Helpers {
public class Config {
@@ -13,7 +12,8 @@ namespace Elwig.Helpers {
public string? DatabaseLog = null;
public string? Branch = null;
public IList<string?[]> Scales;
private readonly List<string?[]> ScaleList = new();
private readonly List<string?[]> ScaleList = [];
private static readonly string[] trueValues = ["1", "true", "yes", "on"];
public Config(string filename) {
FileName = filename;
@@ -22,57 +22,24 @@ namespace Elwig.Helpers {
}
public void Read() {
var parser = new FileIniDataParser();
IniData? ini = null;
try {
ini = parser.ReadFile(FileName, Utils.UTF8);
} catch {}
var config = new ConfigurationBuilder().AddIniFile(FileName).Build();
if (ini == null || !ini.TryGetKey("database.file", out string db)) {
DatabaseFile = App.DataPath + "database.sqlite3";
} else if (db.Length > 1 && (db[1] == ':' || db[0] == '/' || db[0] == '\\')) {
DatabaseFile = db;
} else {
DatabaseFile = App.DataPath + db;
}
if (ini == null || !ini.TryGetKey("database.log", out string log)) {
DatabaseLog = null;
} else if (log.Length > 1 && (log[1] == ':' || log[0] == '/' || log[0] == '\\')) {
DatabaseLog = log;
} else {
DatabaseLog = App.DataPath + log;
}
if (ini == null || !ini.TryGetKey("general.branch", out string branch)) {
Branch = null;
} else {
Branch = branch;
}
if (ini == null || !ini.TryGetKey("general.debug", out string debug)) {
Debug = false;
} else {
debug = debug.ToLower();
Debug = debug == "1" || debug == "true" || debug == "yes" || debug == "on";
}
DatabaseFile = Utils.GetAbsolutePath(config["database:file"] ?? "database.sqlite3", App.DataPath);
var log = config["database:log"];
DatabaseLog = log != null ? Utils.GetAbsolutePath(log, App.DataPath) : null;
Branch = config["general:branch"];
Debug = trueValues.Contains(config["general:debug"]?.ToLower());
var scales = config.AsEnumerable().Where(i => i.Key.StartsWith("scale.")).GroupBy(i => i.Key.Split(':')[0][6..]).Select(i => i.Key);
ScaleList.Clear();
Scales = ScaleList;
if (ini != null) {
foreach (var s in ini.Sections.Where(s => s.SectionName.StartsWith("scale."))) {
string? scaleLog = null;
if (s.Keys["log"] != null) {
scaleLog = s.Keys["log"];
if (scaleLog.Length <= 1 || (scaleLog[1] != ':' && scaleLog[0] != '/' && scaleLog[0] != '\\')) {
scaleLog = App.DataPath + scaleLog;
}
}
ScaleList.Add(new string?[] {
s.SectionName[6..], s.Keys["type"], s.Keys["model"], s.Keys["connection"],
s.Keys["empty"], s.Keys["filling"], s.Keys["limit"], scaleLog
});
}
foreach (var s in scales) {
string? scaleLog = config[$"scale.{s}:log"];
if (scaleLog != null) scaleLog = Utils.GetAbsolutePath(scaleLog, App.DataPath);
ScaleList.Add([
s, config[$"scale.{s}:type"], config[$"scale.{s}:model"], config[$"scale.{s}:connection"],
config[$"scale.{s}:empty"], config[$"scale.{s}:filling"], config[$"scale.{s}:limit"], scaleLog
]);
}
}
+5
View File
@@ -11,6 +11,7 @@ using Elwig.Dialogs;
using System.Text;
using System.Numerics;
using Elwig.Models.Entities;
using System.IO;
namespace Elwig.Helpers {
public static partial class Utils {
@@ -345,5 +346,9 @@ namespace Elwig.Helpers {
}
return output.OrderByDescending(l => l.Count());
}
public static string GetAbsolutePath(string path, string basePath) {
return (path.Length > 1 && (path[1] == ':' || path[0] == '/' || path[0] == '\\')) ? Path.Combine(basePath, path) : path;
}
}
}
+7 -4
View File
@@ -427,8 +427,9 @@ namespace Elwig.Windows {
}
protected void TextBox_TextChanged(object sender, RoutedEventArgs? evt) {
var input = (TextBox)sender;
if (SenderIsRequired(input) && input.Text.Length == 0) {
var input = (Control)sender;
var tb = input as TextBox ?? (input as UnitTextBox)?.TextBox;
if (SenderIsRequired(input) && tb?.Text.Length == 0) {
ValidateInput(input, false);
ControlUtils.SetInputInvalid(input);
} else {
@@ -472,11 +473,13 @@ namespace Elwig.Windows {
}
protected void IntegerInput_TextChanged(object sender, TextChangedEventArgs evt) {
InputTextChanged((TextBox)sender, Validator.CheckInteger);
// FIXME
InputTextChanged((sender as UnitTextBox)?.TextBox ?? (TextBox)sender, Validator.CheckInteger);
}
protected void DecimalInput_TextChanged(object sender, TextChangedEventArgs evt) {
InputTextChanged((TextBox)sender, Validator.CheckDecimal);
// FIXME
InputTextChanged((sender as UnitTextBox)?.TextBox ?? (TextBox)sender, Validator.CheckDecimal);
}
protected void PartialDateInput_TextChanged(object sender, TextChangedEventArgs evt) {
+6 -3
View File
@@ -1,4 +1,5 @@
<local:AdministrationWindow x:Class="Elwig.Windows.MemberAdminWindow"
<local:AdministrationWindow
x:Class="Elwig.Windows.MemberAdminWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -53,8 +54,10 @@
<MenuItem Header="Drucken">
<MenuItem x:Name="Menu_Print_Letterhead" Header="Briefkopf drucken"
Click="Menu_Print_Letterhead_Click"/>
<MenuItem x:Name="Menu_Show_Memberdata" Header="Stammdatenblatt anzeigen" Click="Menu_Show_Memberdata_Click"/>
<MenuItem x:Name="Menu_Print_Memberdata" Header="Stammdatenblatt drucken" Click="Menu_Print_Memberdata_Click"/>
<MenuItem x:Name="Menu_Show_MemberDataSheet" Header="Stammdatenblatt anzeigen" IsEnabled="False" Tag="Print"
Click="Menu_Show_MemberDataSheet_Click"/>
<MenuItem x:Name="Menu_Print_MemberDataSheet" Header="Stammdatenblatt drucken" IsEnabled="False" Tag="Print"
Click="Menu_Print_MemberDataSheet_Click"/>
<MenuItem Header="Briefköpfe drucken">
<MenuItem x:Name="Menu_Print_Letterheads_MgNr" Header="nach MgNr. sortiert" IsEnabled="False" Tag="Print"
Click="Menu_Print_Letterheads_MgNr_Click"/>
+18 -17
View File
@@ -15,29 +15,29 @@ using Elwig.Documents;
namespace Elwig.Windows {
public partial class MemberAdminWindow : AdministrationWindow {
private List<string> TextFilter = new();
private List<string> TextFilter = [];
private readonly RoutedCommand CtrlF = new();
private readonly (ComboBox, TextBox, TextBox)[] PhoneNrInputs;
private static ObservableCollection<KeyValuePair<string, string>> PhoneNrTypes { get; set; } = new() {
private static ObservableCollection<KeyValuePair<string, string>> PhoneNrTypes { get; set; } = [
new("landline", "Tel.-Nr. (Festnetz)"),
new("mobile", "Tel.-Nr. (mobil)"),
new("fax", "Fax-Nr."),
};
];
public MemberAdminWindow() {
InitializeComponent();
CtrlF.InputGestures.Add(new KeyGesture(Key.F, ModifierKeys.Control));
CommandBindings.Add(new CommandBinding(CtrlF, FocusSearchInput));
ExemptInputs = new Control[] {
ExemptInputs = [
SearchInput, ActiveMemberInput, MemberList,
};
RequiredInputs = new Control[] {
];
RequiredInputs = [
MgNrInput, GivenNameInput, FamilyNameInput,
AddressInput, PlzInput, OrtInput, BillingOrtInput,
BusinessSharesInput, BranchInput, DefaultKgInput
};
PhoneNrInputs = new (ComboBox, TextBox, TextBox)[] {
];
PhoneNrInputs = [
(PhoneNr1TypeInput, PhoneNr1Input, PhoneNr1CommentInput),
(PhoneNr2TypeInput, PhoneNr2Input, PhoneNr2CommentInput),
(PhoneNr3TypeInput, PhoneNr3Input, PhoneNr3CommentInput),
@@ -47,7 +47,7 @@ namespace Elwig.Windows {
(PhoneNr7TypeInput, PhoneNr7Input, PhoneNr7CommentInput),
(PhoneNr8TypeInput, PhoneNr8Input, PhoneNr8CommentInput),
(PhoneNr9TypeInput, PhoneNr9Input, PhoneNr9CommentInput),
};
];
foreach (var input in PhoneNrInputs) input.Item1.ItemsSource = PhoneNrTypes;
InitializeDelayTimer(SearchInput, SearchInput_TextChanged);
@@ -57,8 +57,9 @@ namespace Elwig.Windows {
private void Window_Loaded(object sender, RoutedEventArgs evt) {
Menu_Print_Letterheads_MgNr.IsEnabled = App.IsPrintingReady;
Menu_Print_Letterheads_Name.IsEnabled = App.IsPrintingReady;
Menu_Show_Memberdata.IsEnabled = App.IsPrintingReady;
Menu_Print_Memberdata.IsEnabled = App.IsPrintingReady;
Menu_Print_Letterheads_Plz.IsEnabled = App.IsPrintingReady;
Menu_Show_MemberDataSheet.IsEnabled = App.IsPrintingReady;
Menu_Print_MemberDataSheet.IsEnabled = App.IsPrintingReady;
ActiveMemberInput.IsChecked = true;
UpdatePhoneNrInputVisibility();
@@ -339,7 +340,7 @@ namespace Elwig.Windows {
await PrintLetterheads(2);
}
private async void Menu_Print_Memberdata_Click(object sender, RoutedEventArgs evt) {
private async void Menu_Print_MemberDataSheet_Click(object sender, RoutedEventArgs evt) {
if (MemberList.SelectedItem is not Member m)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
@@ -353,7 +354,7 @@ namespace Elwig.Windows {
}
}
private async void Menu_Show_Memberdata_Click(object sender, RoutedEventArgs evt) {
private async void Menu_Show_MemberDataSheet_Click(object sender, RoutedEventArgs evt) {
if (MemberList.SelectedItem is not Member m)
return;
Mouse.OverrideCursor = Cursors.AppStarting;
@@ -649,8 +650,8 @@ namespace Elwig.Windows {
Menu_Member_SendEmail.IsEnabled = m.EmailAddresses.Count > 0;
Menu_Print_Letterhead.IsEnabled = true;
Menu_Show_Memberdata.IsEnabled = true;
Menu_Print_Memberdata.IsEnabled = true;
Menu_Show_MemberDataSheet.IsEnabled = true;
Menu_Print_MemberDataSheet.IsEnabled = true;
FinishInputFilling();
}
@@ -658,8 +659,8 @@ namespace Elwig.Windows {
new protected void ClearInputs(bool validate = false) {
Menu_Member_SendEmail.IsEnabled = false;
Menu_Print_Letterhead.IsEnabled = false;
Menu_Show_Memberdata.IsEnabled = false;
Menu_Print_Memberdata.IsEnabled = false;
Menu_Show_MemberDataSheet.IsEnabled = false;
Menu_Print_MemberDataSheet.IsEnabled = false;
StatusDeliveriesLastSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason - 1}): -";
StatusDeliveriesThisSeason.Text = $"Lieferungen ({Utils.CurrentLastSeason}): -";
StatusAreaCommitment.Text = "Gebundene Fläche: -";
+1 -1
View File
@@ -1,7 +1,7 @@
[general]
; Only needed, if more than one branch is stored in database
branch = Zweigstelle
;branch = Zweigstelle
;debug = true
[database]
+1 -1
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<TargetFramework>net8.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>